/* Any copyright is dedicated to the Public Domain. * http://creativecommons.org/publicdomain/zero/1.0/ */ const TESTROOT = "http://example.com/browser/toolkit/mozapps/extensions/test/xpinstall/"; const TESTROOT2 = "http://example.org/browser/toolkit/mozapps/extensions/test/xpinstall/"; const SECUREROOT = "https://example.com/browser/toolkit/mozapps/extensions/test/xpinstall/"; const XPINSTALL_URL = "chrome://mozapps/content/xpinstall/xpinstallConfirm.xul"; const PREF_INSTALL_REQUIREBUILTINCERTS = "extensions.install.requireBuiltInCerts"; const PROGRESS_NOTIFICATION = "addon-progress"; const { REQUIRE_SIGNING } = Cu.import("resource://gre/modules/addons/AddonConstants.jsm", {}); const { Task } = Cu.import("resource://gre/modules/Task.jsm"); var rootDir = getRootDirectory(gTestPath); var rootPath = rootDir.split('/'); var chromeName = rootPath[0] + '//' + rootPath[2]; var croot = chromeName + "/content/browser/toolkit/mozapps/extensions/test/xpinstall/"; var jar = getJar(croot); if (jar) { var tmpdir = extractJarToTmp(jar); croot = 'file://' + tmpdir.path + '/'; } const CHROMEROOT = croot; var gApp = document.getElementById("bundle_brand").getString("brandShortName"); var gVersion = Services.appinfo.version; function getObserverTopic(aNotificationId) { let topic = aNotificationId; if (topic == "xpinstall-disabled") topic = "addon-install-disabled"; else if (topic == "addon-progress") topic = "addon-install-started"; else if (topic == "addon-install-restart") topic = "addon-install-complete"; return topic; } function waitForProgressNotification(aPanelOpen = false, aExpectedCount = 1) { return Task.spawn(function* () { let notificationId = PROGRESS_NOTIFICATION; info("Waiting for " + notificationId + " notification"); let topic = getObserverTopic(notificationId); let observerPromise = new Promise(resolve => { Services.obs.addObserver(function observer(aSubject, aTopic, aData) { // Ignore the progress notification unless that is the notification we want if (notificationId != PROGRESS_NOTIFICATION && aTopic == getObserverTopic(PROGRESS_NOTIFICATION)) { return; } Services.obs.removeObserver(observer, topic); resolve(); }, topic, false); }); let panelEventPromise; if (aPanelOpen) { panelEventPromise = Promise.resolve(); } else { panelEventPromise = new Promise(resolve => { PopupNotifications.panel.addEventListener("popupshowing", function eventListener() { PopupNotifications.panel.removeEventListener("popupshowing", eventListener); resolve(); }); }); } yield observerPromise; yield panelEventPromise; info("Saw a notification"); ok(PopupNotifications.isPanelOpen, "Panel should be open"); is(PopupNotifications.panel.childNodes.length, aExpectedCount, "Should be the right number of notifications"); if (PopupNotifications.panel.childNodes.length) { let nodes = Array.from(PopupNotifications.panel.childNodes); let notification = nodes.find(n => n.id == notificationId + "-notification"); ok(notification, `Should have seen the right notification`); } return PopupNotifications.panel; }); } function waitForNotification(aId, aExpectedCount = 1) { return Task.spawn(function* () { info("Waiting for " + aId + " notification"); let topic = getObserverTopic(aId); let observerPromise = new Promise(resolve => { Services.obs.addObserver(function observer(aSubject, aTopic, aData) { // Ignore the progress notification unless that is the notification we want if (aId != PROGRESS_NOTIFICATION && aTopic == getObserverTopic(PROGRESS_NOTIFICATION)) { return; } Services.obs.removeObserver(observer, topic); resolve(); }, topic, false); }); let panelEventPromise = new Promise(resolve => { PopupNotifications.panel.addEventListener("PanelUpdated", function eventListener(e) { // Skip notifications that are not the one that we are supposed to be looking for if (e.detail.indexOf(aId) == -1) { return; } PopupNotifications.panel.removeEventListener("PanelUpdated", eventListener); resolve(); }); }); yield observerPromise; yield panelEventPromise; info("Saw a notification"); ok(PopupNotifications.isPanelOpen, "Panel should be open"); is(PopupNotifications.panel.childNodes.length, aExpectedCount, "Should be the right number of notifications"); if (PopupNotifications.panel.childNodes.length) { let nodes = Array.from(PopupNotifications.panel.childNodes); let notification = nodes.find(n => n.id == aId + "-notification"); ok(notification, `Should have seen the right notification`); } return PopupNotifications.panel; }); } function waitForNotificationClose() { return new Promise(resolve => { info("Waiting for notification to close"); PopupNotifications.panel.addEventListener("popuphidden", function listener() { PopupNotifications.panel.removeEventListener("popuphidden", listener, false); resolve(); }, false); }); } function waitForInstallDialog() { return Task.spawn(function* () { if (Preferences.get("xpinstall.customConfirmationUI", false)) { yield waitForNotification("addon-install-confirmation"); return; } info("Waiting for install dialog"); let window = yield new Promise(resolve => { Services.wm.addListener({ onOpenWindow: function(aXULWindow) { Services.wm.removeListener(this); resolve(aXULWindow); }, onCloseWindow: function(aXULWindow) { }, onWindowTitleChange: function(aXULWindow, aNewTitle) { } }); }); info("Install dialog opened, waiting for focus"); let domwindow = window.QueryInterface(Ci.nsIInterfaceRequestor) .getInterface(Ci.nsIDOMWindow); yield new Promise(resolve => { waitForFocus(function() { resolve(); }, domwindow); }); info("Saw install dialog"); is(domwindow.document.location.href, XPINSTALL_URL, "Should have seen the right window open"); // Override the countdown timer on the accept button let button = domwindow.document.documentElement.getButton("accept"); button.disabled = false; return; }); } function removeTab() { return Promise.all([ waitForNotificationClose(), BrowserTestUtils.removeTab(gBrowser.selectedTab) ]); } function acceptInstallDialog() { if (Preferences.get("xpinstall.customConfirmationUI", false)) { document.getElementById("addon-install-confirmation-accept").click(); } else { let win = Services.wm.getMostRecentWindow("Addons:Install"); win.document.documentElement.acceptDialog(); } } function cancelInstallDialog() { if (Preferences.get("xpinstall.customConfirmationUI", false)) { document.getElementById("addon-install-confirmation-cancel").click(); } else { let win = Services.wm.getMostRecentWindow("Addons:Install"); win.document.documentElement.cancelDialog(); } } function waitForSingleNotification(aCallback) { return Task.spawn(function* () { while (PopupNotifications.panel.childNodes.length == 2) { yield new Promise(resolve => executeSoon(resolve)); info("Waiting for single notification"); // Notification should never close while we wait ok(PopupNotifications.isPanelOpen, "Notification should still be open"); } }); } function setupRedirect(aSettings) { var url = "https://example.com/browser/toolkit/mozapps/extensions/test/xpinstall/redirect.sjs?mode=setup"; for (var name in aSettings) { url += "&" + name + "=" + aSettings[name]; } var req = new XMLHttpRequest(); req.open("GET", url, false); req.send(null); } function getInstalls() { return new Promise(resolve => { AddonManager.getAllInstalls(installs => resolve(installs)); }); } var TESTS = [ function test_disabledInstall() { return Task.spawn(function* () { Services.prefs.setBoolPref("xpinstall.enabled", false); let notificationPromise = waitForNotification("xpinstall-disabled"); let triggers = encodeURIComponent(JSON.stringify({ "XPI": "amosigned.xpi" })); BrowserTestUtils.openNewForegroundTab(gBrowser, TESTROOT + "installtrigger.html?" + triggers); let panel = yield notificationPromise; let notification = panel.childNodes[0]; is(notification.button.label, "Enable", "Should have seen the right button"); is(notification.getAttribute("label"), "Software installation is currently disabled. Click Enable and try again."); let closePromise = waitForNotificationClose(); // Click on Enable EventUtils.synthesizeMouseAtCenter(notification.button, {}); yield closePromise; try { ok(Services.prefs.getBoolPref("xpinstall.enabled"), "Installation should be enabled"); } catch (e) { ok(false, "xpinstall.enabled should be set"); } yield BrowserTestUtils.removeTab(gBrowser.selectedTab); let installs = yield getInstalls(); is(installs.length, 0, "Shouldn't be any pending installs"); }); }, function test_blockedInstall() { return Task.spawn(function* () { let notificationPromise = waitForNotification("addon-install-blocked"); let triggers = encodeURIComponent(JSON.stringify({ "XPI": "amosigned.xpi" })); BrowserTestUtils.openNewForegroundTab(gBrowser, TESTROOT + "installtrigger.html?" + triggers); let panel = yield notificationPromise; let notification = panel.childNodes[0]; is(notification.button.label, "Allow", "Should have seen the right button"); is(notification.getAttribute("origin"), "example.com", "Should have seen the right origin host"); is(notification.getAttribute("label"), gApp + " prevented this site from asking you to install software on your computer.", "Should have seen the right message"); let dialogPromise = waitForInstallDialog(); // Click on Allow EventUtils.synthesizeMouse(notification.button, 20, 10, {}); // Notification should have changed to progress notification ok(PopupNotifications.isPanelOpen, "Notification should still be open"); notification = panel.childNodes[0]; is(notification.id, "addon-progress-notification", "Should have seen the progress notification"); yield dialogPromise; notificationPromise = waitForNotification("addon-install-restart"); acceptInstallDialog(); panel = yield notificationPromise; notification = panel.childNodes[0]; is(notification.button.label, "Restart Now", "Should have seen the right button"); is(notification.getAttribute("label"), "XPI Test will be installed after you restart " + gApp + ".", "Should have seen the right message"); let installs = yield getInstalls(); is(installs.length, 1, "Should be one pending install"); installs[0].cancel(); yield removeTab(); }); }, function test_whitelistedInstall() { return Task.spawn(function* () { let originalTab = gBrowser.selectedTab; let tab; gBrowser.selectedTab = originalTab; let pm = Services.perms; pm.add(makeURI("http://example.com/"), "install", pm.ALLOW_ACTION); let progressPromise = waitForProgressNotification(); let dialogPromise = waitForInstallDialog(); let triggers = encodeURIComponent(JSON.stringify({ "XPI": "amosigned.xpi" })); BrowserTestUtils.openNewForegroundTab(gBrowser, TESTROOT + "installtrigger.html?" + triggers).then(newTab => tab = newTab); yield progressPromise; yield dialogPromise; yield BrowserTestUtils.waitForCondition(() => !!tab, "tab should be present"); is(gBrowser.selectedTab, tab, "tab selected in response to the addon-install-confirmation notification"); let notificationPromise = waitForNotification("addon-install-restart"); acceptInstallDialog(); let panel = yield notificationPromise; let notification = panel.childNodes[0]; is(notification.button.label, "Restart Now", "Should have seen the right button"); is(notification.getAttribute("label"), "XPI Test will be installed after you restart " + gApp + ".", "Should have seen the right message"); let installs = yield getInstalls(); is(installs.length, 1, "Should be one pending install"); installs[0].cancel(); Services.perms.remove(makeURI("http://example.com/"), "install"); yield removeTab(); }); }, function test_failedDownload() { return Task.spawn(function* () { let pm = Services.perms; pm.add(makeURI("http://example.com/"), "install", pm.ALLOW_ACTION); let progressPromise = waitForProgressNotification(); let failPromise = waitForNotification("addon-install-failed"); let triggers = encodeURIComponent(JSON.stringify({ "XPI": "missing.xpi" })); BrowserTestUtils.openNewForegroundTab(gBrowser, TESTROOT + "installtrigger.html?" + triggers); yield progressPromise; let panel = yield failPromise; let notification = panel.childNodes[0]; is(notification.getAttribute("label"), "The add-on could not be downloaded because of a connection failure.", "Should have seen the right message"); Services.perms.remove(makeURI("http://example.com/"), "install"); yield removeTab(); }); }, function test_corruptFile() { return Task.spawn(function* () { let pm = Services.perms; pm.add(makeURI("http://example.com/"), "install", pm.ALLOW_ACTION); let progressPromise = waitForProgressNotification(); let failPromise = waitForNotification("addon-install-failed"); let triggers = encodeURIComponent(JSON.stringify({ "XPI": "corrupt.xpi" })); BrowserTestUtils.openNewForegroundTab(gBrowser, TESTROOT + "installtrigger.html?" + triggers); yield progressPromise; let panel = yield failPromise; let notification = panel.childNodes[0]; is(notification.getAttribute("label"), "The add-on downloaded from this site could not be installed " + "because it appears to be corrupt.", "Should have seen the right message"); Services.perms.remove(makeURI("http://example.com/"), "install"); yield removeTab(); }); }, function test_incompatible() { return Task.spawn(function* () { let pm = Services.perms; pm.add(makeURI("http://example.com/"), "install", pm.ALLOW_ACTION); let progressPromise = waitForProgressNotification(); let failPromise = waitForNotification("addon-install-failed"); let triggers = encodeURIComponent(JSON.stringify({ "XPI": "incompatible.xpi" })); BrowserTestUtils.openNewForegroundTab(gBrowser, TESTROOT + "installtrigger.html?" + triggers); yield progressPromise; let panel = yield failPromise; let notification = panel.childNodes[0]; is(notification.getAttribute("label"), "XPI Test could not be installed because it is not compatible with " + gApp + " " + gVersion + ".", "Should have seen the right message"); Services.perms.remove(makeURI("http://example.com/"), "install"); yield removeTab(); }); }, function test_restartless() { return Task.spawn(function* () { let pm = Services.perms; pm.add(makeURI("http://example.com/"), "install", pm.ALLOW_ACTION); let progressPromise = waitForProgressNotification(); let dialogPromise = waitForInstallDialog(); let triggers = encodeURIComponent(JSON.stringify({ "XPI": "restartless.xpi" })); gBrowser.selectedTab = gBrowser.addTab(); gBrowser.loadURI(TESTROOT + "installtrigger.html?" + triggers); yield progressPromise; yield dialogPromise; let notificationPromise = waitForNotification("addon-install-complete"); acceptInstallDialog(); let panel = yield notificationPromise; let notification = panel.childNodes[0]; is(notification.getAttribute("label"), "XPI Test has been installed successfully.", "Should have seen the right message"); let installs = yield getInstalls(); is(installs.length, 0, "Should be no pending installs"); let addon = yield new Promise(resolve => { AddonManager.getAddonByID("restartless-xpi@tests.mozilla.org", result => { resolve(result); }); }); addon.uninstall(); Services.perms.remove(makeURI("http://example.com/"), "install"); let closePromise = waitForNotificationClose(); gBrowser.removeTab(gBrowser.selectedTab); yield closePromise; }); }, function test_multiple() { return Task.spawn(function* () { let pm = Services.perms; pm.add(makeURI("http://example.com/"), "install", pm.ALLOW_ACTION); let progressPromise = waitForProgressNotification(); let dialogPromise = waitForInstallDialog(); let triggers = encodeURIComponent(JSON.stringify({ "Unsigned XPI": "amosigned.xpi", "Restartless XPI": "restartless.xpi" })); BrowserTestUtils.openNewForegroundTab(gBrowser, TESTROOT + "installtrigger.html?" + triggers); let panel = yield progressPromise; yield dialogPromise; let notificationPromise = waitForNotification("addon-install-restart"); acceptInstallDialog(); yield notificationPromise; let notification = panel.childNodes[0]; is(notification.button.label, "Restart Now", "Should have seen the right button"); is(notification.getAttribute("label"), "2 add-ons will be installed after you restart " + gApp + ".", "Should have seen the right message"); let installs = yield getInstalls(); is(installs.length, 1, "Should be one pending install"); installs[0].cancel(); let addon = yield new Promise(resolve => { AddonManager.getAddonByID("restartless-xpi@tests.mozilla.org", function (result) { resolve(result); }); }); addon.uninstall(); Services.perms.remove(makeURI("http://example.com/"), "install"); yield removeTab(); }); }, function test_sequential() { return Task.spawn(function* () { // This test is only relevant if using the new doorhanger UI if (!Preferences.get("xpinstall.customConfirmationUI", false)) { return; } let pm = Services.perms; pm.add(makeURI("http://example.com/"), "install", pm.ALLOW_ACTION); let progressPromise = waitForProgressNotification(); let dialogPromise = waitForInstallDialog(); let triggers = encodeURIComponent(JSON.stringify({ "Restartless XPI": "restartless.xpi" })); BrowserTestUtils.openNewForegroundTab(gBrowser, TESTROOT + "installtrigger.html?" + triggers); yield progressPromise; yield dialogPromise; // Should see the right add-on let container = document.getElementById("addon-install-confirmation-content"); is(container.childNodes.length, 1, "Should be one item listed"); is(container.childNodes[0].firstChild.getAttribute("value"), "XPI Test", "Should have the right add-on"); progressPromise = waitForProgressNotification(true, 2); triggers = encodeURIComponent(JSON.stringify({ "Theme XPI": "theme.xpi" })); gBrowser.loadURI(TESTROOT + "installtrigger.html?" + triggers); yield progressPromise; // Should still have the right add-on in the confirmation notification is(container.childNodes.length, 1, "Should be one item listed"); is(container.childNodes[0].firstChild.getAttribute("value"), "XPI Test", "Should have the right add-on"); // Wait for the install to complete, we won't see a new confirmation // notification yield new Promise(resolve => { Services.obs.addObserver(function observer() { Services.obs.removeObserver(observer, "addon-install-confirmation"); resolve(); }, "addon-install-confirmation", false); }); // Make sure browser-addons.js executes first yield new Promise(resolve => executeSoon(resolve)); // Should have dropped the progress notification is(PopupNotifications.panel.childNodes.length, 1, "Should be the right number of notifications"); is(PopupNotifications.panel.childNodes[0].id, "addon-install-confirmation-notification", "Should only be showing one install confirmation"); // Should still have the right add-on in the confirmation notification is(container.childNodes.length, 1, "Should be one item listed"); is(container.childNodes[0].firstChild.getAttribute("value"), "XPI Test", "Should have the right add-on"); cancelInstallDialog(); ok(PopupNotifications.isPanelOpen, "Panel should still be open"); is(PopupNotifications.panel.childNodes.length, 1, "Should be the right number of notifications"); is(PopupNotifications.panel.childNodes[0].id, "addon-install-confirmation-notification", "Should still have an install confirmation open"); // Should have the next add-on's confirmation dialog is(container.childNodes.length, 1, "Should be one item listed"); is(container.childNodes[0].firstChild.getAttribute("value"), "Theme Test", "Should have the right add-on"); Services.perms.remove(makeURI("http://example.com"), "install"); let closePromise = waitForNotificationClose(); cancelInstallDialog(); yield closePromise; yield BrowserTestUtils.removeTab(gBrowser.selectedTab); }); }, function test_someUnverified() { return Task.spawn(function* () { // This test is only relevant if using the new doorhanger UI and allowing // unsigned add-ons if (!Preferences.get("xpinstall.customConfirmationUI", false) || Preferences.get("xpinstall.signatures.required", true) || REQUIRE_SIGNING) { return; } let pm = Services.perms; pm.add(makeURI("http://example.com/"), "install", pm.ALLOW_ACTION); let progressPromise = waitForProgressNotification(); let dialogPromise = waitForInstallDialog(); let triggers = encodeURIComponent(JSON.stringify({ "Extension XPI": "restartless-unsigned.xpi", "Theme XPI": "theme.xpi" })); BrowserTestUtils.openNewForegroundTab(gBrowser, TESTROOT + "installtrigger.html?" + triggers); yield progressPromise; yield dialogPromise; let notification = document.getElementById("addon-install-confirmation-notification"); let message = notification.getAttribute("label"); is(message, "Caution: This site would like to install 2 add-ons in " + gApp + ", some of which are unverified. Proceed at your own risk.", "Should see the right message"); let container = document.getElementById("addon-install-confirmation-content"); is(container.childNodes.length, 2, "Should be two items listed"); is(container.childNodes[0].firstChild.getAttribute("value"), "XPI Test", "Should have the right add-on"); is(container.childNodes[0].lastChild.getAttribute("class"), "addon-install-confirmation-unsigned", "Should have the unverified marker"); is(container.childNodes[1].firstChild.getAttribute("value"), "Theme Test", "Should have the right add-on"); is(container.childNodes[1].childNodes.length, 1, "Shouldn't have the unverified marker"); let notificationPromise = waitForNotification("addon-install-restart"); acceptInstallDialog(); yield notificationPromise; let [addon, theme] = yield new Promise(resolve => { AddonManager.getAddonsByIDs(["restartless-xpi@tests.mozilla.org", "theme-xpi@tests.mozilla.org"], function(addons) { resolve(addons); }); }); addon.uninstall(); // Installing a new theme tries to switch to it, switch back to the // default theme. theme.userDisabled = true; theme.uninstall(); Services.perms.remove(makeURI("http://example.com/"), "install"); yield removeTab(); }); }, function test_allUnverified() { return Task.spawn(function* () { // This test is only relevant if using the new doorhanger UI and allowing // unsigned add-ons if (!Preferences.get("xpinstall.customConfirmationUI", false) || Preferences.get("xpinstall.signatures.required", true) || REQUIRE_SIGNING) { return; } let pm = Services.perms; pm.add(makeURI("http://example.com/"), "install", pm.ALLOW_ACTION); let progressPromise = waitForProgressNotification(); let dialogPromise = waitForInstallDialog(); let triggers = encodeURIComponent(JSON.stringify({ "Extension XPI": "restartless-unsigned.xpi" })); BrowserTestUtils.openNewForegroundTab(gBrowser, TESTROOT + "installtrigger.html?" + triggers); yield progressPromise; yield dialogPromise; let notification = document.getElementById("addon-install-confirmation-notification"); let message = notification.getAttribute("label"); is(message, "Caution: This site would like to install an unverified add-on in " + gApp + ". Proceed at your own risk."); let container = document.getElementById("addon-install-confirmation-content"); is(container.childNodes.length, 1, "Should be one item listed"); is(container.childNodes[0].firstChild.getAttribute("value"), "XPI Test", "Should have the right add-on"); is(container.childNodes[0].childNodes.length, 1, "Shouldn't have the unverified marker"); let notificationPromise = waitForNotification("addon-install-complete"); acceptInstallDialog(); yield notificationPromise; let addon = yield new Promise(resolve => { AddonManager.getAddonByID("restartless-xpi@tests.mozilla.org", function(result) { resolve(result); }); }); addon.uninstall(); Services.perms.remove(makeURI("http://example.com/"), "install"); yield removeTab(); }); }, function test_url() { return Task.spawn(function* () { let progressPromise = waitForProgressNotification(); let dialogPromise = waitForInstallDialog(); gBrowser.selectedTab = gBrowser.addTab("about:blank"); yield BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser); gBrowser.loadURI(TESTROOT + "amosigned.xpi"); yield progressPromise; yield dialogPromise; let notificationPromise = waitForNotification("addon-install-restart"); acceptInstallDialog(); let panel = yield notificationPromise; let notification = panel.childNodes[0]; is(notification.button.label, "Restart Now", "Should have seen the right button"); is(notification.getAttribute("label"), "XPI Test will be installed after you restart " + gApp + ".", "Should have seen the right message"); let installs = yield getInstalls(); is(installs.length, 1, "Should be one pending install"); installs[0].cancel(); yield removeTab(); }); }, function test_localFile() { return Task.spawn(function* () { let cr = Components.classes["@mozilla.org/chrome/chrome-registry;1"] .getService(Components.interfaces.nsIChromeRegistry); let path; try { path = cr.convertChromeURL(makeURI(CHROMEROOT + "corrupt.xpi")).spec; } catch (ex) { path = CHROMEROOT + "corrupt.xpi"; } let failPromise = new Promise(resolve => { Services.obs.addObserver(function observer() { Services.obs.removeObserver(observer, "addon-install-failed"); resolve(); }, "addon-install-failed", false); }); gBrowser.selectedTab = gBrowser.addTab("about:blank"); yield BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser); gBrowser.loadURI(path); yield failPromise; // Wait for the browser code to add the failure notification yield waitForSingleNotification(); let notification = PopupNotifications.panel.childNodes[0]; is(notification.id, "addon-install-failed-notification", "Should have seen the install fail"); is(notification.getAttribute("label"), "This add-on could not be installed because it appears to be corrupt.", "Should have seen the right message"); yield removeTab(); }); }, function test_tabClose() { return Task.spawn(function* () { if (!Preferences.get("xpinstall.customConfirmationUI", false)) { runNextTest(); return; } let progressPromise = waitForProgressNotification(); let dialogPromise = waitForInstallDialog(); gBrowser.selectedTab = gBrowser.addTab("about:blank"); yield BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser); gBrowser.loadURI(TESTROOT + "amosigned.xpi"); yield progressPromise; yield dialogPromise; let installs = yield getInstalls(); is(installs.length, 1, "Should be one pending install"); let closePromise = waitForNotificationClose(); yield BrowserTestUtils.removeTab(gBrowser.selectedTab); yield closePromise; installs = yield getInstalls(); is(installs.length, 0, "Should be no pending install since the tab is closed"); }); }, // Add-ons should be cancelled and the install notification destroyed when // navigating to a new origin function test_tabNavigate() { return Task.spawn(function* () { if (!Preferences.get("xpinstall.customConfirmationUI", false)) { return; } let pm = Services.perms; pm.add(makeURI("http://example.com/"), "install", pm.ALLOW_ACTION); let progressPromise = waitForProgressNotification(); let dialogPromise = waitForInstallDialog(); let triggers = encodeURIComponent(JSON.stringify({ "Extension XPI": "amosigned.xpi" })); BrowserTestUtils.openNewForegroundTab(gBrowser, TESTROOT + "installtrigger.html?" + triggers); yield progressPromise; yield dialogPromise; let closePromise = waitForNotificationClose(); let loadPromise = BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser); gBrowser.loadURI("about:blank"); yield closePromise; let installs = yield getInstalls(); is(installs.length, 0, "Should be no pending install"); Services.perms.remove(makeURI("http://example.com/"), "install"); yield loadPromise; yield BrowserTestUtils.removeTab(gBrowser.selectedTab); }); }, function test_urlBar() { return Task.spawn(function* () { let progressPromise = waitForProgressNotification(); let dialogPromise = waitForInstallDialog(); gBrowser.selectedTab = gBrowser.addTab("about:blank"); yield BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser); gURLBar.value = TESTROOT + "amosigned.xpi"; gURLBar.focus(); EventUtils.synthesizeKey("VK_RETURN", {}); yield progressPromise; let installDialog = yield dialogPromise; let notificationPromise = waitForNotification("addon-install-restart"); acceptInstallDialog(installDialog); let panel = yield notificationPromise; let notification = panel.childNodes[0]; is(notification.button.label, "Restart Now", "Should have seen the right button"); is(notification.getAttribute("label"), "XPI Test will be installed after you restart " + gApp + ".", "Should have seen the right message"); let installs = yield getInstalls(); is(installs.length, 1, "Should be one pending install"); installs[0].cancel(); yield removeTab(); }); }, function test_wrongHost() { return Task.spawn(function* () { let requestedUrl = TESTROOT2 + "enabled.html"; gBrowser.selectedTab = gBrowser.addTab(); let loadedPromise = BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser, false, requestedUrl); gBrowser.loadURI(TESTROOT2 + "enabled.html"); yield loadedPromise; let progressPromise = waitForProgressNotification(); let notificationPromise = waitForNotification("addon-install-failed"); gBrowser.loadURI(TESTROOT + "corrupt.xpi"); yield progressPromise; let panel = yield notificationPromise; let notification = panel.childNodes[0]; is(notification.getAttribute("label"), "The add-on downloaded from this site could not be installed " + "because it appears to be corrupt.", "Should have seen the right message"); yield removeTab(); }); }, function test_reload() { return Task.spawn(function* () { let pm = Services.perms; pm.add(makeURI("http://example.com/"), "install", pm.ALLOW_ACTION); let progressPromise = waitForProgressNotification(); let dialogPromise = waitForInstallDialog(); let triggers = encodeURIComponent(JSON.stringify({ "Unsigned XPI": "amosigned.xpi" })); BrowserTestUtils.openNewForegroundTab(gBrowser, TESTROOT + "installtrigger.html?" + triggers); yield progressPromise; yield dialogPromise; let notificationPromise = waitForNotification("addon-install-restart"); acceptInstallDialog(); let panel = yield notificationPromise; let notification = panel.childNodes[0]; is(notification.button.label, "Restart Now", "Should have seen the right button"); is(notification.getAttribute("label"), "XPI Test will be installed after you restart " + gApp + ".", "Should have seen the right message"); function testFail() { ok(false, "Reloading should not have hidden the notification"); } PopupNotifications.panel.addEventListener("popuphiding", testFail, false); let requestedUrl = TESTROOT2 + "enabled.html"; let loadedPromise = BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser, false, requestedUrl); gBrowser.loadURI(TESTROOT2 + "enabled.html"); yield loadedPromise; PopupNotifications.panel.removeEventListener("popuphiding", testFail, false); let installs = yield getInstalls(); is(installs.length, 1, "Should be one pending install"); installs[0].cancel(); Services.perms.remove(makeURI("http://example.com/"), "install"); yield removeTab(); }); }, function test_theme() { return Task.spawn(function* () { let pm = Services.perms; pm.add(makeURI("http://example.com/"), "install", pm.ALLOW_ACTION); let progressPromise = waitForProgressNotification(); let dialogPromise = waitForInstallDialog(); let triggers = encodeURIComponent(JSON.stringify({ "Theme XPI": "theme.xpi" })); BrowserTestUtils.openNewForegroundTab(gBrowser, TESTROOT + "installtrigger.html?" + triggers); yield progressPromise; yield dialogPromise; let notificationPromise = waitForNotification("addon-install-restart"); acceptInstallDialog(); let panel = yield notificationPromise; let notification = panel.childNodes[0]; is(notification.button.label, "Restart Now", "Should have seen the right button"); is(notification.getAttribute("label"), "Theme Test will be installed after you restart " + gApp + ".", "Should have seen the right message"); let addon = yield new Promise(resolve => { AddonManager.getAddonByID("{972ce4c6-7e08-4474-a285-3208198ce6fd}", function(result) { resolve(result); }); }); ok(addon.userDisabled, "Should be switching away from the default theme."); // Undo the pending theme switch addon.userDisabled = false; addon = yield new Promise(resolve => { AddonManager.getAddonByID("theme-xpi@tests.mozilla.org", function(result) { resolve(result); }); }); isnot(addon, null, "Test theme will have been installed"); addon.uninstall(); Services.perms.remove(makeURI("http://example.com/"), "install"); yield removeTab(); }); }, function test_renotifyBlocked() { return Task.spawn(function* () { let notificationPromise = waitForNotification("addon-install-blocked"); let triggers = encodeURIComponent(JSON.stringify({ "XPI": "amosigned.xpi" })); BrowserTestUtils.openNewForegroundTab(gBrowser, TESTROOT + "installtrigger.html?" + triggers); let panel = yield notificationPromise; let closePromise = waitForNotificationClose(); // hide the panel (this simulates the user dismissing it) panel.hidePopup(); yield closePromise; info("Timeouts after this probably mean bug 589954 regressed"); yield new Promise(resolve => executeSoon(resolve)); notificationPromise = waitForNotification("addon-install-blocked"); gBrowser.loadURI(TESTROOT + "installtrigger.html?" + triggers); yield notificationPromise; let installs = yield getInstalls(); is(installs.length, 2, "Should be two pending installs"); closePromise = waitForNotificationClose(); yield BrowserTestUtils.removeTab(gBrowser.selectedTab); yield closePromise; installs = yield getInstalls(); is(installs.length, 0, "Should have cancelled the installs"); }); }, function test_renotifyInstalled() { return Task.spawn(function* () { let pm = Services.perms; pm.add(makeURI("http://example.com/"), "install", pm.ALLOW_ACTION); let progressPromise = waitForProgressNotification(); let dialogPromise = waitForInstallDialog(); let triggers = encodeURIComponent(JSON.stringify({ "XPI": "amosigned.xpi" })); BrowserTestUtils.openNewForegroundTab(gBrowser, TESTROOT + "installtrigger.html?" + triggers); yield progressPromise; yield dialogPromise; // Wait for the complete notification let notificationPromise = waitForNotification("addon-install-restart"); acceptInstallDialog(); let panel = yield notificationPromise; let closePromise = waitForNotificationClose(); // hide the panel (this simulates the user dismissing it) panel.hidePopup(); yield closePromise; // Install another yield new Promise(resolve => executeSoon(resolve)); progressPromise = waitForProgressNotification(); dialogPromise = waitForInstallDialog(); gBrowser.loadURI(TESTROOT + "installtrigger.html?" + triggers); yield progressPromise; yield dialogPromise; info("Timeouts after this probably mean bug 589954 regressed"); // Wait for the complete notification notificationPromise = waitForNotification("addon-install-restart"); acceptInstallDialog(); yield notificationPromise; let installs = yield getInstalls(); is(installs.length, 1, "Should be one pending installs"); installs[0].cancel(); Services.perms.remove(makeURI("http://example.com/"), "install"); yield removeTab(); }); }, function test_cancel() { return Task.spawn(function* () { let pm = Services.perms; pm.add(makeURI("http://example.com/"), "install", pm.ALLOW_ACTION); let notificationPromise = waitForNotification(PROGRESS_NOTIFICATION); let triggers = encodeURIComponent(JSON.stringify({ "XPI": "slowinstall.sjs?file=amosigned.xpi" })); BrowserTestUtils.openNewForegroundTab(gBrowser, TESTROOT + "installtrigger.html?" + triggers); let panel = yield notificationPromise; let notification = panel.childNodes[0]; // Close the notification let anchor = document.getElementById("addons-notification-icon"); anchor.click(); // Reopen the notification anchor.click(); ok(PopupNotifications.isPanelOpen, "Notification should still be open"); is(PopupNotifications.panel.childNodes.length, 1, "Should be only one notification"); notification = panel.childNodes[0]; is(notification.id, "addon-progress-notification", "Should have seen the progress notification"); let button = document.getElementById("addon-progress-cancel"); // Cancel the download let install = notification.notification.options.installs[0]; let cancelledPromise = new Promise(resolve => { install.addListener({ onDownloadCancelled: function() { install.removeListener(this); resolve(); } }); }); EventUtils.synthesizeMouseAtCenter(button, {}); yield cancelledPromise; yield new Promise(resolve => executeSoon(resolve)); ok(!PopupNotifications.isPanelOpen, "Notification should be closed"); let installs = yield getInstalls(); is(installs.length, 0, "Should be no pending install"); Services.perms.remove(makeURI("http://example.com/"), "install"); yield BrowserTestUtils.removeTab(gBrowser.selectedTab); }); }, function test_failedSecurity() { return Task.spawn(function* () { Services.prefs.setBoolPref(PREF_INSTALL_REQUIREBUILTINCERTS, false); setupRedirect({ "Location": TESTROOT + "amosigned.xpi" }); let notificationPromise = waitForNotification("addon-install-blocked"); let triggers = encodeURIComponent(JSON.stringify({ "XPI": "redirect.sjs?mode=redirect" })); BrowserTestUtils.openNewForegroundTab(gBrowser, SECUREROOT + "installtrigger.html?" + triggers); let panel = yield notificationPromise; let notification = panel.childNodes[0]; // Click on Allow EventUtils.synthesizeMouse(notification.button, 20, 10, {}); // Notification should have changed to progress notification ok(PopupNotifications.isPanelOpen, "Notification should still be open"); is(PopupNotifications.panel.childNodes.length, 1, "Should be only one notification"); notification = panel.childNodes[0]; is(notification.id, "addon-progress-notification", "Should have seen the progress notification"); // Wait for it to fail yield new Promise(resolve => { Services.obs.addObserver(function observer() { Services.obs.removeObserver(observer, "addon-install-failed"); resolve(); }, "addon-install-failed", false); }); // Allow the browser code to add the failure notification and then wait // for the progress notification to dismiss itself yield waitForSingleNotification(); is(PopupNotifications.panel.childNodes.length, 1, "Should be only one notification"); notification = panel.childNodes[0]; is(notification.id, "addon-install-failed-notification", "Should have seen the install fail"); Services.prefs.setBoolPref(PREF_INSTALL_REQUIREBUILTINCERTS, true); yield removeTab(); }); } ]; var gTestStart = null; var XPInstallObserver = { observe: function (aSubject, aTopic, aData) { var installInfo = aSubject.QueryInterface(Components.interfaces.amIWebInstallInfo); info("Observed " + aTopic + " for " + installInfo.installs.length + " installs"); installInfo.installs.forEach(function(aInstall) { info("Install of " + aInstall.sourceURI.spec + " was in state " + aInstall.state); }); } }; add_task(function* () { requestLongerTimeout(4); Services.prefs.setBoolPref("extensions.logging.enabled", true); Services.prefs.setBoolPref("extensions.strictCompatibility", true); Services.prefs.setBoolPref("extensions.install.requireSecureOrigin", false); Services.prefs.setIntPref("security.dialog_enable_delay", 0); Services.obs.addObserver(XPInstallObserver, "addon-install-started", false); Services.obs.addObserver(XPInstallObserver, "addon-install-blocked", false); Services.obs.addObserver(XPInstallObserver, "addon-install-failed", false); Services.obs.addObserver(XPInstallObserver, "addon-install-complete", false); registerCleanupFunction(function() { // Make sure no more test parts run in case we were timed out TESTS = []; AddonManager.getAllInstalls(function(aInstalls) { aInstalls.forEach(function(aInstall) { aInstall.cancel(); }); }); Services.prefs.clearUserPref("extensions.logging.enabled"); Services.prefs.clearUserPref("extensions.strictCompatibility"); Services.prefs.clearUserPref("extensions.install.requireSecureOrigin"); Services.prefs.clearUserPref("security.dialog_enable_delay"); Services.obs.removeObserver(XPInstallObserver, "addon-install-started"); Services.obs.removeObserver(XPInstallObserver, "addon-install-blocked"); Services.obs.removeObserver(XPInstallObserver, "addon-install-failed"); Services.obs.removeObserver(XPInstallObserver, "addon-install-complete"); }); for (let i = 0; i < TESTS.length; ++i) { if (gTestStart) info("Test part took " + (Date.now() - gTestStart) + "ms"); ok(!PopupNotifications.isPanelOpen, "Notification should be closed"); let installs = yield new Promise(resolve => { AddonManager.getAllInstalls(function(aInstalls) { resolve(aInstalls); }); }); is(installs.length, 0, "Should be no active installs"); info("Running " + TESTS[i].name); gTestStart = Date.now(); yield TESTS[i](); } });