From 5c2d5eb30cd540fb65238f15900299e35e9adc68 Mon Sep 17 00:00:00 2001 From: Gijs Kruitbosch Date: Fri, 23 Jan 2015 17:55:56 +0000 Subject: [PATCH] Bug 1111540 - setting app in about:preferences should also work on Windows, r=jaws --- .../preferences/in-content/applications.js | 64 ++++++---- .../preferences/in-content/tests/browser.ini | 2 + .../tests/browser_change_app_handler.js | 110 ++++++++++++++++++ .../preferences/in-content/tests/head.js | 68 +++++++---- 4 files changed, 197 insertions(+), 47 deletions(-) create mode 100644 browser/components/preferences/in-content/tests/browser_change_app_handler.js diff --git a/browser/components/preferences/in-content/applications.js b/browser/components/preferences/in-content/applications.js index 218c1b379637..1db83aabaf22 100644 --- a/browser/components/preferences/in-content/applications.js +++ b/browser/components/preferences/in-content/applications.js @@ -1506,16 +1506,21 @@ var gApplicationsPane = { } // Create a menu item for selecting a local application. + let canOpenWithOtherApp = true; #ifdef XP_WIN // On Windows, selecting an application to open another application // would be meaningless so we special case executables. - var executableType = Cc["@mozilla.org/mime;1"].getService(Ci.nsIMIMEService) + let executableType = Cc["@mozilla.org/mime;1"].getService(Ci.nsIMIMEService) .getTypeFromExtension("exe"); - if (handlerInfo.type != executableType) + canOpenWithOtherApp = handlerInfo.type != executableType; #endif + if (canOpenWithOtherApp) { let menuItem = document.createElement("menuitem"); - menuItem.setAttribute("oncommand", "gApplicationsPane.chooseApp(event)"); + menuItem.className = "choose-app-item"; + menuItem.addEventListener("command", function(e) { + gApplicationsPane.chooseApp(e); + }); let label = this._prefsBundle.getString("useOtherApp"); menuItem.setAttribute("label", label); menuItem.setAttribute("tooltiptext", label); @@ -1527,7 +1532,10 @@ var gApplicationsPane = { let menuItem = document.createElement("menuseparator"); menuPopup.appendChild(menuItem); menuItem = document.createElement("menuitem"); - menuItem.setAttribute("oncommand", "gApplicationsPane.manageApp(event)"); + menuItem.className = "manage-app-item"; + menuItem.addEventListener("command", function(e) { + gApplicationsPane.manageApp(e); + }); menuItem.setAttribute("label", this._prefsBundle.getString("manageApp")); menuPopup.appendChild(menuItem); } @@ -1700,20 +1708,23 @@ var gApplicationsPane = { var typeItem = this._list.selectedItem; var handlerInfo = this._handledTypes[typeItem.type]; + let onComplete = () => { + // Rebuild the actions menu so that we revert to the previous selection, + // or "Always ask" if the previous default application has been removed + this.rebuildActionsMenu(); + + // update the richlistitem too. Will be visible when selecting another row + typeItem.setAttribute("actionDescription", + this._describePreferredAction(handlerInfo)); + if (!this._setIconClassForPreferredAction(handlerInfo, typeItem)) { + typeItem.setAttribute("actionIcon", + this._getIconURLForPreferredAction(handlerInfo)); + } + }; + gSubDialog.open("chrome://browser/content/preferences/applicationManager.xul", - "resizable=no", handlerInfo); + "resizable=no", handlerInfo, onComplete); - // Rebuild the actions menu so that we revert to the previous selection, - // or "Always ask" if the previous default application has been removed - this.rebuildActionsMenu(); - - // update the richlistitem too. Will be visible when selecting another row - typeItem.setAttribute("actionDescription", - this._describePreferredAction(handlerInfo)); - if (!this._setIconClassForPreferredAction(handlerInfo, typeItem)) { - typeItem.setAttribute("actionIcon", - this._getIconURLForPreferredAction(handlerInfo)); - } }, chooseApp: function(aEvent) { @@ -1762,17 +1773,20 @@ var gApplicationsPane = { params.filename = null; params.handlerApp = null; + let onAppSelected = () => { + if (this.isValidHandlerApp(params.handlerApp)) { + handlerApp = params.handlerApp; + + // Add the app to the type's list of possible handlers. + handlerInfo.addPossibleApplicationHandler(handlerApp); + } + + chooseAppCallback(handlerApp); + }; + gSubDialog.open("chrome://global/content/appPicker.xul", - null, params); + null, params, onAppSelected); - if (this.isValidHandlerApp(params.handlerApp)) { - handlerApp = params.handlerApp; - - // Add the app to the type's list of possible handlers. - handlerInfo.addPossibleApplicationHandler(handlerApp); - } - - chooseAppCallback(handlerApp); #else let winTitle = this._prefsBundle.getString("fpTitleChooseApp"); let fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker); diff --git a/browser/components/preferences/in-content/tests/browser.ini b/browser/components/preferences/in-content/tests/browser.ini index a0783fcfc99c..0d9bc7613fb1 100644 --- a/browser/components/preferences/in-content/tests/browser.ini +++ b/browser/components/preferences/in-content/tests/browser.ini @@ -10,6 +10,8 @@ support-files = [browser_bug795764_cachedisabled.js] [browser_bug1018066_resetScrollPosition.js] [browser_bug1020245_openPreferences_to_paneContent.js] +[browser_change_app_handler.js] +run-if = os == "win" # This test tests the windows-specific app selection dialog, so can't run on non-Windows [browser_connection.js] [browser_connection_bug388287.js] [browser_healthreport.js] diff --git a/browser/components/preferences/in-content/tests/browser_change_app_handler.js b/browser/components/preferences/in-content/tests/browser_change_app_handler.js new file mode 100644 index 000000000000..7a5dde31336c --- /dev/null +++ b/browser/components/preferences/in-content/tests/browser_change_app_handler.js @@ -0,0 +1,110 @@ +let gMimeSvc = Cc["@mozilla.org/mime;1"].getService(Ci.nsIMIMEService); +let gHandlerSvc = Cc["@mozilla.org/uriloader/handler-service;1"].getService(Ci.nsIHandlerService); + +function setupFakeHandler() { + let info = gMimeSvc.getFromTypeAndExtension("text/plain", "foo.txt"); + ok(info.possibleLocalHandlers.length, "Should have at least one known handler"); + let handler = info.possibleLocalHandlers.queryElementAt(0, Ci.nsILocalHandlerApp); + + let infoToModify = gMimeSvc.getFromTypeAndExtension("text/x-test-handler", null); + infoToModify.possibleApplicationHandlers.appendElement(handler, false); + + gHandlerSvc.store(infoToModify); +} + +function promisePopupEvent(popup, ev) { + return new Promise((resolve, reject) => { + popup.addEventListener(ev, () => { + popup.removeEventListener(ev, arguments.callee); + resolve(); + }, false); + }); +} + +add_task(function*() { + setupFakeHandler(); + yield openPreferencesViaOpenPreferencesAPI("applications", null, {leaveOpen: true}); + let win = gBrowser.selectedBrowser.contentWindow; + + let container = win.document.getElementById("handlersView"); + let ourItem = container.querySelector("richlistitem[type='text/x-test-handler']"); + ok(ourItem, "handlersView is present"); + ourItem.scrollIntoView(); + container.selectItem(ourItem); + ok(ourItem.selected, "Should be able to select our item."); + + let list = yield waitForCondition(() => win.document.getAnonymousElementByAttribute(ourItem, "class", "actionsMenu")); + let popup = list.firstChild; + let popupShownPromise = promisePopupEvent(popup, "popupshown"); + list.boxObject.openMenu(true); + yield popupShownPromise; + + let dialogLoadedPromise = promiseLoadSubDialog("chrome://global/content/appPicker.xul"); + let popupHiddenPromise = promisePopupEvent(popup, "popuphidden"); + + let chooseItem = popup.querySelector(".choose-app-item"); + chooseItem.click(); + popup.hidePopup(); // Not clear why we need to do this manually, but we do. :-\ + + yield popupHiddenPromise; + let dialog = yield dialogLoadedPromise; + let dialogDoc = dialog.document; + let dialogList = dialogDoc.getElementById("app-picker-listbox"); + dialogList.selectItem(dialogList.firstChild); + let selectedApp = dialogList.firstChild.handlerApp; + dialogDoc.documentElement.acceptDialog(); + + // Verify results are correct in mime service: + let mimeInfo = gMimeSvc.getFromTypeAndExtension("text/x-test-handler", null); + ok(mimeInfo.preferredApplicationHandler.equals(selectedApp), "App should be set as preferred."); + + // Check that we display this result: + list = yield waitForCondition(() => win.document.getAnonymousElementByAttribute(ourItem, "class", "actionsMenu")); + ok(list.selectedItem, "Should have a selected item"); + ok(mimeInfo.preferredApplicationHandler.equals(list.selectedItem.handlerApp), + "App should be visible as preferred item."); + + + // Now try to 'manage' this list: + popup = list.firstChild; + popupShownPromise = promisePopupEvent(popup, "popupshown"); + list.boxObject.openMenu(true); + yield popupShownPromise; + + dialogLoadedPromise = promiseLoadSubDialog("chrome://browser/content/preferences/applicationManager.xul"); + popupHiddenPromise = promisePopupEvent(popup, "popuphidden"); + + let manageItem = popup.querySelector(".manage-app-item"); + manageItem.click(); + popup.hidePopup(); + + yield popupHiddenPromise; + dialog = yield dialogLoadedPromise; + dialogDoc = dialog.document; + dialogList = dialogDoc.getElementById("appList"); + let itemToRemove = dialogList.querySelector('listitem[label="' + selectedApp.name + '"]'); + dialogList.selectItem(itemToRemove); + let itemsBefore = dialogList.children.length; + dialogDoc.getElementById("remove").click(); + ok(!itemToRemove.parentNode, "Item got removed from DOM"); + is(dialogList.children.length, itemsBefore - 1, "Item got removed"); + dialogDoc.documentElement.acceptDialog(); + + // Verify results are correct in mime service: + mimeInfo = gMimeSvc.getFromTypeAndExtension("text/x-test-handler", null); + ok(!mimeInfo.preferredApplicationHandler, "App should no longer be set as preferred."); + + // Check that we display this result: + list = yield waitForCondition(() => win.document.getAnonymousElementByAttribute(ourItem, "class", "actionsMenu")); + ok(list.selectedItem, "Should have a selected item"); + ok(!list.selectedItem.handlerApp, + "No app should be visible as preferred item."); + + gBrowser.removeCurrentTab(); +}); + +registerCleanupFunction(function() { + let infoToModify = gMimeSvc.getFromTypeAndExtension("text/x-test-handler", null); + gHandlerSvc.remove(infoToModify); +}); + diff --git a/browser/components/preferences/in-content/tests/head.js b/browser/components/preferences/in-content/tests/head.js index 9ab30d42c750..6ee86d02e3e9 100644 --- a/browser/components/preferences/in-content/tests/head.js +++ b/browser/components/preferences/in-content/tests/head.js @@ -39,35 +39,38 @@ function open_preferences(aCallback) { } function openAndLoadSubDialog(aURL, aFeatures = null, aParams = null, aClosingCallback = null) { - let dialog = content.gSubDialog.open(aURL, aFeatures, aParams, aClosingCallback); - let deferred = Promise.defer(); + let promise = promiseLoadSubDialog(aURL); + content.gSubDialog.open(aURL, aFeatures, aParams, aClosingCallback); + return promise; +} - content.gSubDialog._frame.addEventListener("load", function load(aEvent) { - if (aEvent.target.contentWindow.location == "about:blank") - return; - content.gSubDialog._frame.removeEventListener("load", load); +function promiseLoadSubDialog(aURL) { + return new Promise((resolve, reject) => { + content.gSubDialog._frame.addEventListener("load", function load(aEvent) { + if (aEvent.target.contentWindow.location == "about:blank") + return; + content.gSubDialog._frame.removeEventListener("load", load); - ise(content.gSubDialog._frame.contentWindow.location.toString(), aURL, - "Check the proper URL is loaded"); + ise(content.gSubDialog._frame.contentWindow.location.toString(), aURL, + "Check the proper URL is loaded"); - // Check visibility - is_element_visible(content.gSubDialog._overlay, "Overlay is visible"); + // Check visibility + is_element_visible(content.gSubDialog._overlay, "Overlay is visible"); - // Check that stylesheets were injected - let expectedStyleSheetURLs = content.gSubDialog._injectedStyleSheets.slice(0); - for (let styleSheet of content.gSubDialog._frame.contentDocument.styleSheets) { - let i = expectedStyleSheetURLs.indexOf(styleSheet.href); - if (i >= 0) { - info("found " + styleSheet.href); - expectedStyleSheetURLs.splice(i, 1); + // Check that stylesheets were injected + let expectedStyleSheetURLs = content.gSubDialog._injectedStyleSheets.slice(0); + for (let styleSheet of content.gSubDialog._frame.contentDocument.styleSheets) { + let i = expectedStyleSheetURLs.indexOf(styleSheet.href); + if (i >= 0) { + info("found " + styleSheet.href); + expectedStyleSheetURLs.splice(i, 1); + } } - } - ise(expectedStyleSheetURLs.length, 0, "All expectedStyleSheetURLs should have been found"); + ise(expectedStyleSheetURLs.length, 0, "All expectedStyleSheetURLs should have been found"); - deferred.resolve(dialog); + resolve(content.gSubDialog._frame.contentWindow); + }); }); - - return deferred.promise; } /** @@ -139,3 +142,24 @@ function openPreferencesViaOpenPreferencesAPI(aPane, aAdvancedTab, aOptions) { return deferred.promise; } + +function waitForCondition(aConditionFn, aMaxTries=50, aCheckInterval=100) { + return new Promise((resolve, reject) => { + function tryNow() { + tries++; + let rv = aConditionFn(); + if (rv) { + resolve(rv); + } else if (tries < aMaxTries) { + tryAgain(); + } else { + reject("Condition timed out: " + aConditionFn.toSource()); + } + } + function tryAgain() { + setTimeout(tryNow, aCheckInterval); + } + let tries = 0; + tryAgain(); + }); +}