diff options
Diffstat (limited to 'toolkit/mozapps/extensions/test/xpinstall')
80 files changed, 6192 insertions, 0 deletions
diff --git a/toolkit/mozapps/extensions/test/xpinstall/amosigned.xpi b/toolkit/mozapps/extensions/test/xpinstall/amosigned.xpi Binary files differnew file mode 100644 index 0000000000..f2948e6994 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpinstall/amosigned.xpi diff --git a/toolkit/mozapps/extensions/test/xpinstall/authRedirect.sjs b/toolkit/mozapps/extensions/test/xpinstall/authRedirect.sjs new file mode 100644 index 0000000000..fffcb9f255 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpinstall/authRedirect.sjs @@ -0,0 +1,21 @@ +// Simple script redirects to the query part of the uri if the browser +// authenticates with username "testuser" password "testpass" + +function handleRequest(request, response) { + if (request.hasHeader("Authorization")) { + if ( + request.getHeader("Authorization") == "Basic dGVzdHVzZXI6dGVzdHBhc3M=" + ) { + response.setStatusLine(request.httpVersion, 302, "Found"); + response.setHeader("Location", request.queryString); + response.write("See " + request.queryString); + } else { + response.setStatusLine(request.httpVersion, 403, "Forbidden"); + response.write("Invalid credentials"); + } + } else { + response.setStatusLine(request.httpVersion, 401, "Authentication required"); + response.setHeader("WWW-Authenticate", 'basic realm="XPInstall"', false); + response.write("Unauthenticated request"); + } +} diff --git a/toolkit/mozapps/extensions/test/xpinstall/browser.toml b/toolkit/mozapps/extensions/test/xpinstall/browser.toml new file mode 100644 index 0000000000..f6ca43982e --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpinstall/browser.toml @@ -0,0 +1,175 @@ +[DEFAULT] +support-files = [ + "amosigned.xpi", + "authRedirect.sjs", + "bug540558.html", + "bug638292.html", + "bug645699.html", + "cookieRedirect.sjs", + "corrupt.xpi", + "empty.xpi", + "enabled.html", + "hashRedirect.sjs", + "head.js", + "incompatible.xpi", + "installchrome.html", + "installtrigger.html", + "installtrigger_frame.html", + "navigate.html", + "recommended.xpi", + "redirect.sjs", + "slowinstall.sjs", + "startsoftwareupdate.html", + "triggerredirect.html", + "unsigned.xpi", + "unsigned_mv3.xpi", + "webmidi_permission.xpi", + "../xpcshell/data/signing_checks/privileged.xpi", +] + +["browser_amosigned_trigger.js"] +https_first_disabled = true # Bug 1737265 + +["browser_amosigned_trigger_iframe.js"] +https_first_disabled = true # Bug 1737265 + +["browser_amosigned_url.js"] +https_first_disabled = true # Bug 1737265 + +["browser_auth.js"] +https_first_disabled = true # Bug 1737265 + +["browser_auth2.js"] + +["browser_auth3.js"] + +["browser_auth4.js"] +https_first_disabled = true # Bug 1737265 + +["browser_badargs.js"] +https_first_disabled = true # Bug 1737265 + +["browser_badargs2.js"] +https_first_disabled = true # Bug 1737265 + +["browser_badhash.js"] + +["browser_badhashtype.js"] + +["browser_block_fullscreen_prompt.js"] +https_first_disabled = true # Bug 1737265 +skip-if = ["os == 'mac' && debug"] #Bug 1590136 + +["browser_bug540558.js"] +https_first_disabled = true # Bug 1737265 + +["browser_bug611242.js"] + +["browser_bug638292.js"] + +["browser_bug645699.js"] + +["browser_bug645699_postDownload.js"] + +["browser_bug672485.js"] +skip-if = ["true"] # disabled due to a leak. See bug 682410. + +["browser_containers.js"] +https_first_disabled = true # Bug 1737265 + +["browser_cookies.js"] + +["browser_cookies2.js"] +https_first_disabled = true # Bug 1737265 + +["browser_cookies3.js"] +https_first_disabled = true # Bug 1737265 + +["browser_cookies4.js"] +skip-if = ["true"] # Bug 1084646 + +["browser_corrupt.js"] +https_first_disabled = true # Bug 1737265 + +["browser_datauri.js"] + +["browser_doorhanger_installs.js"] +https_first_disabled = true # Bug 1737265 +skip-if = [ + "os == 'win' && os_version == '10.0' && bits == 64", #Bug 1615449 +] + +["browser_empty.js"] + +["browser_enabled.js"] + +["browser_hash.js"] +https_first_disabled = true # Bug 1737265 + +["browser_hash2.js"] +https_first_disabled = true # Bug 1737265 + +["browser_httphash.js"] +https_first_disabled = true # Bug 1737265 + +["browser_httphash2.js"] + +["browser_httphash3.js"] +https_first_disabled = true # Bug 1737265 + +["browser_httphash4.js"] +https_first_disabled = true # Bug 1737265 + +["browser_httphash5.js"] +https_first_disabled = true # Bug 1737265 + +["browser_httphash6.js"] +skip-if = ["true"] # Bug 1449788 + +["browser_installchrome.js"] +https_first_disabled = true # Bug 1737265 + +["browser_localfile.js"] + +["browser_localfile2.js"] + +["browser_localfile3.js"] + +["browser_localfile4.js"] + +["browser_localfile4_postDownload.js"] + +["browser_newwindow.js"] +skip-if = ["!debug"] # This is a test for leaks, see comment in the test. + +["browser_offline.js"] + +["browser_privatebrowsing.js"] +https_first_disabled = true # Bug 1737265 +skip-if = ["debug"] # Bug 1541577 - leaks on debug + +["browser_relative.js"] +https_first_disabled = true # Bug 1737265 + +["browser_required_useractivation.js"] + +["browser_softwareupdate.js"] +https_first_disabled = true # Bug 1737265 + +["browser_trigger_redirect.js"] +https_first_disabled = true # Bug 1737265 + +["browser_unsigned_trigger.js"] +https_first_disabled = true # Bug 1737265 +skip-if = ["require_signing"] + +["browser_unsigned_trigger_iframe.js"] +https_first_disabled = true # Bug 1737265 +skip-if = ["require_signing"] + +["browser_unsigned_trigger_xorigin.js"] +https_first_disabled = true # Bug 1737265 + +["browser_unsigned_url.js"] +https_first_disabled = true # Bug 1737265 +skip-if = ["require_signing"] diff --git a/toolkit/mozapps/extensions/test/xpinstall/browser_amosigned_trigger.js b/toolkit/mozapps/extensions/test/xpinstall/browser_amosigned_trigger.js new file mode 100644 index 0000000000..97aa9e1a94 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpinstall/browser_amosigned_trigger.js @@ -0,0 +1,86 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +"use strict"; + +const { AddonTestUtils } = ChromeUtils.importESModule( + "resource://testing-common/AddonTestUtils.sys.mjs" +); + +AddonTestUtils.initMochitest(this); + +// ---------------------------------------------------------------------------- +// Tests installing an unsigned add-on through an InstallTrigger call in web +// content. +function test() { + // This test depends on InstallTrigger.install availability. + setInstallTriggerPrefs(); + + Harness.installConfirmCallback = confirm_install; + Harness.installEndedCallback = install_ended; + Harness.installsCompletedCallback = finish_test; + Harness.finalContentEvent = "InstallComplete"; + Harness.setup(); + + PermissionTestUtils.add( + "http://example.com/", + "install", + Services.perms.ALLOW_ACTION + ); + + var triggers = encodeURIComponent( + JSON.stringify({ + "Unsigned XPI": { + URL: TESTROOT + "amosigned.xpi", + IconURL: TESTROOT + "icon.png", + toString() { + return this.URL; + }, + }, + }) + ); + gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser); + BrowserTestUtils.startLoadingURIString( + gBrowser, + TESTROOT + "installtrigger.html?" + triggers + ); +} + +function confirm_install(panel) { + is(panel.getAttribute("name"), "XPI Test", "Should have seen the name"); + return true; +} + +function install_ended(install, addon) { + AddonTestUtils.checkInstallInfo(install, { + method: "installTrigger", + source: "test-host", + sourceURL: /http:\/\/example.com\/.*\/installtrigger.html/, + }); + + return addon.uninstall(); +} + +const finish_test = async function (count) { + is(count, 1, "1 Add-on should have been successfully installed"); + + PermissionTestUtils.remove("http://example.com", "install"); + + const results = await SpecialPowers.spawn( + gBrowser.selectedBrowser, + [], + () => { + return { + return: content.document.getElementById("return").textContent, + status: content.document.getElementById("status").textContent, + }; + } + ); + + is(results.return, "true", "installTrigger should have claimed success"); + is(results.status, "0", "Callback should have seen a success"); + + gBrowser.removeCurrentTab(); + Harness.finish(); +}; diff --git a/toolkit/mozapps/extensions/test/xpinstall/browser_amosigned_trigger_iframe.js b/toolkit/mozapps/extensions/test/xpinstall/browser_amosigned_trigger_iframe.js new file mode 100644 index 0000000000..f9d91e1cbd --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpinstall/browser_amosigned_trigger_iframe.js @@ -0,0 +1,77 @@ +// ---------------------------------------------------------------------------- +// Test for bug 589598 - Ensure that installing through InstallTrigger +// works in an iframe in web content. + +function test() { + // This test depends on InstallTrigger.install availability. + setInstallTriggerPrefs(); + + Harness.installConfirmCallback = confirm_install; + Harness.installEndedCallback = install_ended; + Harness.installsCompletedCallback = finish_test; + Harness.finalContentEvent = "InstallComplete"; + Harness.setup(); + + PermissionTestUtils.add( + "http://example.com/", + "install", + Services.perms.ALLOW_ACTION + ); + + var inner_url = encodeURIComponent( + TESTROOT + + "installtrigger.html?" + + encodeURIComponent( + JSON.stringify({ + "Unsigned XPI": { + URL: TESTROOT + "amosigned.xpi", + IconURL: TESTROOT + "icon.png", + toString() { + return this.URL; + }, + }, + }) + ) + ); + gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser); + BrowserTestUtils.startLoadingURIString( + gBrowser, + TESTROOT + "installtrigger_frame.html?" + inner_url + ); +} + +function confirm_install(panel) { + is(panel.getAttribute("name"), "XPI Test", "Should have seen the name"); + return true; +} + +function install_ended(install, addon) { + return addon.uninstall(); +} + +const finish_test = async function (count) { + is(count, 1, "1 Add-on should have been successfully installed"); + + PermissionTestUtils.remove("http://example.com", "install"); + + const results = await SpecialPowers.spawn( + gBrowser.selectedBrowser, + [], + () => { + return { + return: content.frames[0].document.getElementById("return").textContent, + status: content.frames[0].document.getElementById("status").textContent, + }; + } + ); + + is( + results.return, + "true", + "installTrigger in iframe should have claimed success" + ); + is(results.status, "0", "Callback in iframe should have seen a success"); + + gBrowser.removeCurrentTab(); + Harness.finish(); +}; diff --git a/toolkit/mozapps/extensions/test/xpinstall/browser_amosigned_url.js b/toolkit/mozapps/extensions/test/xpinstall/browser_amosigned_url.js new file mode 100644 index 0000000000..a6b50c2b27 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpinstall/browser_amosigned_url.js @@ -0,0 +1,63 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +"use strict"; + +const { AddonTestUtils } = ChromeUtils.importESModule( + "resource://testing-common/AddonTestUtils.sys.mjs" +); + +AddonTestUtils.initMochitest(this); + +// ---------------------------------------------------------------------------- +// Tests installing an unsigned add-on by navigating directly to the url +function test() { + waitForExplicitFinish(); + SpecialPowers.pushPrefEnv( + { + set: [ + // Relax the user input requirements while running this test. + ["xpinstall.userActivation.required", false], + ], + }, + runTest + ); +} + +function runTest() { + Harness.installConfirmCallback = confirm_install; + Harness.installEndedCallback = install_ended; + Harness.installsCompletedCallback = finish_test; + Harness.setup(); + + gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser, "about:blank"); + BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser).then(() => { + BrowserTestUtils.startLoadingURIString( + gBrowser, + TESTROOT + "amosigned.xpi" + ); + }); +} + +function confirm_install(panel) { + is(panel.getAttribute("name"), "XPI Test", "Should have seen the name"); + return true; +} + +function install_ended(install, addon) { + AddonTestUtils.checkInstallInfo(install, { + method: "link", + source: "unknown", + sourceURL: undefined, + }); + return addon.uninstall(); +} + +function finish_test(count) { + is(count, 1, "1 Add-on should have been successfully installed"); + + gBrowser.removeCurrentTab(); + Harness.finish(); +} +// ---------------------------------------------------------------------------- diff --git a/toolkit/mozapps/extensions/test/xpinstall/browser_auth.js b/toolkit/mozapps/extensions/test/xpinstall/browser_auth.js new file mode 100644 index 0000000000..2248af4270 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpinstall/browser_auth.js @@ -0,0 +1,68 @@ +// ---------------------------------------------------------------------------- +// Test whether an install succeeds when authentication is required +// This verifies bug 312473 +function test() { + // This test depends on InstallTrigger.install availability. + setInstallTriggerPrefs(); + + // Turn off the authentication dialog blocking for this test. + Services.prefs.setBoolPref( + "network.auth.non-web-content-triggered-resources-http-auth-allow", + true + ); + + Harness.authenticationCallback = get_auth_info; + Harness.downloadFailedCallback = download_failed; + Harness.installEndedCallback = install_ended; + Harness.installsCompletedCallback = finish_test; + Harness.setup(); + + Services.prefs.setIntPref("network.auth.subresource-http-auth-allow", 2); + + PermissionTestUtils.add( + "http://example.com/", + "install", + Services.perms.ALLOW_ACTION + ); + + var triggers = encodeURIComponent( + JSON.stringify({ + "Unsigned XPI": + TESTROOT + "authRedirect.sjs?" + TESTROOT + "amosigned.xpi", + }) + ); + gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser); + BrowserTestUtils.startLoadingURIString( + gBrowser, + TESTROOT + "installtrigger.html?" + triggers + ); +} + +function get_auth_info() { + return ["testuser", "testpass"]; +} + +function download_failed(install) { + ok(false, "Install should not have failed"); +} + +function install_ended(install, addon) { + return addon.uninstall(); +} + +function finish_test(count) { + is(count, 1, "1 Add-on should have been successfully installed"); + var authMgr = Cc["@mozilla.org/network/http-auth-manager;1"].getService( + Ci.nsIHttpAuthManager + ); + authMgr.clearAll(); + + PermissionTestUtils.remove("http://example.com", "install"); + + Services.prefs.clearUserPref( + "network.auth.non-web-content-triggered-resources-http-auth-allow" + ); + + gBrowser.removeCurrentTab(); + Harness.finish(); +} diff --git a/toolkit/mozapps/extensions/test/xpinstall/browser_auth2.js b/toolkit/mozapps/extensions/test/xpinstall/browser_auth2.js new file mode 100644 index 0000000000..b5a5c09749 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpinstall/browser_auth2.js @@ -0,0 +1,73 @@ +// ---------------------------------------------------------------------------- +// Test whether an install fails when authentication is required and bad +// credentials are given +// This verifies bug 312473 +function test() { + // This test depends on InstallTrigger.install availability. + setInstallTriggerPrefs(); + + // Turn off the authentication dialog blocking for this test. + Services.prefs.setBoolPref( + "network.auth.non-web-content-triggered-resources-http-auth-allow", + true + ); + + requestLongerTimeout(2); + Harness.authenticationCallback = get_auth_info; + Harness.downloadFailedCallback = download_failed; + Harness.installEndedCallback = install_ended; + Harness.installsCompletedCallback = finish_test; + Harness.setup(); + + PermissionTestUtils.add( + "http://example.com/", + "install", + Services.perms.ALLOW_ACTION + ); + + var triggers = encodeURIComponent( + JSON.stringify({ + "Unsigned XPI": + TESTROOT + "authRedirect.sjs?" + TESTROOT + "amosigned.xpi", + }) + ); + gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser); + BrowserTestUtils.startLoadingURIString( + gBrowser, + TESTROOT + "installtrigger.html?" + triggers + ); +} + +function get_auth_info() { + return ["baduser", "badpass"]; +} + +function download_failed(install) { + is( + install.error, + AddonManager.ERROR_NETWORK_FAILURE, + "Install should have failed" + ); +} + +function install_ended(install, addon) { + ok(false, "Add-on should not have installed"); + return addon.uninstall(); +} + +function finish_test(count) { + is(count, 0, "No add-ons should have been installed"); + var authMgr = Cc["@mozilla.org/network/http-auth-manager;1"].getService( + Ci.nsIHttpAuthManager + ); + authMgr.clearAll(); + + PermissionTestUtils.remove("http://example.com", "install"); + + Services.prefs.clearUserPref( + "network.auth.non-web-content-triggered-resources-http-auth-allow" + ); + + gBrowser.removeCurrentTab(); + Harness.finish(); +} diff --git a/toolkit/mozapps/extensions/test/xpinstall/browser_auth3.js b/toolkit/mozapps/extensions/test/xpinstall/browser_auth3.js new file mode 100644 index 0000000000..d348be6d30 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpinstall/browser_auth3.js @@ -0,0 +1,72 @@ +// ---------------------------------------------------------------------------- +// Test whether an install fails when authentication is required and it is +// canceled +// This verifies bug 312473 +function test() { + // This test depends on InstallTrigger.install availability. + setInstallTriggerPrefs(); + + // Turn off the authentication dialog blocking for this test. + Services.prefs.setBoolPref( + "network.auth.non-web-content-triggered-resources-http-auth-allow", + true + ); + + Harness.authenticationCallback = get_auth_info; + Harness.downloadFailedCallback = download_failed; + Harness.installEndedCallback = install_ended; + Harness.installsCompletedCallback = finish_test; + Harness.setup(); + + PermissionTestUtils.add( + "http://example.com/", + "install", + Services.perms.ALLOW_ACTION + ); + + var triggers = encodeURIComponent( + JSON.stringify({ + "Unsigned XPI": + TESTROOT + "authRedirect.sjs?" + TESTROOT + "amosigned.xpi", + }) + ); + gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser); + BrowserTestUtils.startLoadingURIString( + gBrowser, + TESTROOT + "installtrigger.html?" + triggers + ); +} + +function get_auth_info() { + return null; +} + +function download_failed(install) { + is( + install.error, + AddonManager.ERROR_NETWORK_FAILURE, + "Install should have failed" + ); +} + +function install_ended(install, addon) { + ok(false, "Add-on should not have installed"); + return addon.uninstall(); +} + +function finish_test(count) { + is(count, 0, "No add-ons should have been installed"); + var authMgr = Cc["@mozilla.org/network/http-auth-manager;1"].getService( + Ci.nsIHttpAuthManager + ); + authMgr.clearAll(); + + PermissionTestUtils.remove("http://example.com", "install"); + + Services.prefs.clearUserPref( + "network.auth.non-web-content-triggered-resources-http-auth-allow" + ); + + gBrowser.removeCurrentTab(); + Harness.finish(); +} diff --git a/toolkit/mozapps/extensions/test/xpinstall/browser_auth4.js b/toolkit/mozapps/extensions/test/xpinstall/browser_auth4.js new file mode 100644 index 0000000000..46ee2b5cb6 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpinstall/browser_auth4.js @@ -0,0 +1,71 @@ +// Test whether a request for auth for an XPI switches to the appropriate tab +var gNewTab; + +function test() { + // This test depends on InstallTrigger.install availability. + setInstallTriggerPrefs(); + + // Turn off the authentication dialog blocking for this test. + Services.prefs.setBoolPref( + "network.auth.non-web-content-triggered-resources-http-auth-allow", + true + ); + + Harness.authenticationCallback = get_auth_info; + Harness.downloadFailedCallback = download_failed; + Harness.installEndedCallback = install_ended; + Harness.installsCompletedCallback = finish_test; + Harness.setup(); + + PermissionTestUtils.add( + "http://example.com/", + "install", + Services.perms.ALLOW_ACTION + ); + + var triggers = encodeURIComponent( + JSON.stringify({ + "Unsigned XPI": + TESTROOT + "authRedirect.sjs?" + TESTROOT + "amosigned.xpi", + }) + ); + gNewTab = BrowserTestUtils.addTab(gBrowser); + BrowserTestUtils.startLoadingURIString( + gBrowser.getBrowserForTab(gNewTab), + TESTROOT + "installtrigger.html?" + triggers + ); +} + +function get_auth_info() { + is( + gBrowser.selectedTab, + gNewTab, + "Should have focused the tab loading the XPI" + ); + return ["testuser", "testpass"]; +} + +function download_failed(install) { + ok(false, "Install should not have failed"); +} + +function install_ended(install, addon) { + return addon.uninstall(); +} + +function finish_test(count) { + is(count, 1, "1 Add-on should have been successfully installed"); + var authMgr = Cc["@mozilla.org/network/http-auth-manager;1"].getService( + Ci.nsIHttpAuthManager + ); + authMgr.clearAll(); + + PermissionTestUtils.remove("http://example.com", "install"); + + Services.prefs.clearUserPref( + "network.auth.non-web-content-triggered-resources-http-auth-allow" + ); + + gBrowser.removeTab(gNewTab); + Harness.finish(); +} diff --git a/toolkit/mozapps/extensions/test/xpinstall/browser_badargs.js b/toolkit/mozapps/extensions/test/xpinstall/browser_badargs.js new file mode 100644 index 0000000000..8f75a7a653 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpinstall/browser_badargs.js @@ -0,0 +1,49 @@ +// ---------------------------------------------------------------------------- +// Test whether passing a simple string to InstallTrigger.install throws an +// exception +function test() { + // This test depends on InstallTrigger.install availability. + setInstallTriggerPrefs(); + + waitForExplicitFinish(); + + var triggers = encodeURIComponent(JSON.stringify(TESTROOT + "amosigned.xpi")); + gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser, TESTROOT); + + ContentTask.spawn(gBrowser.selectedBrowser, null, function () { + return new Promise(resolve => { + addEventListener( + "load", + () => { + content.addEventListener("InstallTriggered", () => { + resolve(content.document.getElementById("return").textContent); + }); + }, + true + ); + }); + }).then(page_loaded); + + // In non-e10s the exception in the content page would trigger a test failure + if (!gMultiProcessBrowser) { + expectUncaughtException(); + } + + BrowserTestUtils.startLoadingURIString( + gBrowser, + TESTROOT + "installtrigger.html?" + triggers + ); +} + +function page_loaded(result) { + is(result, "exception", "installTrigger should have failed"); + + // In non-e10s the exception from the page is thrown after the event so we + // have to spin the event loop to make sure it arrives so expectUncaughtException + // sees it. + executeSoon(() => { + gBrowser.removeCurrentTab(); + finish(); + }); +} +// ---------------------------------------------------------------------------- diff --git a/toolkit/mozapps/extensions/test/xpinstall/browser_badargs2.js b/toolkit/mozapps/extensions/test/xpinstall/browser_badargs2.js new file mode 100644 index 0000000000..8376cc83a4 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpinstall/browser_badargs2.js @@ -0,0 +1,55 @@ +// ---------------------------------------------------------------------------- +// Test whether passing an undefined url InstallTrigger.install throws an +// exception +function test() { + // This test depends on InstallTrigger.install availability. + setInstallTriggerPrefs(); + + waitForExplicitFinish(); + + var triggers = encodeURIComponent( + JSON.stringify({ + "Unsigned XPI": { + URL: undefined, + }, + }) + ); + gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser, TESTROOT); + + ContentTask.spawn(gBrowser.selectedBrowser, null, function () { + return new Promise(resolve => { + addEventListener( + "load", + () => { + content.addEventListener("InstallTriggered", () => { + resolve(content.document.getElementById("return").textContent); + }); + }, + true + ); + }); + }).then(page_loaded); + + // In non-e10s the exception in the content page would trigger a test failure + if (!gMultiProcessBrowser) { + expectUncaughtException(); + } + + BrowserTestUtils.startLoadingURIString( + gBrowser, + TESTROOT + "installtrigger.html?" + triggers + ); +} + +function page_loaded(result) { + is(result, "exception", "installTrigger should have failed"); + + // In non-e10s the exception from the page is thrown after the event so we + // have to spin the event loop to make sure it arrives so expectUncaughtException + // sees it. + executeSoon(() => { + gBrowser.removeCurrentTab(); + finish(); + }); +} +// ---------------------------------------------------------------------------- diff --git a/toolkit/mozapps/extensions/test/xpinstall/browser_badhash.js b/toolkit/mozapps/extensions/test/xpinstall/browser_badhash.js new file mode 100644 index 0000000000..985eae9cfc --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpinstall/browser_badhash.js @@ -0,0 +1,46 @@ +// ---------------------------------------------------------------------------- +// Test whether an install fails when an invalid hash is included +// This verifies bug 302284 +function test() { + // This test depends on InstallTrigger.install availability. + setInstallTriggerPrefs(); + + Harness.downloadFailedCallback = download_failed; + Harness.installsCompletedCallback = finish_test; + Harness.setup(); + + PermissionTestUtils.add( + "http://example.com/", + "install", + Services.perms.ALLOW_ACTION + ); + + var triggers = encodeURIComponent( + JSON.stringify({ + "Unsigned XPI": { + URL: TESTROOT + "amosigned.xpi", + Hash: "sha1:643b08418599ddbd1ea8a511c90696578fb844b9", + toString() { + return this.URL; + }, + }, + }) + ); + gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser); + BrowserTestUtils.startLoadingURIString( + gBrowser, + TESTROOT + "installtrigger.html?" + triggers + ); +} + +function download_failed(install) { + is(install.error, AddonManager.ERROR_INCORRECT_HASH, "Install should fail"); +} + +function finish_test(count) { + is(count, 0, "No add-ons should have been installed"); + PermissionTestUtils.remove("http://example.com/", "install"); + + gBrowser.removeCurrentTab(); + Harness.finish(); +} diff --git a/toolkit/mozapps/extensions/test/xpinstall/browser_badhashtype.js b/toolkit/mozapps/extensions/test/xpinstall/browser_badhashtype.js new file mode 100644 index 0000000000..f6c1b17d1f --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpinstall/browser_badhashtype.js @@ -0,0 +1,46 @@ +// ---------------------------------------------------------------------------- +// Test whether an install fails when an unknown hash type is included +// This verifies bug 302284 +function test() { + // This test depends on InstallTrigger.install availability. + setInstallTriggerPrefs(); + + Harness.downloadFailedCallback = download_failed; + Harness.installsCompletedCallback = finish_test; + Harness.setup(); + + PermissionTestUtils.add( + "http://example.com/", + "install", + Services.perms.ALLOW_ACTION + ); + + var triggers = encodeURIComponent( + JSON.stringify({ + "Unsigned XPI": { + URL: TESTROOT + "amosigned.xpi", + Hash: "foo:3d0dc22e1f394e159b08aaf5f0f97de4d5c65f4f", + toString() { + return this.URL; + }, + }, + }) + ); + gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser); + BrowserTestUtils.startLoadingURIString( + gBrowser, + TESTROOT + "installtrigger.html?" + triggers + ); +} + +function download_failed(install) { + is(install.error, AddonManager.ERROR_INCORRECT_HASH, "Install should fail"); +} + +function finish_test(count) { + is(count, 0, "No add-ons should have been installed"); + PermissionTestUtils.remove("http://example.com/", "install"); + + gBrowser.removeCurrentTab(); + Harness.finish(); +} diff --git a/toolkit/mozapps/extensions/test/xpinstall/browser_block_fullscreen_prompt.js b/toolkit/mozapps/extensions/test/xpinstall/browser_block_fullscreen_prompt.js new file mode 100644 index 0000000000..97f09d9a9c --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpinstall/browser_block_fullscreen_prompt.js @@ -0,0 +1,129 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +// This test tends to trigger a race in the fullscreen time telemetry, +// where the fullscreen enter and fullscreen exit events (which use the +// same histogram ID) overlap. That causes TelemetryStopwatch to log an +// error. +SimpleTest.ignoreAllUncaughtExceptions(true); + +/** + * Spawns content task in browser to enter / leave fullscreen + * @param browser - Browser to use for JS fullscreen requests + * @param {Boolean} fullscreenState - true to enter fullscreen, false to leave + */ +function changeFullscreen(browser, fullscreenState) { + return SpecialPowers.spawn( + browser, + [fullscreenState], + async function (state) { + if (state) { + await content.document.body.requestFullscreen(); + } else { + await content.document.exitFullscreen(); + } + } + ); +} + +function triggerInstall(browser, xpi_url) { + return SpecialPowers.spawn(browser, [xpi_url], async function (xpi_url) { + content.location = xpi_url; + }); +} + +add_setup(async () => { + await SpecialPowers.pushPrefEnv({ + // Relax the user input requirements while running this test. + set: [["xpinstall.userActivation.required", false]], + }); +}); + +// This tests if addon installation is blocked when requested in fullscreen +add_task(async function testFullscreenBlockAddonInstallPrompt() { + // Open example.com + await BrowserTestUtils.openNewForegroundTab(gBrowser, TESTROOT); + + // Enter and wait for fullscreen + await changeFullscreen(gBrowser.selectedBrowser, true); + await TestUtils.waitForCondition( + () => window.fullScreen, + "Waiting for window to enter fullscreen" + ); + + // Trigger addon installation and expect it to be blocked + let addonEventPromise = TestUtils.topicObserved( + "addon-install-fullscreen-blocked" + ); + await triggerInstall(gBrowser.selectedBrowser, "amosigned.xpi"); + await addonEventPromise; + + // Test if addon installation prompt has been blocked + let panelOpened; + try { + panelOpened = await TestUtils.waitForCondition( + () => PopupNotifications.isPanelOpen, + 100, + 10 + ); + } catch (ex) { + panelOpened = false; + } + is(panelOpened, false, "Addon installation prompt not opened"); + + window.fullScreen = false; + await BrowserTestUtils.waitForEvent(window, "fullscreenchange"); + await BrowserTestUtils.removeTab(gBrowser.selectedTab); +}); + +// This tests if the addon install prompt is closed when entering fullscreen +add_task(async function testFullscreenCloseAddonInstallPrompt() { + // Open example.com + await BrowserTestUtils.openNewForegroundTab(gBrowser, "http://example.com"); + + // Trigger addon installation + let addonEventPromise = TestUtils.topicObserved( + "webextension-permission-prompt" + ); + await SpecialPowers.spawn( + gBrowser.selectedBrowser, + [TESTROOT + "amosigned.xpi"], + xpi_url => { + this.content.location = xpi_url; + } + ); + // Wait for addon install event + info("Wait for webextension-permission-prompt"); + await addonEventPromise; + + // Test if addon installation prompt is visible + await TestUtils.waitForCondition( + () => PopupNotifications.isPanelOpen, + "Waiting for addon installation prompt to open" + ); + Assert.ok( + PopupNotifications.getNotification( + "addon-webext-permissions", + gBrowser.selectedBrowser + ) != null, + "Opened notification is webextension permissions prompt" + ); + + // Switch to fullscreen and test for addon installation prompt close + await changeFullscreen(gBrowser.selectedBrowser, true); + await TestUtils.waitForCondition( + () => window.fullScreen, + "Waiting for window to enter fullscreen" + ); + await TestUtils.waitForCondition( + () => !PopupNotifications.isPanelOpen, + "Waiting for addon installation prompt to close" + ); + + window.fullScreen = false; + await BrowserTestUtils.waitForEvent(window, "fullscreenchange"); + await BrowserTestUtils.removeTab(gBrowser.selectedTab); +}); diff --git a/toolkit/mozapps/extensions/test/xpinstall/browser_bug540558.js b/toolkit/mozapps/extensions/test/xpinstall/browser_bug540558.js new file mode 100644 index 0000000000..ece34583a1 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpinstall/browser_bug540558.js @@ -0,0 +1,31 @@ +// ---------------------------------------------------------------------------- +// Tests that calling InstallTrigger.installChrome works +function test() { + // This test depends on InstallTrigger.installChrome availability. + setInstallTriggerPrefs(); + + Harness.installEndedCallback = check_xpi_install; + Harness.installsCompletedCallback = finish_test; + Harness.setup(); + + PermissionTestUtils.add( + "http://example.com/", + "install", + Services.perms.ALLOW_ACTION + ); + + gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser); + BrowserTestUtils.startLoadingURIString(gBrowser, TESTROOT + "bug540558.html"); +} + +function check_xpi_install(install, addon) { + return addon.uninstall(); +} + +function finish_test(count) { + is(count, 1, "1 Add-on should have been successfully installed"); + PermissionTestUtils.remove("http://example.com", "install"); + + gBrowser.removeCurrentTab(); + Harness.finish(); +} diff --git a/toolkit/mozapps/extensions/test/xpinstall/browser_bug611242.js b/toolkit/mozapps/extensions/test/xpinstall/browser_bug611242.js new file mode 100644 index 0000000000..98cf433f6f --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpinstall/browser_bug611242.js @@ -0,0 +1,34 @@ +// ---------------------------------------------------------------------------- +// Test whether setting a new property in InstallTrigger then persists to other +// page loads +add_task(async function test() { + await SpecialPowers.pushPrefEnv({ + set: [ + ["extensions.InstallTrigger.enabled", true], + ["extensions.InstallTriggerImpl.enabled", true], + ], + }); + + await BrowserTestUtils.withNewTab( + { gBrowser, url: TESTROOT + "enabled.html" }, + async function (browser) { + await SpecialPowers.spawn(browser, [], () => { + content.wrappedJSObject.InstallTrigger.enabled.k = function () {}; + }); + + BrowserTestUtils.startLoadingURIString( + browser, + TESTROOT2 + "enabled.html" + ); + await BrowserTestUtils.browserLoaded(browser); + await SpecialPowers.spawn(browser, [], () => { + is( + content.wrappedJSObject.InstallTrigger.enabled.k, + undefined, + "Property should not be defined" + ); + }); + } + ); +}); +// ---------------------------------------------------------------------------- diff --git a/toolkit/mozapps/extensions/test/xpinstall/browser_bug638292.js b/toolkit/mozapps/extensions/test/xpinstall/browser_bug638292.js new file mode 100644 index 0000000000..078d94cb50 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpinstall/browser_bug638292.js @@ -0,0 +1,51 @@ +// ---------------------------------------------------------------------------- +// Test whether an InstallTrigger.enabled is working +add_task(async function () { + await SpecialPowers.pushPrefEnv({ + set: [ + ["extensions.InstallTrigger.enabled", true], + ["extensions.InstallTriggerImpl.enabled", true], + ], + }); + + let testtab = await BrowserTestUtils.openNewForegroundTab( + gBrowser, + TESTROOT + "bug638292.html" + ); + + async function verify(link, button) { + info("Clicking " + link); + + let loadedPromise = BrowserTestUtils.waitForNewTab(gBrowser, null, true); + + await BrowserTestUtils.synthesizeMouseAtCenter( + "#" + link, + { button }, + gBrowser.selectedBrowser + ); + + let newtab = await loadedPromise; + + let result = await SpecialPowers.spawn( + newtab.linkedBrowser, + [], + async function () { + return content.document.getElementById("enabled").textContent == "true"; + } + ); + + ok(result, "installTrigger for " + link + " should have been enabled"); + + // Focus the old tab (link3 is opened in the background) + if (link != "link3") { + await BrowserTestUtils.switchTab(gBrowser, testtab); + } + gBrowser.removeTab(newtab); + } + + await verify("link1", 0); + await verify("link2", 0); + await verify("link3", 1); + + gBrowser.removeCurrentTab(); +}); diff --git a/toolkit/mozapps/extensions/test/xpinstall/browser_bug645699.js b/toolkit/mozapps/extensions/test/xpinstall/browser_bug645699.js new file mode 100644 index 0000000000..690ac2b3eb --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpinstall/browser_bug645699.js @@ -0,0 +1,69 @@ +// ---------------------------------------------------------------------------- +// Tests installing an unsigned add-on through an InstallTrigger call in web +// content. This should be blocked by the whitelist check. +// This verifies bug 645699 +function test() { + if ( + !SpecialPowers.Services.prefs.getBoolPref( + "extensions.InstallTrigger.enabled" + ) || + !SpecialPowers.Services.prefs.getBoolPref( + "extensions.InstallTriggerImpl.enabled" + ) + ) { + ok(true, "InstallTrigger is not enabled"); + return; + } + + // prompt prior to download + SpecialPowers.pushPrefEnv({ + set: [ + ["extensions.postDownloadThirdPartyPrompt", false], + ["extensions.InstallTrigger.requireUserInput", false], + // Relax the user input requirements while running this test. + ["xpinstall.userActivation.required", false], + ], + }); + + Harness.installConfirmCallback = confirm_install; + Harness.installBlockedCallback = allow_blocked; + Harness.installsCompletedCallback = finish_test; + Harness.setup(); + + PermissionTestUtils.add( + "http://example.org/", + "install", + Services.perms.ALLOW_ACTION + ); + + gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser); + BrowserTestUtils.startLoadingURIString(gBrowser, TESTROOT + "bug645699.html"); +} + +function allow_blocked(installInfo) { + is( + installInfo.browser, + gBrowser.selectedBrowser, + "Install should have been triggered by the right browser" + ); + is( + installInfo.originatingURI.spec, + gBrowser.currentURI.spec, + "Install should have been triggered by the right uri" + ); + return false; +} + +function confirm_install(panel) { + ok(false, "Should not see the install dialog"); + return false; +} + +function finish_test(count) { + is(count, 0, "0 Add-ons should have been successfully installed"); + PermissionTestUtils.remove("http://example.org/", "install"); + + gBrowser.removeCurrentTab(); + Harness.finish(); +} +// ---------------------------------------------------------------------------- diff --git a/toolkit/mozapps/extensions/test/xpinstall/browser_bug645699_postDownload.js b/toolkit/mozapps/extensions/test/xpinstall/browser_bug645699_postDownload.js new file mode 100644 index 0000000000..aa8b948c14 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpinstall/browser_bug645699_postDownload.js @@ -0,0 +1,55 @@ +// ---------------------------------------------------------------------------- +// Tests installing an unsigned add-on through an InstallTrigger call in web +// content. This should be blocked by the origin allow check. +// This verifies bug 645699 +function test() { + // This test depends on InstallTrigger.install availability. + setInstallTriggerPrefs(); + + Harness.installConfirmCallback = confirm_install; + Harness.installBlockedCallback = allow_blocked; + Harness.installsCompletedCallback = finish_test; + // Prevent the Harness from ending the test on download cancel. + Harness.downloadCancelledCallback = () => { + return false; + }; + + Harness.setup(); + + PermissionTestUtils.add( + "http://example.org/", + "install", + Services.perms.ALLOW_ACTION + ); + + gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser); + BrowserTestUtils.startLoadingURIString(gBrowser, TESTROOT + "bug645699.html"); +} + +function allow_blocked(installInfo) { + is( + installInfo.browser, + gBrowser.selectedBrowser, + "Install should have been triggered by the right browser" + ); + is( + installInfo.originatingURI.spec, + gBrowser.currentURI.spec, + "Install should have been triggered by the right uri" + ); + return false; +} + +function confirm_install(panel) { + ok(false, "Should not see the install dialog"); + return false; +} + +function finish_test(count) { + is(count, 0, "0 Add-ons should have been successfully installed"); + PermissionTestUtils.remove("http://example.org/", "install"); + + gBrowser.removeCurrentTab(); + Harness.finish(); +} +// ---------------------------------------------------------------------------- diff --git a/toolkit/mozapps/extensions/test/xpinstall/browser_bug672485.js b/toolkit/mozapps/extensions/test/xpinstall/browser_bug672485.js new file mode 100644 index 0000000000..216d543458 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpinstall/browser_bug672485.js @@ -0,0 +1,63 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +var gWindowWatcher = null; + +function test() { + // This test depends on InstallTrigger.install availability. + setInstallTriggerPrefs(); + + Harness.installConfirmCallback = confirm_install; + Harness.installCancelledCallback = cancelled_install; + Harness.installEndedCallback = complete_install; + Harness.installsCompletedCallback = finish_test; + Harness.setup(); + + gWindowWatcher = Services.ww; + delete Services.ww; + is(Services.ww, undefined, "Services.ww should now be undefined"); + + PermissionTestUtils.add( + "http://example.com/", + "install", + Services.perms.ALLOW_ACTION + ); + + var triggers = encodeURIComponent( + JSON.stringify({ + "Unsigned XPI": TESTROOT + "amosigned.xpi", + }) + ); + gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser); + BrowserTestUtils.startLoadingURIString( + gBrowser, + TESTROOT + "installtrigger.html?" + triggers + ); +} + +function confirm_install(panel) { + ok(false, "Should not see the install dialog"); + return false; +} + +function cancelled_install() { + ok(true, "Install should b cancelled"); +} + +function complete_install() { + ok(false, "Install should not have completed"); + return false; +} + +function finish_test(count) { + is(count, 0, "0 Add-ons should have been successfully installed"); + + gBrowser.removeCurrentTab(); + + Services.ww = gWindowWatcher; + + PermissionTestUtils.remove("http://example.com", "install"); + + Harness.finish(); +} diff --git a/toolkit/mozapps/extensions/test/xpinstall/browser_containers.js b/toolkit/mozapps/extensions/test/xpinstall/browser_containers.js new file mode 100644 index 0000000000..486f391c9e --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpinstall/browser_containers.js @@ -0,0 +1,116 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +"use strict"; + +const { AddonTestUtils } = ChromeUtils.importESModule( + "resource://testing-common/AddonTestUtils.sys.mjs" +); + +AddonTestUtils.initMochitest(this); + +const MY_CONTEXT = 2; +let gDidSeeChannel = false; + +function check_channel(subject) { + if (!(subject instanceof Ci.nsIHttpChannel)) { + return; + } + let channel = subject.QueryInterface(Ci.nsIHttpChannel); + let uri = channel.URI; + if (!uri || !uri.spec.endsWith("amosigned.xpi")) { + return; + } + gDidSeeChannel = true; + ok(true, "Got request for " + uri.spec); + + let loadInfo = channel.loadInfo; + is( + loadInfo.originAttributes.userContextId, + MY_CONTEXT, + "Got expected usercontextid" + ); +} +// ---------------------------------------------------------------------------- +// Tests we send the right cookies when installing through an InstallTrigger call +function test() { + // This test depends on InstallTrigger.install availability. + setInstallTriggerPrefs(); + + Harness.installConfirmCallback = confirm_install; + Harness.installEndedCallback = install_ended; + Harness.installsCompletedCallback = finish_test; + Harness.finalContentEvent = "InstallComplete"; + Harness.setup(); + + let principal = Services.scriptSecurityManager.createContentPrincipal( + Services.io.newURI("http://example.com/"), + { userContextId: MY_CONTEXT } + ); + + PermissionTestUtils.add(principal, "install", Services.perms.ALLOW_ACTION); + + var triggers = encodeURIComponent( + JSON.stringify({ + "Unsigned XPI": { + URL: TESTROOT + "amosigned.xpi", + IconURL: TESTROOT + "icon.png", + toString() { + return this.URL; + }, + }, + }) + ); + gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser, "", { + userContextId: MY_CONTEXT, + }); + Services.obs.addObserver(check_channel, "http-on-before-connect"); + BrowserTestUtils.startLoadingURIString( + gBrowser, + TESTROOT + "installtrigger.html?" + triggers + ); +} + +function confirm_install(panel) { + is(panel.getAttribute("name"), "XPI Test", "Should have seen the name"); + return true; +} + +function install_ended(install, addon) { + AddonTestUtils.checkInstallInfo(install, { + method: "installTrigger", + source: "test-host", + sourceURL: /http:\/\/example.com\/.*\/installtrigger.html/, + }); + return addon.uninstall(); +} + +const finish_test = async function (count) { + ok( + gDidSeeChannel, + "Should have seen the request for the XPI and verified it was sent the right way." + ); + is(count, 1, "1 Add-on should have been successfully installed"); + + Services.obs.removeObserver(check_channel, "http-on-before-connect"); + + PermissionTestUtils.remove("http://example.com", "install"); + + const results = await SpecialPowers.spawn( + gBrowser.selectedBrowser, + [], + () => { + return { + return: content.document.getElementById("return").textContent, + status: content.document.getElementById("status").textContent, + }; + } + ); + + is(results.return, "true", "installTrigger should have claimed success"); + is(results.status, "0", "Callback should have seen a success"); + + gBrowser.removeCurrentTab(); + Harness.finish(); +}; diff --git a/toolkit/mozapps/extensions/test/xpinstall/browser_cookies.js b/toolkit/mozapps/extensions/test/xpinstall/browser_cookies.js new file mode 100644 index 0000000000..fc08de89b1 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpinstall/browser_cookies.js @@ -0,0 +1,42 @@ +// ---------------------------------------------------------------------------- +// Test that an install that requires cookies to be sent fails when no cookies +// are set +// This verifies bug 462739 +function test() { + // This test depends on InstallTrigger.install availability. + setInstallTriggerPrefs(); + + Harness.downloadFailedCallback = download_failed; + Harness.installsCompletedCallback = finish_test; + Harness.setup(); + + PermissionTestUtils.add( + "http://example.com/", + "install", + Services.perms.ALLOW_ACTION + ); + + var triggers = encodeURIComponent( + JSON.stringify({ + "Cookie check": + TESTROOT + "cookieRedirect.sjs?" + TESTROOT + "amosigned.xpi", + }) + ); + gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser); + BrowserTestUtils.startLoadingURIString( + gBrowser, + TESTROOT + "installtrigger.html?" + triggers + ); +} + +function download_failed(install) { + is(install.error, AddonManager.ERROR_NETWORK_FAILURE, "Install should fail"); +} + +function finish_test(count) { + is(count, 0, "No add-ons should have been installed"); + PermissionTestUtils.remove("http://example.com", "install"); + + gBrowser.removeCurrentTab(); + Harness.finish(); +} diff --git a/toolkit/mozapps/extensions/test/xpinstall/browser_cookies2.js b/toolkit/mozapps/extensions/test/xpinstall/browser_cookies2.js new file mode 100644 index 0000000000..1465f0f93d --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpinstall/browser_cookies2.js @@ -0,0 +1,64 @@ +// ---------------------------------------------------------------------------- +// Test that an install that requires cookies to be sent succeeds when cookies +// are set +// This verifies bug 462739 +function test() { + // This test depends on InstallTrigger.install availability. + setInstallTriggerPrefs(); + + Harness.installEndedCallback = install_ended; + Harness.installsCompletedCallback = finish_test; + Harness.setup(); + + Services.cookies.add( + "example.com", + "/browser/" + RELATIVE_DIR, + "xpinstall", + "true", + false, + false, + true, + Date.now() / 1000 + 60, + {}, + Ci.nsICookie.SAMESITE_NONE, + Ci.nsICookie.SCHEME_HTTP + ); + + PermissionTestUtils.add( + "http://example.com/", + "install", + Services.perms.ALLOW_ACTION + ); + + var triggers = encodeURIComponent( + JSON.stringify({ + "Cookie check": + TESTROOT + "cookieRedirect.sjs?" + TESTROOT + "amosigned.xpi", + }) + ); + gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser); + BrowserTestUtils.startLoadingURIString( + gBrowser, + TESTROOT + "installtrigger.html?" + triggers + ); +} + +function install_ended(install, addon) { + return addon.uninstall(); +} + +function finish_test(count) { + is(count, 1, "1 Add-on should have been successfully installed"); + + Services.cookies.remove( + "example.com", + "xpinstall", + "/browser/" + RELATIVE_DIR, + {} + ); + + PermissionTestUtils.remove("http://example.com", "install"); + + gBrowser.removeCurrentTab(); + Harness.finish(); +} diff --git a/toolkit/mozapps/extensions/test/xpinstall/browser_cookies3.js b/toolkit/mozapps/extensions/test/xpinstall/browser_cookies3.js new file mode 100644 index 0000000000..03ceead636 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpinstall/browser_cookies3.js @@ -0,0 +1,68 @@ +// ---------------------------------------------------------------------------- +// Test that an install that requires cookies to be sent succeeds when cookies +// are set and third party cookies are disabled. +// This verifies bug 462739 +function test() { + // This test depends on InstallTrigger.install availability. + setInstallTriggerPrefs(); + + Harness.installEndedCallback = install_ended; + Harness.installsCompletedCallback = finish_test; + Harness.setup(); + + Services.cookies.add( + "example.com", + "/browser/" + RELATIVE_DIR, + "xpinstall", + "true", + false, + false, + true, + Date.now() / 1000 + 60, + {}, + Ci.nsICookie.SAMESITE_NONE, + Ci.nsICookie.SCHEME_HTTP + ); + + PermissionTestUtils.add( + "http://example.com/", + "install", + Services.perms.ALLOW_ACTION + ); + + Services.prefs.setIntPref("network.cookie.cookieBehavior", 1); + + var triggers = encodeURIComponent( + JSON.stringify({ + "Cookie check": + TESTROOT + "cookieRedirect.sjs?" + TESTROOT + "amosigned.xpi", + }) + ); + gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser); + BrowserTestUtils.startLoadingURIString( + gBrowser, + TESTROOT + "installtrigger.html?" + triggers + ); +} + +function install_ended(install, addon) { + return addon.uninstall(); +} + +function finish_test(count) { + is(count, 1, "1 Add-on should have been successfully installed"); + + Services.cookies.remove( + "example.com", + "xpinstall", + "/browser/" + RELATIVE_DIR, + {} + ); + + Services.prefs.clearUserPref("network.cookie.cookieBehavior"); + + PermissionTestUtils.remove("http://example.com", "install"); + + gBrowser.removeCurrentTab(); + Harness.finish(); +} diff --git a/toolkit/mozapps/extensions/test/xpinstall/browser_cookies4.js b/toolkit/mozapps/extensions/test/xpinstall/browser_cookies4.js new file mode 100644 index 0000000000..931a9a5ff5 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpinstall/browser_cookies4.js @@ -0,0 +1,68 @@ +// ---------------------------------------------------------------------------- +// Test that an install that requires cookies to be sent fails when cookies +// are set and third party cookies are disabled and the request is to a third +// party. +// This verifies bug 462739 +function test() { + // This test depends on InstallTrigger.install availability. + setInstallTriggerPrefs(); + + Harness.downloadFailedCallback = download_failed; + Harness.installsCompletedCallback = finish_test; + Harness.setup(); + + Services.cookies.add( + "example.org", + "/browser/" + RELATIVE_DIR, + "xpinstall", + "true", + false, + false, + true, + Date.now() / 1000 + 60, + {}, + Ci.nsICookie.SAMESITE_NONE, + Ci.nsICookie.SCHEME_HTTP + ); + + PermissionTestUtils.add( + "http://example.com/", + "install", + Services.perms.ALLOW_ACTION + ); + + Services.prefs.setIntPref("network.cookie.cookieBehavior", 1); + + var triggers = encodeURIComponent( + JSON.stringify({ + "Cookie check": + TESTROOT2 + "cookieRedirect.sjs?" + TESTROOT + "amosigned.xpi", + }) + ); + gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser); + BrowserTestUtils.startLoadingURIString( + gBrowser, + TESTROOT + "installtrigger.html?" + triggers + ); +} + +function download_failed(install) { + is(install.error, AddonManager.ERROR_NETWORK_FAILURE, "Install should fail"); +} + +function finish_test(count) { + is(count, 0, "No add-ons should have been installed"); + + Services.cookies.remove( + "example.org", + "xpinstall", + "/browser/" + RELATIVE_DIR, + {} + ); + + Services.prefs.clearUserPref("network.cookie.cookieBehavior"); + PermissionTestUtils.remove("http://example.com", "install"); + + gBrowser.removeCurrentTab(); + Harness.finish(); +} diff --git a/toolkit/mozapps/extensions/test/xpinstall/browser_corrupt.js b/toolkit/mozapps/extensions/test/xpinstall/browser_corrupt.js new file mode 100644 index 0000000000..11ecc6446d --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpinstall/browser_corrupt.js @@ -0,0 +1,53 @@ +// ---------------------------------------------------------------------------- +// Test whether an install fails when the xpi is corrupt. +function test() { + // This test currently depends on InstallTrigger.install availability. + setInstallTriggerPrefs(); + + Harness.downloadFailedCallback = download_failed; + Harness.installsCompletedCallback = finish_test; + Harness.finalContentEvent = "InstallComplete"; + Harness.setup(); + + PermissionTestUtils.add( + "http://example.com/", + "install", + Services.perms.ALLOW_ACTION + ); + + var triggers = encodeURIComponent( + JSON.stringify({ + "Corrupt XPI": TESTROOT + "corrupt.xpi", + }) + ); + gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser); + BrowserTestUtils.startLoadingURIString( + gBrowser, + TESTROOT + "installtrigger.html?" + triggers + ); +} + +function download_failed(install) { + is(install.error, AddonManager.ERROR_CORRUPT_FILE, "Install should fail"); +} + +const finish_test = async function (count) { + is(count, 0, "No add-ons should have been installed"); + PermissionTestUtils.remove("http://example.com", "install"); + + const results = await SpecialPowers.spawn( + gBrowser.selectedBrowser, + [], + () => { + return { + return: content.document.getElementById("return").textContent, + status: content.document.getElementById("status").textContent, + }; + } + ); + + is(results.status, "-207", "Callback should have seen the failure"); + + gBrowser.removeCurrentTab(); + Harness.finish(); +}; diff --git a/toolkit/mozapps/extensions/test/xpinstall/browser_datauri.js b/toolkit/mozapps/extensions/test/xpinstall/browser_datauri.js new file mode 100644 index 0000000000..08ec7e41cb --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpinstall/browser_datauri.js @@ -0,0 +1,80 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +"use strict"; + +const { AddonTestUtils } = ChromeUtils.importESModule( + "resource://testing-common/AddonTestUtils.sys.mjs" +); + +AddonTestUtils.initMochitest(this); + +// ---------------------------------------------------------------------------- +// Checks that a chained redirect through a data URI and javascript is blocked + +function setup_redirect(aSettings) { + var url = TESTROOT + "redirect.sjs?mode=setup"; + for (var name in aSettings) { + url += "&" + name + "=" + encodeURIComponent(aSettings[name]); + } + + var req = new XMLHttpRequest(); + req.open("GET", url, false); + req.send(null); +} + +function test() { + waitForExplicitFinish(); + SpecialPowers.pushPrefEnv( + { + set: [ + ["network.allow_redirect_to_data", true], + ["security.data_uri.block_toplevel_data_uri_navigations", false], + // Relax the user input requirements while running this test. + ["xpinstall.userActivation.required", false], + ], + }, + runTest + ); +} + +function runTest() { + Harness.installOriginBlockedCallback = install_blocked; + Harness.installsCompletedCallback = finish_test; + Harness.setup(); + + setup_redirect({ + Location: + "data:text/html,<script>window.location.href='" + + TESTROOT + + "amosigned.xpi'</script>", + }); + + BrowserTestUtils.openNewForegroundTab( + gBrowser, + TESTROOT + "redirect.sjs?mode=redirect" + ); +} + +function install_blocked(installInfo) { + is( + installInfo.installs.length, + 1, + "Got one AddonInstall instance as expected" + ); + AddonTestUtils.checkInstallInfo(installInfo.installs[0], { + method: "link", + source: "unknown", + sourceURL: /moz-nullprincipal:\{.*\}/, + }); +} + +function finish_test(count) { + is(count, 0, "No add-ons should have been installed"); + PermissionTestUtils.remove("http://example.com", "install"); + + gBrowser.removeCurrentTab(); + Harness.finish(); + finish(); +} diff --git a/toolkit/mozapps/extensions/test/xpinstall/browser_doorhanger_installs.js b/toolkit/mozapps/extensions/test/xpinstall/browser_doorhanger_installs.js new file mode 100644 index 0000000000..01c8089180 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpinstall/browser_doorhanger_installs.js @@ -0,0 +1,1545 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +// TODO(Bug 1789718): adapt to synthetic addon type implemented by the SitePermAddonProvider +// or remove if redundant, after the deprecated XPIProvider-based implementation is also removed. + +const { AddonTestUtils } = ChromeUtils.importESModule( + "resource://testing-common/AddonTestUtils.sys.mjs" +); +const { ExtensionPermissions } = ChromeUtils.importESModule( + "resource://gre/modules/ExtensionPermissions.sys.mjs" +); +const { Management } = ChromeUtils.importESModule( + "resource://gre/modules/Extension.sys.mjs" +); + +const SECUREROOT = + "https://example.com/browser/toolkit/mozapps/extensions/test/xpinstall/"; +const PROGRESS_NOTIFICATION = "addon-progress"; + +const CHROMEROOT = extractChromeRoot(gTestPath); + +AddonTestUtils.initMochitest(this); + +function waitForTick() { + return new Promise(resolve => executeSoon(resolve)); +} + +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-installed") { + topic = "webextension-install-notify"; + } + return topic; +} + +async function waitForProgressNotification( + aPanelOpen = false, + aExpectedCount = 1, + wantDisabled = true, + expectedAnchorID = "unified-extensions-button", + win = window +) { + 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); + }); + + let panelEventPromise; + if (aPanelOpen) { + panelEventPromise = Promise.resolve(); + } else { + panelEventPromise = new Promise(resolve => { + win.PopupNotifications.panel.addEventListener( + "popupshowing", + function () { + resolve(); + }, + { once: true } + ); + }); + } + + await observerPromise; + await panelEventPromise; + await waitForTick(); + + info("Saw a notification"); + ok(win.PopupNotifications.isPanelOpen, "Panel should be open"); + is( + win.PopupNotifications.panel.childNodes.length, + aExpectedCount, + "Should be the right number of notifications" + ); + if (win.PopupNotifications.panel.childNodes.length) { + let nodes = Array.from(win.PopupNotifications.panel.childNodes); + let notification = nodes.find( + n => n.id == notificationId + "-notification" + ); + ok(notification, `Should have seen the right notification`); + is( + notification.button.hasAttribute("disabled"), + wantDisabled, + "The install button should be disabled?" + ); + + let n = win.PopupNotifications.getNotification(PROGRESS_NOTIFICATION); + is( + n?.anchorElement?.id || n?.anchorElement?.parentElement?.id, + expectedAnchorID, + "expected the right anchor ID" + ); + } + + return win.PopupNotifications.panel; +} + +function acceptAppMenuNotificationWhenShown( + id, + extensionId, + { + dismiss = false, + checkIncognito = false, + incognitoChecked = false, + incognitoHidden = false, + global = window, + } = {} +) { + const { AppMenuNotifications, PanelUI, document } = global; + return new Promise(resolve => { + let permissionChangePromise = null; + function appMenuPopupHidden() { + PanelUI.panel.removeEventListener("popuphidden", appMenuPopupHidden); + ok( + !PanelUI.menuButton.hasAttribute("badge-status"), + "badge is not set after addon-installed" + ); + resolve(permissionChangePromise); + } + function appMenuPopupShown() { + PanelUI.panel.removeEventListener("popupshown", appMenuPopupShown); + PanelUI.menuButton.click(); + } + function popupshown() { + let notification = AppMenuNotifications.activeNotification; + if (!notification) { + return; + } + + is(notification.id, id, `${id} notification shown`); + ok(PanelUI.isNotificationPanelOpen, "notification panel open"); + + PanelUI.notificationPanel.removeEventListener("popupshown", popupshown); + + let checkbox = document.getElementById("addon-incognito-checkbox"); + is(checkbox.hidden, incognitoHidden, "checkbox visibility is correct"); + is(checkbox.checked, incognitoChecked, "checkbox is marked as expected"); + + // If we're unchecking or checking the incognito property, this will + // trigger an update in ExtensionPermission, let's wait for it before + // returning from this promise. + if (incognitoChecked != checkIncognito) { + permissionChangePromise = new Promise(resolve => { + const listener = (type, change) => { + if (extensionId == change.extensionId) { + // Let's make sure we received the right message + let { permissions } = checkIncognito + ? change.added + : change.removed; + ok(permissions.includes("internal:privateBrowsingAllowed")); + resolve(); + } + }; + Management.once("change-permissions", listener); + }); + } + + checkbox.checked = checkIncognito; + + if (dismiss) { + // Dismiss the panel by clicking on the appMenu button. + PanelUI.panel.addEventListener("popupshown", appMenuPopupShown); + PanelUI.panel.addEventListener("popuphidden", appMenuPopupHidden); + PanelUI.menuButton.click(); + return; + } + + // Dismiss the panel by clicking the primary button. + let popupnotificationID = PanelUI._getPopupId(notification); + let popupnotification = document.getElementById(popupnotificationID); + + popupnotification.button.click(); + resolve(permissionChangePromise); + } + PanelUI.notificationPanel.addEventListener("popupshown", popupshown); + }); +} + +async function waitForNotification( + aId, + aExpectedCount = 1, + expectedAnchorID = "unified-extensions-button", + win = window +) { + info("Waiting for " + aId + " notification"); + + let topic = getObserverTopic(aId); + + let observerPromise; + if (aId !== "addon-webext-permissions") { + 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); + }); + } + + let panelEventPromise = new Promise(resolve => { + win.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.includes(aId)) { + return; + } + win.PopupNotifications.panel.removeEventListener( + "PanelUpdated", + eventListener + ); + resolve(); + } + ); + }); + + await observerPromise; + await panelEventPromise; + await waitForTick(); + + info("Saw a " + aId + " notification"); + ok(win.PopupNotifications.isPanelOpen, "Panel should be open"); + is( + win.PopupNotifications.panel.childNodes.length, + aExpectedCount, + "Should be the right number of notifications" + ); + if (win.PopupNotifications.panel.childNodes.length) { + let nodes = Array.from(win.PopupNotifications.panel.childNodes); + let notification = nodes.find(n => n.id == aId + "-notification"); + ok(notification, "Should have seen the " + aId + " notification"); + + let n = win.PopupNotifications.getNotification(aId); + is( + n?.anchorElement?.id || n?.anchorElement?.parentElement?.id, + expectedAnchorID, + "expected the right anchor ID" + ); + } + await SimpleTest.promiseFocus(win.PopupNotifications.window); + + return win.PopupNotifications.panel; +} + +function waitForNotificationClose(win = window) { + if (!win.PopupNotifications.isPanelOpen) { + return Promise.resolve(); + } + return new Promise(resolve => { + info("Waiting for notification to close"); + win.PopupNotifications.panel.addEventListener( + "popuphidden", + function () { + resolve(); + }, + { once: true } + ); + }); +} + +async function waitForInstallDialog(id = "addon-webext-permissions") { + let panel = await waitForNotification(id); + return panel.childNodes[0]; +} + +function removeTabAndWaitForNotificationClose() { + let closePromise = waitForNotificationClose(); + BrowserTestUtils.removeTab(gBrowser.selectedTab); + return closePromise; +} + +function acceptInstallDialog(installDialog) { + installDialog.button.click(); +} + +async function waitForSingleNotification(aCallback) { + while (PopupNotifications.panel.childNodes.length != 1) { + await 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); +} + +var TESTS = [ + async function test_disabledInstall() { + await SpecialPowers.pushPrefEnv({ + set: [["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 = await 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.", + "notification label is correct" + ); + + let closePromise = waitForNotificationClose(); + // Click on Enable + EventUtils.synthesizeMouseAtCenter(notification.button, {}); + await closePromise; + + try { + ok( + Services.prefs.getBoolPref("xpinstall.enabled"), + "Installation should be enabled" + ); + } catch (e) { + ok(false, "xpinstall.enabled should be set"); + } + + BrowserTestUtils.removeTab(gBrowser.selectedTab); + let installs = await AddonManager.getAllInstalls(); + is(installs.length, 0, "Shouldn't be any pending installs"); + await SpecialPowers.popPrefEnv(); + }, + + async function test_blockedInstall() { + await SpecialPowers.pushPrefEnv({ + set: [["extensions.postDownloadThirdPartyPrompt", false]], + }); + + let notificationPromise = waitForNotification("addon-install-blocked"); + let triggers = encodeURIComponent( + JSON.stringify({ + XPI: "amosigned.xpi", + }) + ); + BrowserTestUtils.openNewForegroundTab( + gBrowser, + TESTROOT + "installtrigger.html?" + triggers + ); + let panel = await notificationPromise; + + let notification = panel.childNodes[0]; + is( + notification.button.label, + "Continue to Installation", + "Should have seen the right button" + ); + is( + notification + .querySelector("#addon-install-blocked-info") + .getAttribute("href"), + Services.urlFormatter.formatURLPref("app.support.baseURL") + + "unlisted-extensions-risks", + "Got the expected SUMO page as a learn more link in the addon-install-blocked panel" + ); + let message = panel.ownerDocument.getElementById( + "addon-install-blocked-message" + ); + is( + message.textContent, + "You are attempting to install an add-on from example.com. Make sure you trust this site before continuing.", + "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" + ); + + let installDialog = await dialogPromise; + + notificationPromise = acceptAppMenuNotificationWhenShown( + "addon-installed", + "amosigned-xpi@tests.mozilla.org" + ); + + installDialog.button.click(); + await notificationPromise; + + let installs = await AddonManager.getAllInstalls(); + is(installs.length, 0, "Should be no pending installs"); + + let addon = await AddonManager.getAddonByID( + "amosigned-xpi@tests.mozilla.org" + ); + await addon.uninstall(); + + await BrowserTestUtils.removeTab(gBrowser.selectedTab); + await SpecialPowers.popPrefEnv(); + }, + + async function test_blockedInstallDomain() { + await SpecialPowers.pushPrefEnv({ + set: [ + ["extensions.postDownloadThirdPartyPrompt", true], + ["extensions.install_origins.enabled", true], + ], + }); + + let progressPromise = waitForProgressNotification(); + let notificationPromise = waitForNotification("addon-install-failed"); + let triggers = encodeURIComponent( + JSON.stringify({ + XPI: TESTROOT2 + "webmidi_permission.xpi", + }) + ); + BrowserTestUtils.openNewForegroundTab( + gBrowser, + TESTROOT + "installtrigger.html?" + triggers + ); + await progressPromise; + let panel = await notificationPromise; + + let notification = panel.childNodes[0]; + is( + notification.getAttribute("label"), + "The add-on WebMIDI test addon can not be installed from this location.", + "Should have seen the right message" + ); + + await removeTabAndWaitForNotificationClose(); + await SpecialPowers.popPrefEnv(); + }, + + async function test_allowedInstallDomain() { + await SpecialPowers.pushPrefEnv({ + set: [ + ["extensions.postDownloadThirdPartyPrompt", true], + ["extensions.install_origins.enabled", true], + ], + }); + + let notificationPromise = waitForNotification("addon-install-blocked"); + let triggers = encodeURIComponent( + JSON.stringify({ + XPI: TESTROOT + "webmidi_permission.xpi", + }) + ); + BrowserTestUtils.openNewForegroundTab( + gBrowser, + TESTROOT + "installtrigger.html?" + triggers + ); + let panel = await notificationPromise; + + let notification = panel.childNodes[0]; + is( + notification.button.label, + "Continue to Installation", + "Should have seen the right button" + ); + let message = panel.ownerDocument.getElementById( + "addon-install-blocked-message" + ); + is( + message.textContent, + "You are attempting to install an add-on from example.com. Make sure you trust this site before continuing.", + "Should have seen the right message" + ); + + // Next we get the permissions prompt, which also warns of the unsigned state of the addon + notificationPromise = waitForNotification("addon-webext-permissions"); + // Click on Allow on the 3rd party panel + notification.button.click(); + panel = await notificationPromise; + notification = panel.childNodes[0]; + + is(notification.button.label, "Add", "Should have seen the right button"); + + is( + notification.id, + "addon-webext-permissions-notification", + "Should have seen the permissions panel" + ); + let singlePerm = panel.ownerDocument.getElementById( + "addon-webext-perm-single-entry" + ); + is( + singlePerm.textContent, + "Access MIDI devices", + "Should have seen the right permission text" + ); + + notificationPromise = acceptAppMenuNotificationWhenShown( + "addon-installed", + "webmidi@test.mozilla.org", + { incognitoHidden: false, checkIncognito: true } + ); + + // Click on Allow on the permissions panel + notification.button.click(); + + await notificationPromise; + + let installs = await AddonManager.getAllInstalls(); + is(installs.length, 0, "Should be no pending installs"); + + let addon = await AddonManager.getAddonByID("webmidi@test.mozilla.org"); + await TestUtils.topicObserved("webextension-sitepermissions-startup"); + + // This addon should have a site permission with private browsing. + let uri = Services.io.newURI(addon.siteOrigin); + let pbPrincipal = Services.scriptSecurityManager.createContentPrincipal( + uri, + { + privateBrowsingId: 1, + } + ); + let permission = Services.perms.testExactPermissionFromPrincipal( + pbPrincipal, + "midi" + ); + is( + permission, + Services.perms.ALLOW_ACTION, + "api access in private browsing granted" + ); + + await addon.uninstall(); + + // Verify the permission has not been retained. + let { permissions } = await ExtensionPermissions.get( + "webmidi@test.mozilla.org" + ); + ok( + !permissions.includes("internal:privateBrowsingAllowed"), + "permission is not set after uninstall" + ); + + await BrowserTestUtils.removeTab(gBrowser.selectedTab); + await SpecialPowers.popPrefEnv(); + }, + + async function test_blockedPostDownload() { + await SpecialPowers.pushPrefEnv({ + set: [["extensions.postDownloadThirdPartyPrompt", true]], + }); + + let notificationPromise = waitForNotification("addon-install-blocked"); + let triggers = encodeURIComponent( + JSON.stringify({ + XPI: "amosigned.xpi", + }) + ); + BrowserTestUtils.openNewForegroundTab( + gBrowser, + TESTROOT + "installtrigger.html?" + triggers + ); + let panel = await notificationPromise; + + let notification = panel.childNodes[0]; + is( + notification.button.label, + "Continue to Installation", + "Should have seen the right button" + ); + let message = panel.ownerDocument.getElementById( + "addon-install-blocked-message" + ); + is( + message.textContent, + "You are attempting to install an add-on from example.com. Make sure you trust this site before continuing.", + "Should have seen the right message" + ); + + let dialogPromise = waitForInstallDialog(); + // Click on Allow + EventUtils.synthesizeMouse(notification.button, 20, 10, {}); + + let installDialog = await dialogPromise; + + notificationPromise = acceptAppMenuNotificationWhenShown( + "addon-installed", + "amosigned-xpi@tests.mozilla.org" + ); + + installDialog.button.click(); + await notificationPromise; + + let installs = await AddonManager.getAllInstalls(); + is(installs.length, 0, "Should be no pending installs"); + + let addon = await AddonManager.getAddonByID( + "amosigned-xpi@tests.mozilla.org" + ); + await addon.uninstall(); + + await BrowserTestUtils.removeTab(gBrowser.selectedTab); + await SpecialPowers.popPrefEnv(); + }, + + async function test_recommendedPostDownload() { + await SpecialPowers.pushPrefEnv({ + set: [["extensions.postDownloadThirdPartyPrompt", true]], + }); + + let triggers = encodeURIComponent( + JSON.stringify({ + XPI: "recommended.xpi", + }) + ); + BrowserTestUtils.openNewForegroundTab( + gBrowser, + TESTROOT + "installtrigger.html?" + triggers + ); + + let installDialog = await waitForInstallDialog(); + + let notificationPromise = acceptAppMenuNotificationWhenShown( + "addon-installed", + "{811d77f1-f306-4187-9251-b4ff99bad60b}" + ); + + installDialog.button.click(); + await notificationPromise; + + let installs = await AddonManager.getAllInstalls(); + is(installs.length, 0, "Should be no pending installs"); + + let addon = await AddonManager.getAddonByID( + "{811d77f1-f306-4187-9251-b4ff99bad60b}" + ); + await addon.uninstall(); + + await BrowserTestUtils.removeTab(gBrowser.selectedTab); + await SpecialPowers.popPrefEnv(); + }, + + async function test_priviledgedNo3rdPartyPrompt() { + await SpecialPowers.pushPrefEnv({ + set: [["extensions.postDownloadThirdPartyPrompt", true]], + }); + AddonManager.checkUpdateSecurity = false; + registerCleanupFunction(() => { + AddonManager.checkUpdateSecurity = true; + }); + + let triggers = encodeURIComponent( + JSON.stringify({ + XPI: "privileged.xpi", + }) + ); + + let installDialogPromise = waitForInstallDialog(); + + let tab = await BrowserTestUtils.openNewForegroundTab( + gBrowser, + TESTROOT + "installtrigger.html?" + triggers + ); + + let notificationPromise = acceptAppMenuNotificationWhenShown( + "addon-installed", + "test@tests.mozilla.org", + { incognitoHidden: true } + ); + + (await installDialogPromise).button.click(); + await notificationPromise; + + let installs = await AddonManager.getAllInstalls(); + is(installs.length, 0, "Should be no pending installs"); + + let addon = await AddonManager.getAddonByID("test@tests.mozilla.org"); + await addon.uninstall(); + + await BrowserTestUtils.removeTab(tab); + await SpecialPowers.popPrefEnv(); + AddonManager.checkUpdateSecurity = true; + }, + + async function test_permaBlockInstall() { + let notificationPromise = waitForNotification("addon-install-blocked"); + let triggers = encodeURIComponent( + JSON.stringify({ + XPI: "amosigned.xpi", + }) + ); + let target = TESTROOT + "installtrigger.html?" + triggers; + + BrowserTestUtils.openNewForegroundTab(gBrowser, target); + let notification = (await notificationPromise).firstElementChild; + let neverAllowBtn = notification.menupopup.firstElementChild; + + neverAllowBtn.click(); + + await TestUtils.waitForCondition( + () => !PopupNotifications.isPanelOpen, + "Waiting for notification to close" + ); + + let installs = await AddonManager.getAllInstalls(); + is(installs.length, 0, "Should be no pending installs"); + + let installPerm = PermissionTestUtils.testPermission( + gBrowser.currentURI, + "install" + ); + is( + installPerm, + Ci.nsIPermissionManager.DENY_ACTION, + "Addon installation should be blocked for site" + ); + + await BrowserTestUtils.removeTab(gBrowser.selectedTab); + + PermissionTestUtils.remove(target, "install"); + }, + + async function test_permaBlockedInstallNoPrompt() { + let triggers = encodeURIComponent( + JSON.stringify({ + XPI: "amosigned.xpi", + }) + ); + let target = TESTROOT + "installtrigger.html?" + triggers; + + PermissionTestUtils.add(target, "install", Services.perms.DENY_ACTION); + await BrowserTestUtils.openNewForegroundTab(gBrowser, target); + + let panelOpened; + try { + panelOpened = await TestUtils.waitForCondition( + () => PopupNotifications.isPanelOpen, + 100, + 10 + ); + } catch (ex) { + panelOpened = false; + } + is(panelOpened, false, "Addon prompt should not open"); + + let installs = await AddonManager.getAllInstalls(); + is(installs.length, 0, "Should be no pending installs"); + + await BrowserTestUtils.removeTab(gBrowser.selectedTab); + + PermissionTestUtils.remove(target, "install"); + }, + + async function test_whitelistedInstall() { + let originalTab = gBrowser.selectedTab; + let tab; + gBrowser.selectedTab = originalTab; + PermissionTestUtils.add( + "http://example.com/", + "install", + Services.perms.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)); + await progressPromise; + let installDialog = await dialogPromise; + await BrowserTestUtils.waitForCondition( + () => !!tab, + "tab should be present" + ); + + is( + gBrowser.selectedTab, + tab, + "tab selected in response to the addon-install-confirmation notification" + ); + + let notificationPromise = acceptAppMenuNotificationWhenShown( + "addon-installed", + "amosigned-xpi@tests.mozilla.org", + { dismiss: true } + ); + acceptInstallDialog(installDialog); + await notificationPromise; + + let installs = await AddonManager.getAllInstalls(); + is(installs.length, 0, "Should be no pending installs"); + + let addon = await AddonManager.getAddonByID( + "amosigned-xpi@tests.mozilla.org" + ); + + // Test that the addon does not have permission. Reload it to ensure it would + // have been set if possible. + await addon.reload(); + let policy = WebExtensionPolicy.getByID(addon.id); + ok( + !policy.privateBrowsingAllowed, + "private browsing permission was not granted" + ); + + await addon.uninstall(); + + PermissionTestUtils.remove("http://example.com/", "install"); + + await removeTabAndWaitForNotificationClose(); + }, + + async function test_failedDownload() { + PermissionTestUtils.add( + "http://example.com/", + "install", + Services.perms.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 + ); + await progressPromise; + let panel = await 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" + ); + + PermissionTestUtils.remove("http://example.com/", "install"); + await removeTabAndWaitForNotificationClose(); + }, + + async function test_corruptFile() { + PermissionTestUtils.add( + "http://example.com/", + "install", + Services.perms.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 + ); + await progressPromise; + let panel = await 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" + ); + + PermissionTestUtils.remove("http://example.com/", "install"); + await removeTabAndWaitForNotificationClose(); + }, + + async function test_incompatible() { + PermissionTestUtils.add( + "http://example.com/", + "install", + Services.perms.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 + ); + await progressPromise; + let panel = await failPromise; + + let notification = panel.childNodes[0]; + let brandBundle = Services.strings.createBundle( + "chrome://branding/locale/brand.properties" + ); + let brandShortName = brandBundle.GetStringFromName("brandShortName"); + let message = `XPI Test could not be installed because it is not compatible with ${brandShortName} ${Services.appinfo.version}.`; + is( + notification.getAttribute("label"), + message, + "Should have seen the right message" + ); + + PermissionTestUtils.remove("http://example.com/", "install"); + await removeTabAndWaitForNotificationClose(); + }, + + async function test_localFile() { + let cr = Cc["@mozilla.org/chrome/chrome-registry;1"].getService( + Ci.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"); + }); + gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser, "about:blank"); + await BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser); + BrowserTestUtils.startLoadingURIString(gBrowser, path); + await failPromise; + + // Wait for the browser code to add the failure notification + await 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" + ); + + await removeTabAndWaitForNotificationClose(); + }, + + async function test_urlBar() { + let progressPromise = waitForProgressNotification(); + let dialogPromise = waitForInstallDialog(); + + gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser, "about:blank"); + await BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser); + gURLBar.value = TESTROOT + "amosigned.xpi"; + gURLBar.focus(); + EventUtils.synthesizeKey("KEY_Enter"); + + await progressPromise; + let installDialog = await dialogPromise; + + let notificationPromise = acceptAppMenuNotificationWhenShown( + "addon-installed", + "amosigned-xpi@tests.mozilla.org", + { checkIncognito: true } + ); + installDialog.button.click(); + await notificationPromise; + + let installs = await AddonManager.getAllInstalls(); + is(installs.length, 0, "Should be no pending installs"); + + let addon = await AddonManager.getAddonByID( + "amosigned-xpi@tests.mozilla.org" + ); + // The panel is reloading the addon due to the permission change, we need some way + // to wait for the reload to finish. addon.startupPromise doesn't do it for + // us, so we'll just restart again. + await addon.reload(); + + // This addon should have private browsing permission. + let policy = WebExtensionPolicy.getByID(addon.id); + ok(policy.privateBrowsingAllowed, "private browsing permission granted"); + + await addon.uninstall(); + + await removeTabAndWaitForNotificationClose(); + }, + + async function test_wrongHost() { + let requestedUrl = TESTROOT2 + "enabled.html"; + gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser); + + let loadedPromise = BrowserTestUtils.browserLoaded( + gBrowser.selectedBrowser, + false, + requestedUrl + ); + BrowserTestUtils.startLoadingURIString( + gBrowser, + TESTROOT2 + "enabled.html" + ); + await loadedPromise; + + let progressPromise = waitForProgressNotification(); + let notificationPromise = waitForNotification("addon-install-failed"); + BrowserTestUtils.startLoadingURIString(gBrowser, TESTROOT + "corrupt.xpi"); + await progressPromise; + let panel = await 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" + ); + + await removeTabAndWaitForNotificationClose(); + }, + + async function test_renotifyBlocked() { + let notificationPromise = waitForNotification("addon-install-blocked"); + let triggers = encodeURIComponent( + JSON.stringify({ + XPI: "amosigned.xpi", + }) + ); + BrowserTestUtils.openNewForegroundTab( + gBrowser, + TESTROOT + "installtrigger.html?" + triggers + ); + let panel = await notificationPromise; + + let closePromise = waitForNotificationClose(); + // hide the panel (this simulates the user dismissing it) + panel.hidePopup(); + await closePromise; + + info("Timeouts after this probably mean bug 589954 regressed"); + + await new Promise(resolve => executeSoon(resolve)); + + notificationPromise = waitForNotification("addon-install-blocked"); + BrowserTestUtils.startLoadingURIString( + gBrowser, + TESTROOT + "installtrigger.html?" + triggers + ); + await notificationPromise; + + let installs = await AddonManager.getAllInstalls(); + is(installs.length, 2, "Should be two pending installs"); + + await removeTabAndWaitForNotificationClose(gBrowser.selectedTab); + + installs = await AddonManager.getAllInstalls(); + is(installs.length, 0, "Should have cancelled the installs"); + }, + + async function test_cancel() { + PermissionTestUtils.add( + "http://example.com/", + "install", + Services.perms.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 = await notificationPromise; + let notification = panel.childNodes[0]; + + ok(PopupNotifications.isPanelOpen, "Notification should still be open"); + is( + PopupNotifications.panel.childNodes.length, + 1, + "Should be only one notification" + ); + is( + notification.id, + "addon-progress-notification", + "Should have seen the progress notification" + ); + + // Cancel the download + let install = notification.notification.options.installs[0]; + let cancelledPromise = new Promise(resolve => { + install.addListener({ + onDownloadCancelled() { + install.removeListener(this); + resolve(); + }, + }); + }); + EventUtils.synthesizeMouseAtCenter(notification.secondaryButton, {}); + await cancelledPromise; + + await waitForTick(); + + ok(!PopupNotifications.isPanelOpen, "Notification should be closed"); + + let installs = await AddonManager.getAllInstalls(); + is(installs.length, 0, "Should be no pending install"); + + PermissionTestUtils.remove("http://example.com/", "install"); + BrowserTestUtils.removeTab(gBrowser.selectedTab); + }, + + async function test_failedSecurity() { + await SpecialPowers.pushPrefEnv({ + set: [ + [PREF_INSTALL_REQUIREBUILTINCERTS, false], + ["extensions.postDownloadThirdPartyPrompt", 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 = await 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 + await new Promise(resolve => { + Services.obs.addObserver(function observer() { + Services.obs.removeObserver(observer, "addon-install-failed"); + resolve(); + }, "addon-install-failed"); + }); + + // Allow the browser code to add the failure notification and then wait + // for the progress notification to dismiss itself + await 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" + ); + + await removeTabAndWaitForNotificationClose(); + await SpecialPowers.popPrefEnv(); + }, + + async function test_incognito_checkbox() { + // Grant permission up front. + const permissionName = "internal:privateBrowsingAllowed"; + let incognitoPermission = { + permissions: [permissionName], + origins: [], + }; + await ExtensionPermissions.add( + "amosigned-xpi@tests.mozilla.org", + incognitoPermission + ); + + let progressPromise = waitForProgressNotification(); + let dialogPromise = waitForInstallDialog(); + + gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser, "about:blank"); + await BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser); + gURLBar.value = TESTROOT + "amosigned.xpi"; + gURLBar.focus(); + EventUtils.synthesizeKey("KEY_Enter"); + + await progressPromise; + let installDialog = await dialogPromise; + + let notificationPromise = acceptAppMenuNotificationWhenShown( + "addon-installed", + "amosigned-xpi@tests.mozilla.org", + { incognitoChecked: true } + ); + installDialog.button.click(); + await notificationPromise; + + let installs = await AddonManager.getAllInstalls(); + is(installs.length, 0, "Should be no pending installs"); + + let addon = await AddonManager.getAddonByID( + "amosigned-xpi@tests.mozilla.org" + ); + // The panel is reloading the addon due to the permission change, we need some way + // to wait for the reload to finish. addon.startupPromise doesn't do it for + // us, so we'll just restart again. + await AddonTestUtils.promiseWebExtensionStartup( + "amosigned-xpi@tests.mozilla.org" + ); + + // This addon should no longer have private browsing permission. + let policy = WebExtensionPolicy.getByID(addon.id); + ok(!policy.privateBrowsingAllowed, "private browsing permission removed"); + + await addon.uninstall(); + + await removeTabAndWaitForNotificationClose(); + }, + + async function test_incognito_checkbox_new_window() { + let win = await BrowserTestUtils.openNewBrowserWindow(); + await SimpleTest.promiseFocus(win); + // Grant permission up front. + const permissionName = "internal:privateBrowsingAllowed"; + let incognitoPermission = { + permissions: [permissionName], + origins: [], + }; + await ExtensionPermissions.add( + "amosigned-xpi@tests.mozilla.org", + incognitoPermission + ); + + let panelEventPromise = new Promise(resolve => { + win.PopupNotifications.panel.addEventListener( + "PanelUpdated", + function eventListener(e) { + if (e.detail.includes("addon-webext-permissions")) { + win.PopupNotifications.panel.removeEventListener( + "PanelUpdated", + eventListener + ); + resolve(); + } + } + ); + }); + + win.gBrowser.selectedTab = BrowserTestUtils.addTab( + win.gBrowser, + "about:blank" + ); + await BrowserTestUtils.browserLoaded(win.gBrowser.selectedBrowser); + win.gURLBar.value = TESTROOT + "amosigned.xpi"; + win.gURLBar.focus(); + EventUtils.synthesizeKey("KEY_Enter", {}, win); + + await panelEventPromise; + await waitForTick(); + + let panel = win.PopupNotifications.panel; + let installDialog = panel.childNodes[0]; + + let notificationPromise = acceptAppMenuNotificationWhenShown( + "addon-installed", + "amosigned-xpi@tests.mozilla.org", + { incognitoChecked: true, global: win } + ); + acceptInstallDialog(installDialog); + await notificationPromise; + + let installs = await AddonManager.getAllInstalls(); + is(installs.length, 0, "Should be no pending installs"); + + let addon = await AddonManager.getAddonByID( + "amosigned-xpi@tests.mozilla.org" + ); + // The panel is reloading the addon due to the permission change, we need some way + // to wait for the reload to finish. addon.startupPromise doesn't do it for + // us, so we'll just restart again. + await AddonTestUtils.promiseWebExtensionStartup( + "amosigned-xpi@tests.mozilla.org" + ); + + // This addon should no longer have private browsing permission. + let policy = WebExtensionPolicy.getByID(addon.id); + ok(!policy.privateBrowsingAllowed, "private browsing permission removed"); + + await addon.uninstall(); + + await BrowserTestUtils.closeWindow(win); + }, + + async function test_blockedInstallDomain_with_unified_extensions() { + await SpecialPowers.pushPrefEnv({ + set: [["extensions.install_origins.enabled", true]], + }); + + let win = await BrowserTestUtils.openNewBrowserWindow(); + await SimpleTest.promiseFocus(win); + + let progressPromise = waitForProgressNotification( + false, + 1, + true, + "unified-extensions-button", + win + ); + let notificationPromise = waitForNotification( + "addon-install-failed", + 1, + "unified-extensions-button", + win + ); + let triggers = encodeURIComponent( + JSON.stringify({ + XPI: TESTROOT2 + "webmidi_permission.xpi", + }) + ); + await BrowserTestUtils.openNewForegroundTab( + win.gBrowser, + TESTROOT + "installtrigger.html?" + triggers + ); + await progressPromise; + await notificationPromise; + + await BrowserTestUtils.closeWindow(win); + await SpecialPowers.popPrefEnv(); + }, + + async function test_mv3_installOrigins_disallowed_with_unified_extensions() { + await SpecialPowers.pushPrefEnv({ + set: [ + // Disable signature check because we load an unsigned MV3 extension. + ["xpinstall.signatures.required", false], + ["extensions.install_origins.enabled", true], + ], + }); + + let win = await BrowserTestUtils.openNewBrowserWindow(); + await SimpleTest.promiseFocus(win); + + let notificationPromise = waitForNotification( + "addon-install-failed", + 1, + "unified-extensions-button", + win + ); + let triggers = encodeURIComponent( + JSON.stringify({ + // This XPI does not have any `install_origins` in its manifest. + XPI: "unsigned_mv3.xpi", + }) + ); + await BrowserTestUtils.openNewForegroundTab( + win.gBrowser, + TESTROOT + "installtrigger.html?" + triggers + ); + await notificationPromise; + + await BrowserTestUtils.closeWindow(win); + await SpecialPowers.popPrefEnv(); + }, + + async function test_mv3_installOrigins_allowed_with_unified_extensions() { + await SpecialPowers.pushPrefEnv({ + set: [ + // Disable signature check because we load an unsigned MV3 extension. + ["xpinstall.signatures.required", false], + // When this pref is disabled, install should be possible. + ["extensions.install_origins.enabled", false], + ], + }); + + let win = await BrowserTestUtils.openNewBrowserWindow(); + await SimpleTest.promiseFocus(win); + + let notificationPromise = waitForNotification( + "addon-install-blocked", + 1, + "unified-extensions-button", + win + ); + let triggers = encodeURIComponent( + JSON.stringify({ + // This XPI does not have any `install_origins` in its manifest. + XPI: "unsigned_mv3.xpi", + }) + ); + await BrowserTestUtils.openNewForegroundTab( + win.gBrowser, + TESTROOT + "installtrigger.html?" + triggers + ); + let panel = await notificationPromise; + + let closePromise = waitForNotificationClose(win); + // hide the panel (this simulates the user dismissing it) + panel.hidePopup(); + await closePromise; + + await BrowserTestUtils.closeWindow(win); + await SpecialPowers.popPrefEnv(); + }, +]; + +var gTestStart = null; + +var XPInstallObserver = { + observe(aSubject, aTopic, aData) { + var installInfo = aSubject.wrappedJSObject; + 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(async function () { + requestLongerTimeout(4); + + await SpecialPowers.pushPrefEnv({ + set: [ + ["extensions.logging.enabled", true], + ["extensions.strictCompatibility", true], + ["extensions.install.requireSecureOrigin", false], + ["security.dialog_enable_delay", 0], + // These tests currently depends on InstallTrigger.install. + ["extensions.InstallTrigger.enabled", true], + ["extensions.InstallTriggerImpl.enabled", true], + // Relax the user input requirements while running this test. + ["xpinstall.userActivation.required", false], + ], + }); + + Services.obs.addObserver(XPInstallObserver, "addon-install-started"); + Services.obs.addObserver(XPInstallObserver, "addon-install-blocked"); + Services.obs.addObserver(XPInstallObserver, "addon-install-failed"); + + registerCleanupFunction(async function () { + // Make sure no more test parts run in case we were timed out + TESTS = []; + + let aInstalls = await AddonManager.getAllInstalls(); + aInstalls.forEach(function (aInstall) { + aInstall.cancel(); + }); + + Services.obs.removeObserver(XPInstallObserver, "addon-install-started"); + Services.obs.removeObserver(XPInstallObserver, "addon-install-blocked"); + Services.obs.removeObserver(XPInstallObserver, "addon-install-failed"); + }); + + 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 = await AddonManager.getAllInstalls(); + + is(installs.length, 0, "Should be no active installs"); + info("Running " + TESTS[i].name); + gTestStart = Date.now(); + await TESTS[i](); + } +}); diff --git a/toolkit/mozapps/extensions/test/xpinstall/browser_empty.js b/toolkit/mozapps/extensions/test/xpinstall/browser_empty.js new file mode 100644 index 0000000000..d9739a0dcf --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpinstall/browser_empty.js @@ -0,0 +1,39 @@ +// ---------------------------------------------------------------------------- +// Test whether an install fails when there is no install script present. +function test() { + // This test currently depends on InstallTrigger.install availability. + setInstallTriggerPrefs(); + + Harness.downloadFailedCallback = download_failed; + Harness.installsCompletedCallback = finish_test; + Harness.setup(); + + PermissionTestUtils.add( + "http://example.com/", + "install", + Services.perms.ALLOW_ACTION + ); + + var triggers = encodeURIComponent( + JSON.stringify({ + "Empty XPI": TESTROOT + "empty.xpi", + }) + ); + gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser); + BrowserTestUtils.startLoadingURIString( + gBrowser, + TESTROOT + "installtrigger.html?" + triggers + ); +} + +function download_failed(install) { + is(install.error, AddonManager.ERROR_CORRUPT_FILE, "Install should fail"); +} + +function finish_test(count) { + is(count, 0, "No add-ons should have been installed"); + PermissionTestUtils.remove("http://example.com", "install"); + + gBrowser.removeCurrentTab(); + Harness.finish(); +} diff --git a/toolkit/mozapps/extensions/test/xpinstall/browser_enabled.js b/toolkit/mozapps/extensions/test/xpinstall/browser_enabled.js new file mode 100644 index 0000000000..b8ee1b254f --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpinstall/browser_enabled.js @@ -0,0 +1,103 @@ +"use strict"; + +add_setup(async function () { + await SpecialPowers.pushPrefEnv({ + set: [ + ["extensions.InstallTrigger.enabled", true], + ["extensions.InstallTriggerImpl.enabled", true], + // Relax the user input requirements while running this test. + ["xpinstall.userActivation.required", false], + ], + }); +}); + +// Test whether an InstallTrigger.enabled is working +add_task(async function test_enabled() { + await BrowserTestUtils.openNewForegroundTab( + gBrowser, + TESTROOT + "enabled.html" + ); + + let text = await ContentTask.spawn( + gBrowser.selectedBrowser, + undefined, + () => content.document.getElementById("enabled").textContent + ); + + is(text, "true", "installTrigger should have been enabled"); + gBrowser.removeCurrentTab(); +}); + +// Test whether an InstallTrigger.enabled is working +add_task(async function test_disabled() { + Services.prefs.setBoolPref("xpinstall.enabled", false); + await BrowserTestUtils.openNewForegroundTab( + gBrowser, + TESTROOT + "enabled.html" + ); + + let text = await ContentTask.spawn( + gBrowser.selectedBrowser, + undefined, + () => content.document.getElementById("enabled").textContent + ); + + is(text, "false", "installTrigger should have not been enabled"); + Services.prefs.clearUserPref("xpinstall.enabled"); + gBrowser.removeCurrentTab(); +}); + +// Test whether an InstallTrigger.install call fails when xpinstall is disabled +add_task(async function test_disabled2() { + let installDisabledCalled = false; + + Harness.installDisabledCallback = installInfo => { + installDisabledCalled = true; + ok(true, "Saw installation disabled"); + }; + + Harness.installBlockedCallback = installInfo => { + ok(false, "Should never see the blocked install notification"); + return false; + }; + + Harness.installConfirmCallback = panel => { + ok(false, "Should never see an install confirmation dialog"); + return false; + }; + + Harness.setup(); + Services.prefs.setBoolPref("xpinstall.enabled", false); + + var triggers = encodeURIComponent( + JSON.stringify({ + "Unsigned XPI": TESTROOT + "amosigned.xpi", + }) + ); + + BrowserTestUtils.openNewForegroundTab( + gBrowser, + TESTROOT + "installtrigger.html?" + triggers + ); + + await BrowserTestUtils.waitForContentEvent( + gBrowser.selectedBrowser, + "InstallTriggered", + true, + undefined, + true + ); + + let text = await ContentTask.spawn( + gBrowser.selectedBrowser, + undefined, + () => content.document.getElementById("return").textContent + ); + + is(text, "false", "installTrigger should have not been enabled"); + ok(installDisabledCalled, "installDisabled callback was called"); + + Services.prefs.clearUserPref("xpinstall.enabled"); + gBrowser.removeCurrentTab(); + Harness.finish(); +}); diff --git a/toolkit/mozapps/extensions/test/xpinstall/browser_hash.js b/toolkit/mozapps/extensions/test/xpinstall/browser_hash.js new file mode 100644 index 0000000000..ab7d21b64e --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpinstall/browser_hash.js @@ -0,0 +1,47 @@ +// ---------------------------------------------------------------------------- +// Test whether an install succeeds when a valid hash is included +// This verifies bug 302284 +function test() { + // This test currently depends on InstallTrigger.install availability. + setInstallTriggerPrefs(); + + Harness.installEndedCallback = install_ended; + Harness.installsCompletedCallback = finish_test; + Harness.setup(); + + PermissionTestUtils.add( + "http://example.com/", + "install", + Services.perms.ALLOW_ACTION + ); + + var triggers = encodeURIComponent( + JSON.stringify({ + "Unsigned XPI": { + URL: TESTROOT + "amosigned.xpi", + Hash: "sha1:ee95834ad862245a9ef99ccecc2a857cadc16404", + toString() { + return this.URL; + }, + }, + }) + ); + gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser); + BrowserTestUtils.startLoadingURIString( + gBrowser, + TESTROOT + "installtrigger.html?" + triggers + ); +} + +function install_ended(install, addon) { + return addon.uninstall(); +} + +function finish_test(count) { + is(count, 1, "1 Add-on should have been successfully installed"); + + PermissionTestUtils.remove("http://example.com", "install"); + + gBrowser.removeCurrentTab(); + Harness.finish(); +} diff --git a/toolkit/mozapps/extensions/test/xpinstall/browser_hash2.js b/toolkit/mozapps/extensions/test/xpinstall/browser_hash2.js new file mode 100644 index 0000000000..9fd0c66292 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpinstall/browser_hash2.js @@ -0,0 +1,47 @@ +// ---------------------------------------------------------------------------- +// Test whether an install succeeds using case-insensitive hashes +// This verifies bug 603021 +function test() { + // This test currently depends on InstallTrigger.install availability. + setInstallTriggerPrefs(); + + Harness.installEndedCallback = install_ended; + Harness.installsCompletedCallback = finish_test; + Harness.setup(); + + PermissionTestUtils.add( + "http://example.com/", + "install", + Services.perms.ALLOW_ACTION + ); + + var triggers = encodeURIComponent( + JSON.stringify({ + "Unsigned XPI": { + URL: TESTROOT + "amosigned.xpi", + Hash: "sha1:EE95834AD862245A9EF99CCECC2A857CADC16404", + toString() { + return this.URL; + }, + }, + }) + ); + gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser); + BrowserTestUtils.startLoadingURIString( + gBrowser, + TESTROOT + "installtrigger.html?" + triggers + ); +} + +function install_ended(install, addon) { + return addon.uninstall(); +} + +function finish_test(count) { + is(count, 1, "1 Add-on should have been successfully installed"); + + PermissionTestUtils.remove("http://example.com", "install"); + + gBrowser.removeCurrentTab(); + Harness.finish(); +} diff --git a/toolkit/mozapps/extensions/test/xpinstall/browser_httphash.js b/toolkit/mozapps/extensions/test/xpinstall/browser_httphash.js new file mode 100644 index 0000000000..1ce8eb55af --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpinstall/browser_httphash.js @@ -0,0 +1,55 @@ +// ---------------------------------------------------------------------------- +// Test whether an install succeeds when a valid hash is included in the HTTPS +// request +// This verifies bug 591070 +function test() { + // This test currently depends on InstallTrigger.install availability. + setInstallTriggerPrefs(); + + Harness.installEndedCallback = install_ended; + Harness.installsCompletedCallback = finish_test; + Harness.setup(); + + PermissionTestUtils.add( + "http://example.com/", + "install", + Services.perms.ALLOW_ACTION + ); + Services.prefs.setBoolPref(PREF_INSTALL_REQUIREBUILTINCERTS, false); + + var url = "https://example.com/browser/" + RELATIVE_DIR + "hashRedirect.sjs"; + url += + "?sha1:ee95834ad862245a9ef99ccecc2a857cadc16404|" + + TESTROOT + + "amosigned.xpi"; + + var triggers = encodeURIComponent( + JSON.stringify({ + "Unsigned XPI": { + URL: url, + toString() { + return this.URL; + }, + }, + }) + ); + gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser); + BrowserTestUtils.startLoadingURIString( + gBrowser, + TESTROOT + "installtrigger.html?" + triggers + ); +} + +function install_ended(install, addon) { + return addon.uninstall(); +} + +function finish_test(count) { + is(count, 1, "1 Add-on should have been successfully installed"); + + PermissionTestUtils.remove("http://example.com", "install"); + Services.prefs.clearUserPref(PREF_INSTALL_REQUIREBUILTINCERTS); + + gBrowser.removeCurrentTab(); + Harness.finish(); +} diff --git a/toolkit/mozapps/extensions/test/xpinstall/browser_httphash2.js b/toolkit/mozapps/extensions/test/xpinstall/browser_httphash2.js new file mode 100644 index 0000000000..56014b0a3e --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpinstall/browser_httphash2.js @@ -0,0 +1,52 @@ +// ---------------------------------------------------------------------------- +// Test whether an install fails when a invalid hash is included in the HTTPS +// request +// This verifies bug 591070 +function test() { + // This test currently depends on InstallTrigger.install availability. + setInstallTriggerPrefs(); + + Harness.downloadFailedCallback = download_failed; + Harness.installsCompletedCallback = finish_test; + Harness.setup(); + + PermissionTestUtils.add( + "http://example.com/", + "install", + Services.perms.ALLOW_ACTION + ); + Services.prefs.setBoolPref(PREF_INSTALL_REQUIREBUILTINCERTS, false); + + var url = "https://example.com/browser/" + RELATIVE_DIR + "hashRedirect.sjs"; + url += "?sha1:foobar|" + TESTROOT + "amosigned.xpi"; + + var triggers = encodeURIComponent( + JSON.stringify({ + "Unsigned XPI": { + URL: url, + toString() { + return this.URL; + }, + }, + }) + ); + gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser); + BrowserTestUtils.startLoadingURIString( + gBrowser, + TESTROOT + "installtrigger.html?" + triggers + ); +} + +function download_failed(install) { + is(install.error, AddonManager.ERROR_INCORRECT_HASH, "Download should fail"); +} + +function finish_test(count) { + is(count, 0, "0 Add-ons should have been successfully installed"); + + PermissionTestUtils.remove("http://example.com", "install"); + Services.prefs.clearUserPref(PREF_INSTALL_REQUIREBUILTINCERTS); + + gBrowser.removeCurrentTab(); + Harness.finish(); +} diff --git a/toolkit/mozapps/extensions/test/xpinstall/browser_httphash3.js b/toolkit/mozapps/extensions/test/xpinstall/browser_httphash3.js new file mode 100644 index 0000000000..ffb5a3ddb4 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpinstall/browser_httphash3.js @@ -0,0 +1,52 @@ +// ---------------------------------------------------------------------------- +// Tests that the HTTPS hash is ignored when InstallTrigger is passed a hash. +// This verifies bug 591070 +function test() { + // This test currently depends on InstallTrigger.install availability. + setInstallTriggerPrefs(); + + Harness.installEndedCallback = install_ended; + Harness.installsCompletedCallback = finish_test; + Harness.setup(); + + PermissionTestUtils.add( + "http://example.com/", + "install", + Services.perms.ALLOW_ACTION + ); + Services.prefs.setBoolPref(PREF_INSTALL_REQUIREBUILTINCERTS, false); + + var url = "https://example.com/browser/" + RELATIVE_DIR + "hashRedirect.sjs"; + url += "?sha1:foobar|" + TESTROOT + "amosigned.xpi"; + + var triggers = encodeURIComponent( + JSON.stringify({ + "Unsigned XPI": { + URL: url, + Hash: "sha1:ee95834ad862245a9ef99ccecc2a857cadc16404", + toString() { + return this.URL; + }, + }, + }) + ); + gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser); + BrowserTestUtils.startLoadingURIString( + gBrowser, + TESTROOT + "installtrigger.html?" + triggers + ); +} + +function install_ended(install, addon) { + return addon.uninstall(); +} + +function finish_test(count) { + is(count, 1, "1 Add-on should have been successfully installed"); + + PermissionTestUtils.remove("http://example.com", "install"); + Services.prefs.clearUserPref(PREF_INSTALL_REQUIREBUILTINCERTS); + + gBrowser.removeCurrentTab(); + Harness.finish(); +} diff --git a/toolkit/mozapps/extensions/test/xpinstall/browser_httphash4.js b/toolkit/mozapps/extensions/test/xpinstall/browser_httphash4.js new file mode 100644 index 0000000000..4964d56443 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpinstall/browser_httphash4.js @@ -0,0 +1,49 @@ +// ---------------------------------------------------------------------------- +// Test that hashes are ignored in the headers of HTTP requests +// This verifies bug 591070 +function test() { + // This test currently depends on InstallTrigger.install availability. + setInstallTriggerPrefs(); + + Harness.installEndedCallback = install_ended; + Harness.installsCompletedCallback = finish_test; + Harness.setup(); + + PermissionTestUtils.add( + "http://example.com/", + "install", + Services.perms.ALLOW_ACTION + ); + + var url = "http://example.com/browser/" + RELATIVE_DIR + "hashRedirect.sjs"; + url += "?sha1:foobar|" + TESTROOT + "amosigned.xpi"; + + var triggers = encodeURIComponent( + JSON.stringify({ + "Unsigned XPI": { + URL: url, + toString() { + return this.URL; + }, + }, + }) + ); + gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser); + BrowserTestUtils.startLoadingURIString( + gBrowser, + TESTROOT + "installtrigger.html?" + triggers + ); +} + +function install_ended(install, addon) { + return addon.uninstall(); +} + +function finish_test(count) { + is(count, 1, "1 Add-on should have been successfully installed"); + + PermissionTestUtils.remove("http://example.com", "install"); + + gBrowser.removeCurrentTab(); + Harness.finish(); +} diff --git a/toolkit/mozapps/extensions/test/xpinstall/browser_httphash5.js b/toolkit/mozapps/extensions/test/xpinstall/browser_httphash5.js new file mode 100644 index 0000000000..727f13180b --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpinstall/browser_httphash5.js @@ -0,0 +1,53 @@ +// ---------------------------------------------------------------------------- +// Test that only the first HTTPS hash is used +// This verifies bug 591070 +function test() { + // This test currently depends on InstallTrigger.install availability. + setInstallTriggerPrefs(); + + Harness.installEndedCallback = install_ended; + Harness.installsCompletedCallback = finish_test; + Harness.setup(); + + PermissionTestUtils.add( + "http://example.com/", + "install", + Services.perms.ALLOW_ACTION + ); + Services.prefs.setBoolPref(PREF_INSTALL_REQUIREBUILTINCERTS, false); + + var url = "https://example.com/browser/" + RELATIVE_DIR + "hashRedirect.sjs"; + url += "?sha1:ee95834ad862245a9ef99ccecc2a857cadc16404|"; + url += "https://example.com/browser/" + RELATIVE_DIR + "hashRedirect.sjs"; + url += "?sha1:foobar|" + TESTROOT + "amosigned.xpi"; + + var triggers = encodeURIComponent( + JSON.stringify({ + "Unsigned XPI": { + URL: url, + toString() { + return this.URL; + }, + }, + }) + ); + gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser); + BrowserTestUtils.startLoadingURIString( + gBrowser, + TESTROOT + "installtrigger.html?" + triggers + ); +} + +function install_ended(install, addon) { + return addon.uninstall(); +} + +function finish_test(count) { + is(count, 1, "1 Add-on should have been successfully installed"); + + PermissionTestUtils.remove("http://example.com", "install"); + Services.prefs.clearUserPref(PREF_INSTALL_REQUIREBUILTINCERTS); + + gBrowser.removeCurrentTab(); + Harness.finish(); +} diff --git a/toolkit/mozapps/extensions/test/xpinstall/browser_httphash6.js b/toolkit/mozapps/extensions/test/xpinstall/browser_httphash6.js new file mode 100644 index 0000000000..81f35f2140 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpinstall/browser_httphash6.js @@ -0,0 +1,107 @@ +// ---------------------------------------------------------------------------- +// Tests that a new hash is accepted when restarting a failed download +// This verifies bug 593535 +function setup_redirect(aSettings) { + var url = + "https://example.com/browser/" + RELATIVE_DIR + "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); +} + +var gInstall = null; + +function test() { + // This test currently depends on InstallTrigger.install availability. + setInstallTriggerPrefs(); + + Harness.downloadFailedCallback = download_failed; + Harness.installsCompletedCallback = finish_failed_download; + Harness.setup(); + + PermissionTestUtils.add( + "http://example.com/", + "install", + Services.perms.ALLOW_ACTION + ); + Services.prefs.setBoolPref(PREF_INSTALL_REQUIREBUILTINCERTS, false); + + // Set up the redirect to give a bad hash + setup_redirect({ + "X-Target-Digest": "sha1:foo", + Location: "http://example.com/browser/" + RELATIVE_DIR + "amosigned.xpi", + }); + + var url = + "https://example.com/browser/" + + RELATIVE_DIR + + "redirect.sjs?mode=redirect"; + + var triggers = encodeURIComponent( + JSON.stringify({ + "Unsigned XPI": { + URL: url, + toString() { + return this.URL; + }, + }, + }) + ); + gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser); + BrowserTestUtils.startLoadingURIString( + gBrowser, + TESTROOT + "installtrigger.html?" + triggers + ); +} + +function download_failed(install) { + is( + install.error, + AddonManager.ERROR_INCORRECT_HASH, + "Should have seen a hash failure" + ); + // Stash the failed download while the harness cleans itself up + gInstall = install; +} + +function finish_failed_download() { + // Setup to track the successful re-download + Harness.installEndedCallback = install_ended; + Harness.installsCompletedCallback = finish_test; + Harness.setup(); + + // Give it the right hash this time + setup_redirect({ + "X-Target-Digest": "sha1:ee95834ad862245a9ef99ccecc2a857cadc16404", + Location: "http://example.com/browser/" + RELATIVE_DIR + "amosigned.xpi", + }); + + // The harness expects onNewInstall events for all installs that are about to start + Harness.onNewInstall(gInstall); + + // Restart the install as a regular webpage install so the harness tracks it + AddonManager.installAddonFromWebpage( + "application/x-xpinstall", + gBrowser.selectedBrowser, + gBrowser.contentPrincipal, + gInstall + ); +} + +function install_ended(install, addon) { + return addon.uninstall(); +} + +function finish_test(count) { + is(count, 1, "1 Add-on should have been successfully installed"); + + PermissionTestUtils.remove("http://example.com", "install"); + Services.prefs.clearUserPref(PREF_INSTALL_REQUIREBUILTINCERTS); + + gBrowser.removeCurrentTab(); + Harness.finish(); +} diff --git a/toolkit/mozapps/extensions/test/xpinstall/browser_installchrome.js b/toolkit/mozapps/extensions/test/xpinstall/browser_installchrome.js new file mode 100644 index 0000000000..319214b66a --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpinstall/browser_installchrome.js @@ -0,0 +1,36 @@ +// ---------------------------------------------------------------------------- +// Tests that calling InstallTrigger.installChrome works +function test() { + // This test depends on InstallTrigger.installChrome availability. + setInstallTriggerPrefs(); + + Harness.installEndedCallback = install_ended; + Harness.installsCompletedCallback = finish_test; + Harness.setup(); + + PermissionTestUtils.add( + "http://example.com/", + "install", + Services.perms.ALLOW_ACTION + ); + + gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser); + BrowserTestUtils.startLoadingURIString( + gBrowser, + TESTROOT + + "installchrome.html? " + + encodeURIComponent(TESTROOT + "amosigned.xpi") + ); +} + +function install_ended(install, addon) { + return addon.uninstall(); +} + +function finish_test(count) { + is(count, 1, "1 Add-on should have been successfully installed"); + PermissionTestUtils.remove("http://example.com", "install"); + + gBrowser.removeCurrentTab(); + Harness.finish(); +} diff --git a/toolkit/mozapps/extensions/test/xpinstall/browser_localfile.js b/toolkit/mozapps/extensions/test/xpinstall/browser_localfile.js new file mode 100644 index 0000000000..65ae80bd92 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpinstall/browser_localfile.js @@ -0,0 +1,42 @@ +// ---------------------------------------------------------------------------- +// Tests installing an local file works when loading the url +function test() { + Harness.installEndedCallback = install_ended; + Harness.installsCompletedCallback = finish_test; + Harness.setup(); + + var cr = Cc["@mozilla.org/chrome/chrome-registry;1"].getService( + Ci.nsIChromeRegistry + ); + + var chromeroot = extractChromeRoot(gTestPath); + var xpipath = chromeroot + "unsigned.xpi"; + try { + xpipath = cr.convertChromeURL(makeURI(chromeroot + "amosigned.xpi")).spec; + } catch (ex) { + // scenario where we are running from a .jar and already extracted + } + + gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser, "about:blank"); + BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser).then(() => { + BrowserTestUtils.startLoadingURIString(gBrowser, xpipath); + }); +} + +function install_ended(install, addon) { + Assert.deepEqual( + install.installTelemetryInfo, + { source: "file-url" }, + "Got the expected install.installTelemetryInfo" + ); + + return addon.uninstall(); +} + +function finish_test(count) { + is(count, 1, "1 Add-on should have been successfully installed"); + + gBrowser.removeCurrentTab(); + Harness.finish(); +} +// ---------------------------------------------------------------------------- diff --git a/toolkit/mozapps/extensions/test/xpinstall/browser_localfile2.js b/toolkit/mozapps/extensions/test/xpinstall/browser_localfile2.js new file mode 100644 index 0000000000..bfd52b18b9 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpinstall/browser_localfile2.js @@ -0,0 +1,61 @@ +add_setup(async function () { + await SpecialPowers.pushPrefEnv({ + set: [ + ["extensions.InstallTrigger.enabled", true], + ["extensions.InstallTriggerImpl.enabled", true], + ], + }); +}); + +// ---------------------------------------------------------------------------- +// Test whether an install fails if the url is a local file when requested from +// web content +add_task(async function test() { + var cr = Cc["@mozilla.org/chrome/chrome-registry;1"].getService( + Ci.nsIChromeRegistry + ); + + var chromeroot = getChromeRoot(gTestPath); + var xpipath = chromeroot + "amosigned.xpi"; + try { + xpipath = cr.convertChromeURL(makeURI(xpipath)).spec; + } catch (ex) { + // scenario where we are running from a .jar and already extracted + } + + var triggers = encodeURIComponent( + JSON.stringify({ + "Unsigned XPI": xpipath, + }) + ); + + // In non-e10s the exception in the content page would trigger a test failure + if (!gMultiProcessBrowser) { + expectUncaughtException(); + } + + let URI = TESTROOT + "installtrigger.html?manualStartInstall" + triggers; + await BrowserTestUtils.withNewTab( + { gBrowser, url: URI }, + async function (browser) { + await SpecialPowers.spawn(browser, [], async function () { + let installTriggered = ContentTaskUtils.waitForEvent( + docShell.chromeEventHandler, + "InstallTriggered", + true, + null, + true + ); + content.wrappedJSObject.startInstall(); + await installTriggered; + let doc = content.document; + is( + doc.getElementById("return").textContent, + "exception", + "installTrigger should have failed" + ); + }); + } + ); +}); +// ---------------------------------------------------------------------------- diff --git a/toolkit/mozapps/extensions/test/xpinstall/browser_localfile3.js b/toolkit/mozapps/extensions/test/xpinstall/browser_localfile3.js new file mode 100644 index 0000000000..c8d8532ed0 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpinstall/browser_localfile3.js @@ -0,0 +1,42 @@ +// ---------------------------------------------------------------------------- +// Tests installing an add-on from a local file with whitelisting disabled. +// This should be blocked by the whitelist check. +function test() { + Harness.installBlockedCallback = allow_blocked; + Harness.installsCompletedCallback = finish_test; + Harness.setup(); + + // Disable direct request whitelisting, installing from file should be blocked. + Services.prefs.setBoolPref("xpinstall.whitelist.directRequest", false); + + var cr = Cc["@mozilla.org/chrome/chrome-registry;1"].getService( + Ci.nsIChromeRegistry + ); + + var chromeroot = extractChromeRoot(gTestPath); + var xpipath = chromeroot + "amosigned.xpi"; + try { + xpipath = cr.convertChromeURL(makeURI(xpipath)).spec; + } catch (ex) { + // scenario where we are running from a .jar and already extracted + } + + gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser, "about:blank"); + BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser).then(() => { + BrowserTestUtils.startLoadingURIString(gBrowser, xpipath); + }); +} + +function allow_blocked(installInfo) { + ok(true, "Seen blocked"); + return false; +} + +function finish_test(count) { + is(count, 0, "No add-ons should have been installed"); + + Services.prefs.clearUserPref("xpinstall.whitelist.directRequest"); + + gBrowser.removeCurrentTab(); + Harness.finish(); +} diff --git a/toolkit/mozapps/extensions/test/xpinstall/browser_localfile4.js b/toolkit/mozapps/extensions/test/xpinstall/browser_localfile4.js new file mode 100644 index 0000000000..771832a72b --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpinstall/browser_localfile4.js @@ -0,0 +1,55 @@ +// ---------------------------------------------------------------------------- +// Tests installing an add-on from a local file with file origins disabled. +// This should be blocked by the origin allowed check. +function test() { + // This test currently depends on InstallTrigger.install availability. + setInstallTriggerPrefs(); + + // prompt prior to download + SpecialPowers.pushPrefEnv({ + set: [["extensions.postDownloadThirdPartyPrompt", false]], + }); + + Harness.installBlockedCallback = allow_blocked; + Harness.installsCompletedCallback = finish_test; + Harness.setup(); + + // Disable local file install, installing by file referrer should be blocked. + Services.prefs.setBoolPref("xpinstall.whitelist.fileRequest", false); + + var cr = Cc["@mozilla.org/chrome/chrome-registry;1"].getService( + Ci.nsIChromeRegistry + ); + + var chromeroot = extractChromeRoot(gTestPath); + var xpipath = chromeroot; + try { + xpipath = cr.convertChromeURL(makeURI(chromeroot)).spec; + } catch (ex) { + // scenario where we are running from a .jar and already extracted + } + var triggers = encodeURIComponent( + JSON.stringify({ + "Unsigned XPI": TESTROOT + "amosigned.xpi", + }) + ); + gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser); + BrowserTestUtils.startLoadingURIString( + gBrowser, + xpipath + "installtrigger.html?" + triggers + ); +} + +function allow_blocked(installInfo) { + ok(true, "Seen blocked"); + return false; +} + +function finish_test(count) { + is(count, 0, "No add-ons should have been installed"); + + Services.prefs.clearUserPref("xpinstall.whitelist.fileRequest"); + + gBrowser.removeCurrentTab(); + Harness.finish(); +} diff --git a/toolkit/mozapps/extensions/test/xpinstall/browser_localfile4_postDownload.js b/toolkit/mozapps/extensions/test/xpinstall/browser_localfile4_postDownload.js new file mode 100644 index 0000000000..8f8484a1c9 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpinstall/browser_localfile4_postDownload.js @@ -0,0 +1,54 @@ +// ---------------------------------------------------------------------------- +// Tests installing an add-on from a local file with file origins disabled. +// This should be blocked by the origin allowed check. +function test() { + // This test currently depends on InstallTrigger.install availability. + setInstallTriggerPrefs(); + + Harness.installBlockedCallback = allow_blocked; + Harness.installsCompletedCallback = finish_test; + // Prevent the Harness from ending the test on download cancel. + Harness.downloadCancelledCallback = () => { + return false; + }; + Harness.setup(); + + // Disable local file install, installing by file referrer should be blocked. + Services.prefs.setBoolPref("xpinstall.whitelist.fileRequest", false); + + var cr = Cc["@mozilla.org/chrome/chrome-registry;1"].getService( + Ci.nsIChromeRegistry + ); + + var chromeroot = extractChromeRoot(gTestPath); + var xpipath = chromeroot; + try { + xpipath = cr.convertChromeURL(makeURI(chromeroot)).spec; + } catch (ex) { + // scenario where we are running from a .jar and already extracted + } + var triggers = encodeURIComponent( + JSON.stringify({ + "Unsigned XPI": TESTROOT + "amosigned.xpi", + }) + ); + gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser); + BrowserTestUtils.startLoadingURIString( + gBrowser, + xpipath + "installtrigger.html?" + triggers + ); +} + +function allow_blocked(installInfo) { + ok(true, "Seen blocked"); + return false; +} + +function finish_test(count) { + is(count, 0, "No add-ons should have been installed"); + + Services.prefs.clearUserPref("xpinstall.whitelist.fileRequest"); + + gBrowser.removeCurrentTab(); + Harness.finish(); +} diff --git a/toolkit/mozapps/extensions/test/xpinstall/browser_newwindow.js b/toolkit/mozapps/extensions/test/xpinstall/browser_newwindow.js new file mode 100644 index 0000000000..c4bc5c5d56 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpinstall/browser_newwindow.js @@ -0,0 +1,89 @@ +// This functionality covered in this test is also covered in other tests. +// The purpose of this test is to catch window leaks. It should fail in +// debug builds if a window reference is held onto after an install finishes. +// See bug 1541577 for further details. + +let win; +let popupPromise; +let newtabPromise; +const exampleURI = Services.io.newURI("http://example.com"); +async function test() { + waitForExplicitFinish(); // have to call this ourselves because we're async. + + // This test currently depends on InstallTrigger.install availability. + setInstallTriggerPrefs(); + + await SpecialPowers.pushPrefEnv({ + set: [["dom.security.https_first", false]], + }); + Harness.installConfirmCallback = confirm_install; + Harness.installEndedCallback = install => { + return install.addon.uninstall(); + }; + Harness.installsCompletedCallback = finish_test; + Harness.finalContentEvent = "InstallComplete"; + win = await BrowserTestUtils.openNewBrowserWindow(); + Harness.setup(win); + + PermissionTestUtils.add(exampleURI, "install", Services.perms.ALLOW_ACTION); + + const triggers = encodeURIComponent( + JSON.stringify({ + "Unsigned XPI": { + URL: TESTROOT + "amosigned.xpi", + IconURL: TESTROOT + "icon.png", + }, + }) + ); + + const url = `${TESTROOT}installtrigger.html?${triggers}`; + newtabPromise = BrowserTestUtils.openNewForegroundTab(win.gBrowser, url); + popupPromise = BrowserTestUtils.waitForEvent( + win.PanelUI.notificationPanel, + "popupshown" + ); +} + +function confirm_install(panel) { + is(panel.getAttribute("name"), "XPI Test", "Should have seen the name"); + return true; +} + +async function finish_test(count) { + is(count, 1, "1 Add-on should have been successfully installed"); + + PermissionTestUtils.remove(exampleURI, "install"); + + const results = await SpecialPowers.spawn( + win.gBrowser.selectedBrowser, + [], + () => { + return { + return: content.document.getElementById("return").textContent, + status: content.document.getElementById("status").textContent, + }; + } + ); + + is(results.return, "true", "installTrigger should have claimed success"); + is(results.status, "0", "Callback should have seen a success"); + + // Explicitly click the "OK" button to avoid the panel reopening in the other window once this + // window closes (see also bug 1535069): + await popupPromise; + win.PanelUI.notificationPanel + .querySelector("popupnotification[popupid=addon-installed]") + .button.click(); + + // Wait for the promise returned by BrowserTestUtils.openNewForegroundTab + // to be resolved before removing the window to prevent an uncaught exception + // triggered from inside openNewForegroundTab to trigger a test failure due + // to a race between openNewForegroundTab and closeWindow calls, e.g. as for + // Bug 1728482). + await newtabPromise; + + // Now finish the test: + await BrowserTestUtils.closeWindow(win); + Harness.finish(win); + win = null; +} diff --git a/toolkit/mozapps/extensions/test/xpinstall/browser_offline.js b/toolkit/mozapps/extensions/test/xpinstall/browser_offline.js new file mode 100644 index 0000000000..946ad1dff7 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpinstall/browser_offline.js @@ -0,0 +1,82 @@ +var proxyPrefValue; + +// ---------------------------------------------------------------------------- +// Tests that going offline cancels an in progress download. +function test() { + // This test currently depends on InstallTrigger.install availability. + setInstallTriggerPrefs(); + + Harness.downloadProgressCallback = download_progress; + Harness.installsCompletedCallback = finish_test; + Harness.setup(); + + PermissionTestUtils.add( + "http://example.com/", + "install", + Services.perms.ALLOW_ACTION + ); + + var triggers = encodeURIComponent( + JSON.stringify({ + "Unsigned XPI": TESTROOT + "amosigned.xpi", + }) + ); + gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser); + BrowserTestUtils.startLoadingURIString( + gBrowser, + TESTROOT + "installtrigger.html?" + triggers + ); +} + +function download_progress(addon, value, maxValue) { + try { + // Tests always connect to localhost, and per bug 87717, localhost is now + // reachable in offline mode. To avoid this, disable any proxy. + proxyPrefValue = Services.prefs.getIntPref("network.proxy.type"); + Services.prefs.setIntPref("network.proxy.type", 0); + Services.io.manageOfflineStatus = false; + Services.io.offline = true; + } catch (ex) {} +} + +function finish_test(count) { + function wait_for_online() { + info("Checking if the browser is still offline..."); + + let tab = gBrowser.selectedTab; + BrowserTestUtils.waitForContentEvent( + tab.linkedBrowser, + "DOMContentLoaded", + true + ).then(async function () { + let url = await ContentTask.spawn( + tab.linkedBrowser, + null, + async function () { + return content.document.documentURI; + } + ); + info("loaded: " + url); + if (/^about:neterror\?e=netOffline/.test(url)) { + wait_for_online(); + } else { + gBrowser.removeCurrentTab(); + Harness.finish(); + } + }); + BrowserTestUtils.startLoadingURIString( + tab.linkedBrowser, + "http://example.com/" + ); + } + + is(count, 0, "No add-ons should have been installed"); + try { + Services.prefs.setIntPref("network.proxy.type", proxyPrefValue); + Services.io.offline = false; + } catch (ex) {} + + PermissionTestUtils.remove("http://example.com", "install"); + + wait_for_online(); +} diff --git a/toolkit/mozapps/extensions/test/xpinstall/browser_privatebrowsing.js b/toolkit/mozapps/extensions/test/xpinstall/browser_privatebrowsing.js new file mode 100644 index 0000000000..9d55a3d8fa --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpinstall/browser_privatebrowsing.js @@ -0,0 +1,133 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +"use strict"; + +const { AddonTestUtils } = ChromeUtils.importESModule( + "resource://testing-common/AddonTestUtils.sys.mjs" +); + +let gDidSeeChannel = false; + +AddonTestUtils.initMochitest(this); + +function check_channel(subject) { + if (!(subject instanceof Ci.nsIHttpChannel)) { + return; + } + let channel = subject.QueryInterface(Ci.nsIHttpChannel); + let uri = channel.URI; + if (!uri || !uri.spec.endsWith("amosigned.xpi")) { + return; + } + gDidSeeChannel = true; + ok(true, "Got request for " + uri.spec); + + let loadInfo = channel.loadInfo; + is( + loadInfo.originAttributes.privateBrowsingId, + 1, + "Request should have happened using private browsing" + ); +} +// ---------------------------------------------------------------------------- +// Tests we send the right cookies when installing through an InstallTrigger call +let gPrivateWin; +async function test() { + waitForExplicitFinish(); // have to call this ourselves because we're async. + + // This test currently depends on InstallTrigger.install availability. + setInstallTriggerPrefs(); + + await SpecialPowers.pushPrefEnv({ + set: [["dom.security.https_first_pbm", false]], + }); + Harness.installConfirmCallback = confirm_install; + Harness.installEndedCallback = install_ended; + Harness.installsCompletedCallback = finish_test; + Harness.finalContentEvent = "InstallComplete"; + gPrivateWin = await BrowserTestUtils.openNewBrowserWindow({ private: true }); + Harness.setup(gPrivateWin); + + let principal = Services.scriptSecurityManager.createContentPrincipal( + Services.io.newURI("http://example.com/"), + { privateBrowsingId: 1 } + ); + + PermissionTestUtils.add(principal, "install", Services.perms.ALLOW_ACTION); + + var triggers = encodeURIComponent( + JSON.stringify({ + "Unsigned XPI": { + URL: TESTROOT + "amosigned.xpi", + IconURL: TESTROOT + "icon.png", + toString() { + return this.URL; + }, + }, + }) + ); + gPrivateWin.gBrowser.selectedTab = BrowserTestUtils.addTab( + gPrivateWin.gBrowser + ); + Services.obs.addObserver(check_channel, "http-on-before-connect"); + BrowserTestUtils.startLoadingURIString( + gPrivateWin.gBrowser, + TESTROOT + "installtrigger.html?" + triggers + ); +} + +function confirm_install(panel) { + is(panel.getAttribute("name"), "XPI Test", "Should have seen the name"); + return true; +} + +function install_ended(install, addon) { + AddonTestUtils.checkInstallInfo(install, { + method: "installTrigger", + source: "test-host", + sourceURL: /http:\/\/example.com\/.*\/installtrigger.html/, + }); + return addon.uninstall(); +} + +const finish_test = async function (count) { + ok( + gDidSeeChannel, + "Should have seen the request for the XPI and verified it was sent the right way." + ); + is(count, 1, "1 Add-on should have been successfully installed"); + + Services.obs.removeObserver(check_channel, "http-on-before-connect"); + + PermissionTestUtils.remove("http://example.com", "install"); + + const results = await SpecialPowers.spawn( + gPrivateWin.gBrowser.selectedBrowser, + [], + () => { + return { + return: content.document.getElementById("return").textContent, + status: content.document.getElementById("status").textContent, + }; + } + ); + + is(results.return, "true", "installTrigger should have claimed success"); + is(results.status, "0", "Callback should have seen a success"); + + await TestUtils.waitForCondition(() => + gPrivateWin.AppMenuNotifications._notifications.some( + n => n.id == "addon-installed" + ) + ); + // Explicitly remove the notification to avoid the panel reopening in the + // other window once this window closes (see also bug 1535069): + gPrivateWin.AppMenuNotifications.removeNotification("addon-installed"); + + // Now finish the test: + await BrowserTestUtils.closeWindow(gPrivateWin); + Harness.finish(gPrivateWin); + gPrivateWin = null; +}; diff --git a/toolkit/mozapps/extensions/test/xpinstall/browser_relative.js b/toolkit/mozapps/extensions/test/xpinstall/browser_relative.js new file mode 100644 index 0000000000..10d5df8b73 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpinstall/browser_relative.js @@ -0,0 +1,67 @@ +// ---------------------------------------------------------------------------- +// Tests that InstallTrigger deals with relative urls correctly. +function test() { + // This test depends on InstallTrigger.install availability. + setInstallTriggerPrefs(); + + Harness.installConfirmCallback = confirm_install; + Harness.installEndedCallback = install_ended; + Harness.installsCompletedCallback = finish_test; + Harness.finalContentEvent = "InstallComplete"; + Harness.setup(); + + PermissionTestUtils.add( + "http://example.com/", + "install", + Services.perms.ALLOW_ACTION + ); + + var triggers = encodeURIComponent( + JSON.stringify({ + "Unsigned XPI": { + URL: "amosigned.xpi", + IconURL: "icon.png", + toString() { + return this.URL; + }, + }, + }) + ); + gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser); + BrowserTestUtils.startLoadingURIString( + gBrowser, + TESTROOT + "installtrigger.html?" + triggers + ); +} + +function confirm_install(panel) { + is(panel.getAttribute("name"), "XPI Test", "Should have seen the name"); + return true; +} + +function install_ended(install, addon) { + return addon.uninstall(); +} + +const finish_test = async function (count) { + is(count, 1, "1 Add-on should have been successfully installed"); + + PermissionTestUtils.remove("http://example.com", "install"); + + const results = await SpecialPowers.spawn( + gBrowser.selectedBrowser, + [], + () => { + return { + return: content.document.getElementById("return").textContent, + status: content.document.getElementById("status").textContent, + }; + } + ); + + is(results.return, "true", "installTrigger should have claimed success"); + is(results.status, "0", "Callback should have seen a success"); + + gBrowser.removeCurrentTab(); + Harness.finish(); +}; diff --git a/toolkit/mozapps/extensions/test/xpinstall/browser_required_useractivation.js b/toolkit/mozapps/extensions/test/xpinstall/browser_required_useractivation.js new file mode 100644 index 0000000000..6c8894699d --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpinstall/browser_required_useractivation.js @@ -0,0 +1,156 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +"use strict"; + +const { AddonTestUtils } = ChromeUtils.importESModule( + "resource://testing-common/AddonTestUtils.sys.mjs" +); + +AddonTestUtils.initMochitest(this); + +const XPI_URL = `${TESTROOT}amosigned.xpi`; + +async function runTestCase(spawnArgs, spawnFn, { expectInstall, clickLink }) { + await SpecialPowers.pushPrefEnv({ + set: [ + // Make use the user activation requirements is enabled while running this test. + ["xpinstall.userActivation.required", true], + ], + }); + await BrowserTestUtils.withNewTab(TESTROOT, async browser => { + const expectedError = `${XPI_URL} install cancelled because of missing user gesture activation`; + + let promiseDone; + + if (expectInstall) { + promiseDone = TestUtils.topicObserved("addon-install-blocked").then( + ([subject]) => { + // Cancel the pending installation flow. + subject.wrappedJSObject.cancel(); + } + ); + } else { + promiseDone = new Promise(resolve => { + function messageHandler(msgObj) { + if ( + msgObj instanceof Ci.nsIScriptError && + msgObj.message.includes(expectedError) + ) { + ok( + true, + "Expect error on triggering navigation to xpi without user gesture activation" + ); + cleanupListener(); + resolve(); + } + } + let listenerCleared = false; + function cleanupListener() { + if (!listenerCleared) { + Services.console.unregisterListener(messageHandler); + } + listenerCleared = true; + } + Services.console.registerListener(messageHandler); + registerCleanupFunction(cleanupListener); + }); + } + + await SpecialPowers.spawn(browser, spawnArgs, spawnFn); + + if (clickLink) { + info("Click link element"); + // Wait for the install to trigger the third party website doorhanger. + // Trigger the link by simulating a mouse click, and expect it to trigger the + // install flow instead (the window is still navigated to the xpi url from the + // webpage JS code, but doing it while handling a DOM event does make it pass + // the user activation check). + await BrowserTestUtils.synthesizeMouseAtCenter( + "#link-to-xpi-file", + {}, + browser + ); + } + + info("Wait test case to be completed"); + await promiseDone; + ok(true, "Test case run completed"); + }); +} + +add_task(async function testSuccessOnUserActivatedLink() { + await runTestCase( + [XPI_URL], + xpiURL => { + const { document } = this.content; + const link = document.createElement("a"); + link.id = "link-to-xpi-file"; + link.setAttribute("href", xpiURL); + link.textContent = "Link to XPI File"; + + // Empty the test case and add the link, if the link is not visible + // without scrolling, BrowserTestUtils.synthesizeMouseAtCenter may + // fail to trigger the mouse event. + document.body.innerHTML = ""; + document.body.appendChild(link); + }, + { expectInstall: true, clickLink: true } + ); +}); + +add_task(async function testSuccessOnJSWithUserActivation() { + await runTestCase( + [XPI_URL], + xpiURL => { + const { document } = this.content; + const link = document.createElement("a"); + link.id = "link-to-xpi-file"; + link.setAttribute("href", "#"); + link.textContent = "Link to XPI File"; + + // Empty the test case and add the link, if the link is not visible + // without scrolling, BrowserTestUtils.synthesizeMouseAtCenter may + // fail to trigger the mouse event. + document.body.innerHTML = ""; + document.body.appendChild(link); + + this.content.eval(` + const linkEl = document.querySelector("#link-to-xpi-file"); + linkEl.onclick = () => { + // This is expected to trigger the install flow successfully if handling + // a user gesture DOM event, but to fail when triggered outside of it (as + // done a few line below). + window.location = "${xpiURL}"; + }; + `); + }, + { expectInstall: true, clickLink: true } + ); +}); + +add_task(async function testFailureOnJSWithoutUserActivation() { + await runTestCase( + [XPI_URL], + xpiURL => { + this.content.eval(`window.location = "${xpiURL}";`); + }, + { expectInstall: false } + ); +}); + +add_task(async function testFailureOnJSWithoutUserActivation() { + await runTestCase( + [XPI_URL], + xpiURL => { + this.content.eval(` + const frame = document.createElement("iframe"); + frame.src = "${xpiURL}"; + document.body.innerHTML = ""; + document.body.appendChild(frame); + `); + }, + { expectInstall: false } + ); +}); diff --git a/toolkit/mozapps/extensions/test/xpinstall/browser_signed_url.js b/toolkit/mozapps/extensions/test/xpinstall/browser_signed_url.js new file mode 100644 index 0000000000..b01137927b --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpinstall/browser_signed_url.js @@ -0,0 +1,32 @@ +// ---------------------------------------------------------------------------- +// Tests installing an signed add-on by navigating directly to the url +function test() { + Harness.installConfirmCallback = confirm_install; + Harness.installEndedCallback = install_ended; + Harness.installsCompletedCallback = finish_test; + Harness.setup(); + + gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser, "about:blank"); + BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser).then(() => { + BrowserTestUtils.startLoadingURIString( + gBrowser, + TESTROOT + "amosigned.xpi" + ); + }); +} + +function confirm_install(panel) { + is(panel.getAttribute("name"), "XPI Test", "Should have seen the name"); + return true; +} + +function install_ended(install, addon) { + return addon.uninstall(); +} + +function finish_test(count) { + is(count, 1, "1 Add-on should have been successfully installed"); + gBrowser.removeCurrentTab(); + Harness.finish(); +} +// ---------------------------------------------------------------------------- diff --git a/toolkit/mozapps/extensions/test/xpinstall/browser_softwareupdate.js b/toolkit/mozapps/extensions/test/xpinstall/browser_softwareupdate.js new file mode 100644 index 0000000000..76ddf0b7d3 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpinstall/browser_softwareupdate.js @@ -0,0 +1,36 @@ +// ---------------------------------------------------------------------------- +// Tests that calling InstallTrigger.startSoftwareUpdate works +function test() { + // This test depends on InstallTrigger.startSoftwareUpdate availability. + setInstallTriggerPrefs(); + + Harness.installEndedCallback = install_ended; + Harness.installsCompletedCallback = finish_test; + Harness.setup(); + + PermissionTestUtils.add( + "http://example.com/", + "install", + Services.perms.ALLOW_ACTION + ); + + gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser); + BrowserTestUtils.startLoadingURIString( + gBrowser, + TESTROOT + + "startsoftwareupdate.html? " + + encodeURIComponent(TESTROOT + "amosigned.xpi") + ); +} + +function install_ended(install, addon) { + return addon.uninstall(); +} + +function finish_test(count) { + is(count, 1, "1 Add-on should have been successfully installed"); + PermissionTestUtils.remove("http://example.com", "install"); + + gBrowser.removeCurrentTab(); + Harness.finish(); +} diff --git a/toolkit/mozapps/extensions/test/xpinstall/browser_trigger_redirect.js b/toolkit/mozapps/extensions/test/xpinstall/browser_trigger_redirect.js new file mode 100644 index 0000000000..51e08e5001 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpinstall/browser_trigger_redirect.js @@ -0,0 +1,48 @@ +// ---------------------------------------------------------------------------- +// Tests that the InstallTrigger callback can redirect to a relative url. +function test() { + // This test depends on InstallTrigger.install availability. + setInstallTriggerPrefs(); + + Harness.installConfirmCallback = confirm_install; + Harness.installEndedCallback = install_ended; + Harness.installsCompletedCallback = finish_test; + Harness.finalContentEvent = "InstallComplete"; + Harness.setup(); + + PermissionTestUtils.add( + "http://example.com/", + "install", + Services.perms.ALLOW_ACTION + ); + + gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser); + BrowserTestUtils.startLoadingURIString( + gBrowser, + TESTROOT + "triggerredirect.html" + ); +} + +function confirm_install(panel) { + is(panel.getAttribute("name"), "XPI Test", "Should have seen the name"); + return true; +} + +function install_ended(install, addon) { + return addon.uninstall(); +} + +function finish_test(count) { + is(count, 1, "1 Add-on should have been successfully installed"); + + PermissionTestUtils.remove("http://example.com", "install"); + + is( + gBrowser.currentURI.spec, + TESTROOT + "triggerredirect.html#foo", + "Should have redirected" + ); + + gBrowser.removeCurrentTab(); + Harness.finish(); +} diff --git a/toolkit/mozapps/extensions/test/xpinstall/browser_unsigned_trigger.js b/toolkit/mozapps/extensions/test/xpinstall/browser_unsigned_trigger.js new file mode 100644 index 0000000000..cb81cf3d36 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpinstall/browser_unsigned_trigger.js @@ -0,0 +1,68 @@ +// ---------------------------------------------------------------------------- +// Tests installing an unsigned add-on through an InstallTrigger call in web +// content. +function test() { + // This test depends on InstallTrigger.install availability. + setInstallTriggerPrefs(); + + Harness.installConfirmCallback = confirm_install; + Harness.installEndedCallback = install_ended; + Harness.installsCompletedCallback = finish_test; + Harness.finalContentEvent = "InstallComplete"; + Harness.setup(); + + PermissionTestUtils.add( + "http://example.com/", + "install", + Services.perms.ALLOW_ACTION + ); + + var triggers = encodeURIComponent( + JSON.stringify({ + "Unsigned XPI": { + URL: TESTROOT + "unsigned.xpi", + IconURL: TESTROOT + "icon.png", + toString() { + return this.URL; + }, + }, + }) + ); + gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser); + BrowserTestUtils.startLoadingURIString( + gBrowser, + TESTROOT + "installtrigger.html?" + triggers + ); +} + +function confirm_install(panel) { + is(panel.getAttribute("name"), "XPI Test", "Should have seen the name"); + return true; +} + +function install_ended(install, addon) { + return addon.uninstall(); +} + +const finish_test = async function (count) { + is(count, 1, "1 Add-on should have been successfully installed"); + + PermissionTestUtils.remove("http://example.com", "install"); + + const results = await SpecialPowers.spawn( + gBrowser.selectedBrowser, + [], + () => { + return { + return: content.document.getElementById("return").textContent, + status: content.document.getElementById("status").textContent, + }; + } + ); + + is(results.return, "true", "installTrigger should have claimed success"); + is(results.status, "0", "Callback should have seen a success"); + + gBrowser.removeCurrentTab(); + Harness.finish(); +}; diff --git a/toolkit/mozapps/extensions/test/xpinstall/browser_unsigned_trigger_iframe.js b/toolkit/mozapps/extensions/test/xpinstall/browser_unsigned_trigger_iframe.js new file mode 100644 index 0000000000..c98477d6e9 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpinstall/browser_unsigned_trigger_iframe.js @@ -0,0 +1,77 @@ +// ---------------------------------------------------------------------------- +// Test for bug 589598 - Ensure that installing through InstallTrigger +// works in an iframe in web content. + +function test() { + // This test depends on InstallTrigger.install availability. + setInstallTriggerPrefs(); + + Harness.installConfirmCallback = confirm_install; + Harness.installEndedCallback = install_ended; + Harness.installsCompletedCallback = finish_test; + Harness.finalContentEvent = "InstallComplete"; + Harness.setup(); + + PermissionTestUtils.add( + "http://example.com/", + "install", + Services.perms.ALLOW_ACTION + ); + + var inner_url = encodeURIComponent( + TESTROOT + + "installtrigger.html?" + + encodeURIComponent( + JSON.stringify({ + "Unsigned XPI": { + URL: TESTROOT + "unsigned.xpi", + IconURL: TESTROOT + "icon.png", + toString() { + return this.URL; + }, + }, + }) + ) + ); + gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser); + BrowserTestUtils.startLoadingURIString( + gBrowser, + TESTROOT + "installtrigger_frame.html?" + inner_url + ); +} + +function confirm_install(panel) { + is(panel.getAttribute("name"), "XPI Test", "Should have seen the name"); + return true; +} + +function install_ended(install, addon) { + return addon.uninstall(); +} + +const finish_test = async function (count) { + is(count, 1, "1 Add-on should have been successfully installed"); + + PermissionTestUtils.remove("http://example.com", "install"); + + const results = await SpecialPowers.spawn( + gBrowser.selectedBrowser, + [], + () => { + return { + return: content.frames[0].document.getElementById("return").textContent, + status: content.frames[0].document.getElementById("status").textContent, + }; + } + ); + + is( + results.return, + "true", + "installTrigger in iframe should have claimed success" + ); + is(results.status, "0", "Callback in iframe should have seen a success"); + + gBrowser.removeCurrentTab(); + Harness.finish(); +}; diff --git a/toolkit/mozapps/extensions/test/xpinstall/browser_unsigned_trigger_xorigin.js b/toolkit/mozapps/extensions/test/xpinstall/browser_unsigned_trigger_xorigin.js new file mode 100644 index 0000000000..7edbf318a0 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpinstall/browser_unsigned_trigger_xorigin.js @@ -0,0 +1,58 @@ +// ---------------------------------------------------------------------------- +// Ensure that an inner frame from a different origin can't initiate an install + +var wasOriginBlocked = false; + +function test() { + // This test depends on InstallTrigger.install availability. + setInstallTriggerPrefs(); + + Harness.installOriginBlockedCallback = install_blocked; + Harness.installsCompletedCallback = finish_test; + Harness.finalContentEvent = "InstallComplete"; + Harness.setup(); + + PermissionTestUtils.add( + "http://example.com/", + "install", + Services.perms.ALLOW_ACTION + ); + + var inner_url = encodeURIComponent( + TESTROOT + + "installtrigger.html?" + + encodeURIComponent( + JSON.stringify({ + "Unsigned XPI": { + URL: TESTROOT + "amosigned.xpi", + IconURL: TESTROOT + "icon.png", + toString() { + return this.URL; + }, + }, + }) + ) + ); + gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser); + BrowserTestUtils.startLoadingURIString( + gBrowser, + TESTROOT2 + "installtrigger_frame.html?" + inner_url + ); +} + +function install_blocked(installInfo) { + wasOriginBlocked = true; +} + +function finish_test(count) { + ok( + wasOriginBlocked, + "Should have been blocked due to the cross origin request." + ); + + is(count, 0, "No add-ons should have been installed"); + PermissionTestUtils.remove("http://example.com", "install"); + + gBrowser.removeCurrentTab(); + Harness.finish(); +} diff --git a/toolkit/mozapps/extensions/test/xpinstall/browser_unsigned_url.js b/toolkit/mozapps/extensions/test/xpinstall/browser_unsigned_url.js new file mode 100644 index 0000000000..ef5723640e --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpinstall/browser_unsigned_url.js @@ -0,0 +1,43 @@ +// ---------------------------------------------------------------------------- +// Tests installing an unsigned add-on by navigating directly to the url +function test() { + waitForExplicitFinish(); + SpecialPowers.pushPrefEnv( + { + set: [ + // Relax the user input requirements while running this test. + ["xpinstall.userActivation.required", false], + ], + }, + runTest + ); +} + +function runTest() { + Harness.installConfirmCallback = confirm_install; + Harness.installEndedCallback = install_ended; + Harness.installsCompletedCallback = finish_test; + Harness.setup(); + + gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser, "about:blank"); + BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser).then(() => { + BrowserTestUtils.startLoadingURIString(gBrowser, TESTROOT + "unsigned.xpi"); + }); +} + +function confirm_install(panel) { + is(panel.getAttribute("name"), "XPI Test", "Should have seen the name"); + return true; +} + +function install_ended(install, addon) { + return addon.uninstall(); +} + +function finish_test(count) { + is(count, 1, "1 Add-on should have been successfully installed"); + + gBrowser.removeCurrentTab(); + Harness.finish(); +} +// ---------------------------------------------------------------------------- diff --git a/toolkit/mozapps/extensions/test/xpinstall/bug540558.html b/toolkit/mozapps/extensions/test/xpinstall/bug540558.html new file mode 100644 index 0000000000..045e3e2d7c --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpinstall/bug540558.html @@ -0,0 +1,24 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> + +<html> + +<!-- This page tests that window.InstallTrigger.install works --> + +<head> +<title>InstallTrigger tests</title> +<script type="text/javascript"> +/* exported startInstall */ +function startInstall() { + window.InstallTrigger.install({ + "Unsigned XPI": "amosigned.xpi", + }); +} +</script> +</head> +<body onload="startInstall()"> +<p>InstallTrigger tests</p> +<p id="return"></p> +<p id="status"></p> +</body> +</html> diff --git a/toolkit/mozapps/extensions/test/xpinstall/bug638292.html b/toolkit/mozapps/extensions/test/xpinstall/bug638292.html new file mode 100644 index 0000000000..198207d4bf --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpinstall/bug638292.html @@ -0,0 +1,17 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> + +<html> + +<!-- This page tests InstallTrigger is defined in a new window --> + +<head> +<title>InstallTrigger tests</title> +</head> +<body> +<p>InstallTrigger tests</p> +<p><a id="link1" target="_blank" href="enabled.html">Open window with target</a></p> +<p><a id="link2" onclick="window.open(this.href); return false" href="enabled.html">Open window with JS</a></p> +<p><a id="link3" href="enabled.html">Open window with middle-click</a></p> +</body> +</html> diff --git a/toolkit/mozapps/extensions/test/xpinstall/bug645699.html b/toolkit/mozapps/extensions/test/xpinstall/bug645699.html new file mode 100644 index 0000000000..d993ad070d --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpinstall/bug645699.html @@ -0,0 +1,32 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> + +<html> + +<head> +<title>InstallTrigger tests</title> +<script type="text/javascript"> +/* globals InstallTrigger */ +/* exported startInstall */ +function startInstall() { + var whiteUrl = "https://example.org/"; + + try { + Object.defineProperty(window, "location", { value: { href: whiteUrl } }); + throw new Error("Object.defineProperty(window, 'location', ...) should have thrown"); + } catch (exc) { + if (!(exc instanceof TypeError)) + throw exc; + } + Object.defineProperty(document, "documentURIObject", { spec: { href: whiteUrl } }); + + InstallTrigger.install({ + "Unsigned XPI": "http://example.com/browser/toolkit/mozapps/extensions/test/xpinstall/amosigned.xpi", + }); +} +</script> +</head> +<body onload="startInstall()"> +<p>InstallTrigger tests</p> +</body> +</html> diff --git a/toolkit/mozapps/extensions/test/xpinstall/cookieRedirect.sjs b/toolkit/mozapps/extensions/test/xpinstall/cookieRedirect.sjs new file mode 100644 index 0000000000..5ddc6f9bd4 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpinstall/cookieRedirect.sjs @@ -0,0 +1,23 @@ +// Simple script redirects to the query part of the uri if the cookie "xpinstall" +// has the value "true", otherwise gives a 500 error. + +function handleRequest(request, response) { + let cookie = null; + if (request.hasHeader("Cookie")) { + let cookies = request.getHeader("Cookie").split(";"); + for (let i = 0; i < cookies.length; i++) { + if (cookies[i].substring(0, 10) == "xpinstall=") { + cookie = cookies[i].substring(10); + } + } + } + + if (cookie == "true") { + response.setStatusLine(request.httpVersion, 302, "Found"); + response.setHeader("Location", request.queryString); + response.write("See " + request.queryString); + } else { + response.setStatusLine(request.httpVersion, 500, "Internal Server Error"); + response.write("Invalid request"); + } +} diff --git a/toolkit/mozapps/extensions/test/xpinstall/corrupt.xpi b/toolkit/mozapps/extensions/test/xpinstall/corrupt.xpi new file mode 100644 index 0000000000..35d7bd5e5d --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpinstall/corrupt.xpi @@ -0,0 +1 @@ +This is a corrupt zip file diff --git a/toolkit/mozapps/extensions/test/xpinstall/empty.xpi b/toolkit/mozapps/extensions/test/xpinstall/empty.xpi Binary files differnew file mode 100644 index 0000000000..74ed2b8174 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpinstall/empty.xpi diff --git a/toolkit/mozapps/extensions/test/xpinstall/enabled.html b/toolkit/mozapps/extensions/test/xpinstall/enabled.html new file mode 100644 index 0000000000..dea8a59036 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpinstall/enabled.html @@ -0,0 +1,25 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> + +<html> + +<!-- This page will test if InstallTrigger seems to be enabled --> + +<head> +<title>InstallTrigger tests</title> +<script type="text/javascript"> +/* globals InstallTrigger */ +/* exported init */ +function init() { + document.getElementById("enabled").textContent = InstallTrigger.enabled() ? "true" : "false"; + dump("Sending PageLoaded\n"); + var event = new CustomEvent("PageLoaded"); + window.dispatchEvent(event); +} +</script> +</head> +<body onload="init()"> +<p>InstallTrigger tests</p> +<p id="enabled"></p> +</body> +</html> diff --git a/toolkit/mozapps/extensions/test/xpinstall/hashRedirect.sjs b/toolkit/mozapps/extensions/test/xpinstall/hashRedirect.sjs new file mode 100644 index 0000000000..8137f9e58a --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpinstall/hashRedirect.sjs @@ -0,0 +1,14 @@ +// Simple script redirects takes the query part of te request and splits it on +// the | character. Anything before is included as the X-Target-Digest header +// the latter part is used as the url to redirect to + +function handleRequest(request, response) { + let pos = request.queryString.indexOf("|"); + let header = request.queryString.substring(0, pos); + let url = request.queryString.substring(pos + 1); + + response.setStatusLine(request.httpVersion, 302, "Found"); + response.setHeader("X-Target-Digest", header); + response.setHeader("Location", url); + response.write("See " + url); +} diff --git a/toolkit/mozapps/extensions/test/xpinstall/head.js b/toolkit/mozapps/extensions/test/xpinstall/head.js new file mode 100644 index 0000000000..33ac33c830 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpinstall/head.js @@ -0,0 +1,568 @@ +/* eslint no-unused-vars: ["error", {vars: "local", args: "none"}] */ + +const { PermissionTestUtils } = ChromeUtils.importESModule( + "resource://testing-common/PermissionTestUtils.sys.mjs" +); +const { PromptTestUtils } = ChromeUtils.importESModule( + "resource://testing-common/PromptTestUtils.sys.mjs" +); + +const RELATIVE_DIR = "toolkit/mozapps/extensions/test/xpinstall/"; + +const TESTROOT = "http://example.com/browser/" + RELATIVE_DIR; +const TESTROOT2 = "http://example.org/browser/" + RELATIVE_DIR; +const PROMPT_URL = "chrome://global/content/commonDialog.xhtml"; +const ADDONS_URL = "chrome://mozapps/content/extensions/aboutaddons.html"; +const PREF_LOGGING_ENABLED = "extensions.logging.enabled"; +const PREF_INSTALL_REQUIREBUILTINCERTS = + "extensions.install.requireBuiltInCerts"; +const PREF_INSTALL_REQUIRESECUREORIGIN = + "extensions.install.requireSecureOrigin"; +const CHROME_NAME = "mochikit"; + +function getChromeRoot(path) { + if (path === undefined) { + return "chrome://" + CHROME_NAME + "/content/browser/" + RELATIVE_DIR; + } + return getRootDirectory(path); +} + +function extractChromeRoot(path) { + var chromeRootPath = getChromeRoot(path); + var jar = getJar(chromeRootPath); + if (jar) { + var tmpdir = extractJarToTmp(jar); + return "file://" + tmpdir.path + "/"; + } + return chromeRootPath; +} + +function setInstallTriggerPrefs() { + Services.prefs.setBoolPref("extensions.InstallTrigger.enabled", true); + Services.prefs.setBoolPref("extensions.InstallTriggerImpl.enabled", true); + // Relax the user input requirements while running tests that call this test helper. + Services.prefs.setBoolPref("xpinstall.userActivation.required", false); + registerCleanupFunction(clearInstallTriggerPrefs); +} + +function clearInstallTriggerPrefs() { + Services.prefs.clearUserPref("extensions.InstallTrigger.enabled"); + Services.prefs.clearUserPref("extensions.InstallTriggerImpl.enabled"); + Services.prefs.clearUserPref("xpinstall.userActivation.required"); +} + +/** + * This is a test harness designed to handle responding to UI during the process + * of installing an XPI. A test can set callbacks to hear about specific parts + * of the sequence. + * Before use setup must be called and finish must be called afterwards. + */ +var Harness = { + // If set then the callback is called when an install is attempted and + // software installation is disabled. + installDisabledCallback: null, + // If set then the callback is called when an install is attempted and + // then canceled. + installCancelledCallback: null, + // If set then the callback will be called when an install's origin is blocked. + installOriginBlockedCallback: null, + // If set then the callback will be called when an install is blocked by the + // whitelist. The callback should return true to continue with the install + // anyway. + installBlockedCallback: null, + // If set will be called in the event of authentication being needed to get + // the xpi. Should return a 2 element array of username and password, or + // null to not authenticate. + authenticationCallback: null, + // If set this will be called to allow checking the contents of the xpinstall + // confirmation dialog. The callback should return true to continue the install. + installConfirmCallback: null, + // If set will be called when downloading of an item has begun. + downloadStartedCallback: null, + // If set will be called during the download of an item. + downloadProgressCallback: null, + // If set will be called when an xpi fails to download. + downloadFailedCallback: null, + // If set will be called when an xpi download is cancelled. + downloadCancelledCallback: null, + // If set will be called when downloading of an item has ended. + downloadEndedCallback: null, + // If set will be called when installation by the extension manager of an xpi + // item starts + installStartedCallback: null, + // If set will be called when an xpi fails to install. + installFailedCallback: null, + // If set will be called when each xpi item to be installed completes + // installation. + installEndedCallback: null, + // If set will be called when all triggered items are installed or the install + // is canceled. + installsCompletedCallback: null, + // If set the harness will wait for this DOM event before calling + // installsCompletedCallback + finalContentEvent: null, + + waitingForEvent: false, + pendingCount: null, + installCount: null, + runningInstalls: null, + + waitingForFinish: false, + + // A unique value to return from the installConfirmCallback to indicate that + // the install UI shouldn't be closed automatically + leaveOpen: {}, + + // Setup and tear down functions + setup(win = window) { + if (!this.waitingForFinish) { + waitForExplicitFinish(); + this.waitingForFinish = true; + + Services.prefs.setBoolPref(PREF_INSTALL_REQUIRESECUREORIGIN, false); + Services.prefs.setBoolPref(PREF_LOGGING_ENABLED, true); + Services.prefs.setBoolPref( + "network.cookieJarSettings.unblocked_for_testing", + true + ); + + Services.obs.addObserver(this, "addon-install-started"); + Services.obs.addObserver(this, "addon-install-disabled"); + Services.obs.addObserver(this, "addon-install-origin-blocked"); + Services.obs.addObserver(this, "addon-install-blocked"); + Services.obs.addObserver(this, "addon-install-failed"); + + // For browser_auth tests which trigger auth dialogs. + Services.obs.addObserver(this, "tabmodal-dialog-loaded"); + Services.obs.addObserver(this, "common-dialog-loaded"); + + this._boundWin = Cu.getWeakReference(win); // need this so our addon manager listener knows which window to use. + AddonManager.addInstallListener(this); + AddonManager.addAddonListener(this); + + win.addEventListener("popupshown", this); + win.PanelUI.notificationPanel.addEventListener("popupshown", this); + + var self = this; + registerCleanupFunction(async function () { + Services.prefs.clearUserPref(PREF_LOGGING_ENABLED); + Services.prefs.clearUserPref(PREF_INSTALL_REQUIRESECUREORIGIN); + Services.prefs.clearUserPref( + "network.cookieJarSettings.unblocked_for_testing" + ); + + Services.obs.removeObserver(self, "addon-install-started"); + Services.obs.removeObserver(self, "addon-install-disabled"); + Services.obs.removeObserver(self, "addon-install-origin-blocked"); + Services.obs.removeObserver(self, "addon-install-blocked"); + Services.obs.removeObserver(self, "addon-install-failed"); + + Services.obs.removeObserver(self, "tabmodal-dialog-loaded"); + Services.obs.removeObserver(self, "common-dialog-loaded"); + + AddonManager.removeInstallListener(self); + AddonManager.removeAddonListener(self); + + win.removeEventListener("popupshown", self); + win.PanelUI.notificationPanel.removeEventListener("popupshown", self); + win = null; + + let aInstalls = await AddonManager.getAllInstalls(); + is( + aInstalls.length, + 0, + "Should be no active installs at the end of the test" + ); + await Promise.all( + aInstalls.map(async function (aInstall) { + info( + "Install for " + + aInstall.sourceURI + + " is in state " + + aInstall.state + ); + if (aInstall.state == AddonManager.STATE_INSTALLED) { + await aInstall.addon.uninstall(); + } else { + aInstall.cancel(); + } + }) + ); + }); + } + + this.installCount = 0; + this.pendingCount = 0; + this.runningInstalls = []; + }, + + finish(win = window) { + // Some tests using this harness somehow finish leaving + // the addon-installed panel open. hiding here addresses + // that which fixes the rest of the tests. Since no test + // here cares about this panel, we just need it to close. + win.PanelUI.notificationPanel.hidePopup(); + win.AppMenuNotifications.removeNotification("addon-installed"); + delete this._boundWin; + finish(); + }, + + endTest() { + let callback = this.installsCompletedCallback; + let count = this.installCount; + + is(this.runningInstalls.length, 0, "Should be no running installs left"); + this.runningInstalls.forEach(function (aInstall) { + info( + "Install for " + aInstall.sourceURI + " is in state " + aInstall.state + ); + }); + + this.installOriginBlockedCallback = null; + this.installBlockedCallback = null; + this.authenticationCallback = null; + this.installConfirmCallback = null; + this.downloadStartedCallback = null; + this.downloadProgressCallback = null; + this.downloadCancelledCallback = null; + this.downloadFailedCallback = null; + this.downloadEndedCallback = null; + this.installStartedCallback = null; + this.installFailedCallback = null; + this.installEndedCallback = null; + this.installsCompletedCallback = null; + this.runningInstalls = null; + + if (callback) { + executeSoon(() => callback(count)); + } + }, + + promptReady(dialog) { + let promptType = dialog.args.promptType; + + switch (promptType) { + case "alert": + case "alertCheck": + case "confirmCheck": + case "confirm": + case "confirmEx": + PromptTestUtils.handlePrompt(dialog, { buttonNumClick: 0 }); + break; + case "promptUserAndPass": + // This is a login dialog, hopefully an authentication prompt + // for the xpi. + if (this.authenticationCallback) { + var auth = this.authenticationCallback(); + if (auth && auth.length == 2) { + PromptTestUtils.handlePrompt(dialog, { + loginInput: auth[0], + passwordInput: auth[1], + buttonNumClick: 0, + }); + } else { + PromptTestUtils.handlePrompt(dialog, { buttonNumClick: 1 }); + } + } else { + PromptTestUtils.handlePrompt(dialog, { buttonNumClick: 1 }); + } + break; + default: + ok(false, "prompt type " + promptType + " not handled in test."); + break; + } + }, + + popupReady(panel) { + if (this.installBlockedCallback) { + ok(false, "Should have been blocked by the whitelist"); + } + this.pendingCount++; + + // If there is a confirm callback then its return status determines whether + // to install the items or not. If not the test is over. + let result = true; + if (this.installConfirmCallback) { + result = this.installConfirmCallback(panel); + if (result === this.leaveOpen) { + return; + } + } + + const panelEl = panel.closest("panel"); + const panelState = panelEl.state; + + const clickButton = () => { + info(`Clicking ${result ? "primary" : "secondary"} panel button`); + Assert.equal( + panelEl.state, + "open", + "Expect panel state to be open when clicking panel buttons" + ); + if (!result) { + panel.secondaryButton.click(); + } else { + panel.button.click(); + } + }; + + if (panelState === "showing") { + info( + "panel is still showing, wait for 'popup-shown' topic to be notified" + ); + BrowserUtils.promiseObserved( + "popup-shown", + shownPanel => shownPanel === panelEl + ).then(clickButton); + } else { + clickButton(); + } + }, + + handleEvent(event) { + if (event.type === "popupshown") { + if (event.target == event.view.PanelUI.notificationPanel) { + event.view.PanelUI.notificationPanel.hidePopup(); + } else if (event.target.firstElementChild) { + let popupId = event.target.firstElementChild.getAttribute("popupid"); + if (popupId === "addon-webext-permissions") { + this.popupReady(event.target.firstElementChild); + } else if (popupId === "addon-install-failed") { + event.target.firstElementChild.button.click(); + } + } + } + }, + + // Install blocked handling + + installDisabled(installInfo) { + ok( + !!this.installDisabledCallback, + "Installation shouldn't have been disabled" + ); + if (this.installDisabledCallback) { + this.installDisabledCallback(installInfo); + } + this.expectingCancelled = true; + this.expectingCancelled = false; + this.endTest(); + }, + + installCancelled(installInfo) { + if (this.expectingCancelled) { + return; + } + + ok( + !!this.installCancelledCallback, + "Installation shouldn't have been cancelled" + ); + if (this.installCancelledCallback) { + this.installCancelledCallback(installInfo); + } + this.endTest(); + }, + + installOriginBlocked(installInfo) { + ok(!!this.installOriginBlockedCallback, "Shouldn't have been blocked"); + if (this.installOriginBlockedCallback) { + this.installOriginBlockedCallback(installInfo); + } + this.endTest(); + }, + + installBlocked(installInfo) { + ok( + !!this.installBlockedCallback, + "Shouldn't have been blocked by the whitelist" + ); + if ( + this.installBlockedCallback && + this.installBlockedCallback(installInfo) + ) { + this.installBlockedCallback = null; + installInfo.install(); + } else { + this.expectingCancelled = true; + installInfo.installs.forEach(function (install) { + install.cancel(); + }); + this.expectingCancelled = false; + this.endTest(); + } + }, + + // Addon Install Listener + + onNewInstall(install) { + this.runningInstalls.push(install); + + if (this.finalContentEvent && !this.waitingForEvent) { + this.waitingForEvent = true; + info("Waiting for " + this.finalContentEvent); + BrowserTestUtils.waitForContentEvent( + this._boundWin.get().gBrowser.selectedBrowser, + this.finalContentEvent, + true, + null, + true + ).then(() => { + info("Saw " + this.finalContentEvent + "," + this.waitingForEvent); + this.waitingForEvent = false; + if (this.pendingCount == 0) { + this.endTest(); + } + }); + } + }, + + onDownloadStarted(install) { + this.pendingCount++; + if (this.downloadStartedCallback) { + this.downloadStartedCallback(install); + } + }, + + onDownloadProgress(install) { + if (this.downloadProgressCallback) { + this.downloadProgressCallback(install); + } + }, + + onDownloadEnded(install) { + if (this.downloadEndedCallback) { + this.downloadEndedCallback(install); + } + }, + + onDownloadCancelled(install) { + isnot( + this.runningInstalls.indexOf(install), + -1, + "Should only see cancelations for started installs" + ); + this.runningInstalls.splice(this.runningInstalls.indexOf(install), 1); + + if ( + this.downloadCancelledCallback && + this.downloadCancelledCallback(install) === false + ) { + return; + } + this.checkTestEnded(); + }, + + onDownloadFailed(install) { + if (this.downloadFailedCallback) { + this.downloadFailedCallback(install); + } + this.checkTestEnded(); + }, + + onInstallStarted(install) { + if (this.installStartedCallback) { + this.installStartedCallback(install); + } + }, + + async onInstallEnded(install, addon) { + this.installCount++; + if (this.installEndedCallback) { + await this.installEndedCallback(install, addon); + } + this.checkTestEnded(); + }, + + onInstallFailed(install) { + if (this.installFailedCallback) { + this.installFailedCallback(install); + } + this.checkTestEnded(); + }, + + onUninstalled(addon) { + let idx = this.runningInstalls.findIndex(install => install.addon == addon); + if (idx != -1) { + this.runningInstalls.splice(idx, 1); + this.checkTestEnded(); + } + }, + + onInstallCancelled(install) { + // This is ugly. We have a bunch of tests that cancel installs + // but don't expect this event to be raised. + // For at least one test (browser_whitelist3.js), we used to generate + // onDownloadCancelled when the user cancelled the installation at the + // confirmation prompt. We're now generating onInstallCancelled instead + // of onDownloadCancelled but making this code unconditional breaks a + // bunch of other tests. Ugh. + let idx = this.runningInstalls.indexOf(install); + if (idx != -1) { + this.runningInstalls.splice(this.runningInstalls.indexOf(install), 1); + this.checkTestEnded(); + } + }, + + checkTestEnded() { + if (--this.pendingCount == 0 && !this.waitingForEvent) { + this.endTest(); + } + }, + + // nsIObserver + + observe(subject, topic, data) { + var installInfo = subject.wrappedJSObject; + switch (topic) { + case "addon-install-started": + is( + this.runningInstalls.length, + installInfo.installs.length, + "Should have seen the expected number of installs started" + ); + break; + case "addon-install-disabled": + this.installDisabled(installInfo); + break; + case "addon-install-cancelled": + this.installCancelled(installInfo); + break; + case "addon-install-origin-blocked": + this.installOriginBlocked(installInfo); + break; + case "addon-install-blocked": + this.installBlocked(installInfo); + break; + case "addon-install-failed": + installInfo.installs.forEach(function (aInstall) { + isnot( + this.runningInstalls.indexOf(aInstall), + -1, + "Should only see failures for started installs" + ); + + ok( + aInstall.error != 0 || aInstall.addon.appDisabled, + "Failed installs should have an error or be appDisabled" + ); + + this.runningInstalls.splice( + this.runningInstalls.indexOf(aInstall), + 1 + ); + }, this); + break; + case "tabmodal-dialog-loaded": + let browser = subject.ownerGlobal.gBrowser.selectedBrowser; + let prompt = browser.tabModalPromptBox.getPrompt(subject); + this.promptReady(prompt.Dialog); + break; + case "common-dialog-loaded": + this.promptReady(subject.Dialog); + break; + } + }, + + QueryInterface: ChromeUtils.generateQI(["nsIObserver"]), +}; diff --git a/toolkit/mozapps/extensions/test/xpinstall/incompatible.xpi b/toolkit/mozapps/extensions/test/xpinstall/incompatible.xpi Binary files differnew file mode 100644 index 0000000000..de895fd1d9 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpinstall/incompatible.xpi diff --git a/toolkit/mozapps/extensions/test/xpinstall/installchrome.html b/toolkit/mozapps/extensions/test/xpinstall/installchrome.html new file mode 100644 index 0000000000..d9ff573ab5 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpinstall/installchrome.html @@ -0,0 +1,23 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> + +<html> + +<!-- This page will accept a url as the uri query and pass it to InstallTrigger.installChrome --> + +<head> +<title>InstallTrigger tests</title> +<script type="text/javascript"> +/* globals InstallTrigger */ +/* exported startInstall */ +function startInstall() { + InstallTrigger.installChrome(InstallTrigger.SKIN, + decodeURIComponent(document.location.search.substring(1)), + "test"); +} +</script> +</head> +<body onload="startInstall()"> +<p>InstallTrigger tests</p> +</body> +</html> diff --git a/toolkit/mozapps/extensions/test/xpinstall/installtrigger.html b/toolkit/mozapps/extensions/test/xpinstall/installtrigger.html new file mode 100644 index 0000000000..d68e4acbe3 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpinstall/installtrigger.html @@ -0,0 +1,57 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> + +<html> + +<!-- This page will accept some json as the uri query and pass it to InstallTrigger.install --> + +<head> +<title>InstallTrigger tests</title> +<script type="text/javascript"> +/* globals InstallTrigger */ +/* exported startInstall */ +function installCallback(url, status) { + document.getElementById("status").textContent = status; + + dump("Sending InstallComplete\n"); + var event = new CustomEvent("InstallComplete"); + var target = window.parent ? window.parent : window; + target.dispatchEvent(event); +} + +function startInstall(viaWindowLoaded = false) { + var event = new CustomEvent("InstallTriggered"); + var text; + if (viaWindowLoaded) { + text = decodeURIComponent(document.location.search.substring(1)); + } else { + text = decodeURIComponent(document.location.search.substring("?manualStartInstall".length)); + } + var triggers = JSON.parse(text); + try { + document.getElementById("return").textContent = InstallTrigger.install(triggers, installCallback); + dump("Sending InstallTriggered\n"); + window.dispatchEvent(event); + } catch (e) { + document.getElementById("return").textContent = "exception"; + dump("Sending InstallTriggered\n"); + window.dispatchEvent(event); + if (viaWindowLoaded) { + throw e; + } + } +} + +window.onload = function () { + if (!document.location.search.startsWith("?manualStartInstall")) { + startInstall(true); + } +} +</script> +</head> +<body> +<p>InstallTrigger tests</p> +<p id="return"></p> +<p id="status"></p> +</body> +</html> diff --git a/toolkit/mozapps/extensions/test/xpinstall/installtrigger_frame.html b/toolkit/mozapps/extensions/test/xpinstall/installtrigger_frame.html new file mode 100644 index 0000000000..7e4bccab18 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpinstall/installtrigger_frame.html @@ -0,0 +1,30 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> + +<html> + +<!-- This page will accept some url as the uri query and load it in + an inner iframe, which will run InstallTrigger.install --> + +<head> +<title>InstallTrigger frame tests</title> +<script type="text/javascript"> +/* exported prepChild */ +function prepChild() { + // Pass our parameters over to the child + var child = window.frames[0]; + var url = decodeURIComponent(document.location.search.substr(1)); + child.location = url; +} +</script> +</head> +<body onload="prepChild()"> + +<iframe src="about:blank"> +</iframe> + +<p>InstallTrigger tests</p> +<p id="return"></p> +<p id="status"></p> +</body> +</html> diff --git a/toolkit/mozapps/extensions/test/xpinstall/navigate.html b/toolkit/mozapps/extensions/test/xpinstall/navigate.html new file mode 100644 index 0000000000..96052009b9 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpinstall/navigate.html @@ -0,0 +1,25 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> + +<html> + +<!-- This page will accept some url as the uri query and navigate to it by + clicking a link --> + +<head> +<title>Navigation tests</title> +<script type="text/javascript"> +/* exported navigate */ +function navigate() { + var url = decodeURIComponent(document.location.search.substr(1)); + var link = document.getElementById("link"); + link.href = url; + link.click(); +} +</script> +</head> +<body onload="navigate()"> + +<p><a id="link">Test Link</a></p> +</body> +</html> diff --git a/toolkit/mozapps/extensions/test/xpinstall/recommended.xpi b/toolkit/mozapps/extensions/test/xpinstall/recommended.xpi Binary files differnew file mode 100644 index 0000000000..e180decfc5 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpinstall/recommended.xpi diff --git a/toolkit/mozapps/extensions/test/xpinstall/redirect.sjs b/toolkit/mozapps/extensions/test/xpinstall/redirect.sjs new file mode 100644 index 0000000000..14236ee821 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpinstall/redirect.sjs @@ -0,0 +1,39 @@ +// Script has two modes based on the query string. If the mode is "setup" then +// parameters from the query string configure the redirection. If the mode is +// "redirect" then a redirect is returned + +function handleRequest(request, response) { + let parts = request.queryString.split("&"); + let settings = {}; + + parts.forEach(function (aString) { + let [k, v] = aString.split("="); + settings[k] = decodeURIComponent(v); + }); + + if (settings.mode == "setup") { + delete settings.mode; + + // Object states must be an nsISupports + var state = { + settings, + QueryInterface: ChromeUtils.generateQI([]), + }; + state.wrappedJSObject = state; + + setObjectState("xpinstall-redirect-settings", state); + response.setStatusLine(request.httpVersion, 200, "Ok"); + response.setHeader("Content-Type", "text/plain"); + response.write("Setup complete"); + } else if (settings.mode == "redirect") { + getObjectState("xpinstall-redirect-settings", function (aObject) { + settings = aObject.wrappedJSObject.settings; + }); + + response.setStatusLine(request.httpVersion, 302, "Found"); + for (var name in settings) { + response.setHeader(name, settings[name]); + } + response.write("Done"); + } +} diff --git a/toolkit/mozapps/extensions/test/xpinstall/restartless.xpi b/toolkit/mozapps/extensions/test/xpinstall/restartless.xpi Binary files differnew file mode 100644 index 0000000000..9fee8f60b1 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpinstall/restartless.xpi diff --git a/toolkit/mozapps/extensions/test/xpinstall/slowinstall.sjs b/toolkit/mozapps/extensions/test/xpinstall/slowinstall.sjs new file mode 100644 index 0000000000..e2a889c329 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpinstall/slowinstall.sjs @@ -0,0 +1,103 @@ +// In an SJS file we need to get NetUtil ourselves, despite +// what eslint might think applies for browser tests. +// eslint-disable-next-line mozilla/no-redeclare-with-import-autofix +let { NetUtil } = ChromeUtils.importESModule( + "resource://gre/modules/NetUtil.sys.mjs" +); + +const RELATIVE_PATH = "browser/toolkit/mozapps/extensions/test/xpinstall"; +const NOTIFICATION_TOPIC = "slowinstall-complete"; + +/** + * Helper function to create a JS object representing the url parameters from + * the request's queryString. + * + * @param aQueryString + * The request's query string. + * @return A JS object representing the url parameters from the request's + * queryString. + */ +function parseQueryString(aQueryString) { + var paramArray = aQueryString.split("&"); + var regex = /^([^=]+)=(.*)$/; + var params = {}; + for (var i = 0, sz = paramArray.length; i < sz; i++) { + var match = regex.exec(paramArray[i]); + if (!match) { + throw new Error("Bad parameter in queryString! '" + paramArray[i] + "'"); + } + params[decodeURIComponent(match[1])] = decodeURIComponent(match[2]); + } + + return params; +} + +function handleRequest(aRequest, aResponse) { + let id = +getState("ID"); + setState("ID", "" + (id + 1)); + + function LOG(str) { + dump("slowinstall.sjs[" + id + "]: " + str + "\n"); + } + + aResponse.setStatusLine(aRequest.httpVersion, 200, "OK"); + + var params = {}; + if (aRequest.queryString) { + params = parseQueryString(aRequest.queryString); + } + + if (params.file) { + let xpiFile = ""; + + function complete_download() { + LOG("Completing download"); + + try { + // Doesn't seem to be a sane way to read using IOUtils and write to an + // nsIOutputStream so here we are. + let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile); + file.initWithPath(xpiFile); + let stream = Cc[ + "@mozilla.org/network/file-input-stream;1" + ].createInstance(Ci.nsIFileInputStream); + stream.init(file, -1, -1, stream.DEFER_OPEN + stream.CLOSE_ON_EOF); + + NetUtil.asyncCopy(stream, aResponse.bodyOutputStream, () => { + LOG("Download complete"); + aResponse.finish(); + }); + } catch (e) { + LOG("Exception " + e); + } + } + + let waitForComplete = new Promise(resolve => { + function complete() { + Services.obs.removeObserver(complete, NOTIFICATION_TOPIC); + resolve(); + } + + Services.obs.addObserver(complete, NOTIFICATION_TOPIC); + }); + + aResponse.processAsync(); + + const dir = Services.dirsvc.get("CurWorkD", Ci.nsIFile).path; + xpiFile = PathUtils.join(dir, ...RELATIVE_PATH.split("/"), params.file); + LOG("Starting slow download of " + xpiFile); + + IOUtils.stat(xpiFile).then(info => { + aResponse.setHeader("Content-Type", "binary/octet-stream"); + aResponse.setHeader("Content-Length", info.size.toString()); + + LOG("Download paused"); + waitForComplete.then(complete_download); + }); + } else if (params.continue) { + dump( + "slowinstall.sjs: Received signal to complete all current downloads.\n" + ); + Services.obs.notifyObservers(null, NOTIFICATION_TOPIC); + } +} diff --git a/toolkit/mozapps/extensions/test/xpinstall/startsoftwareupdate.html b/toolkit/mozapps/extensions/test/xpinstall/startsoftwareupdate.html new file mode 100644 index 0000000000..83792ebdb2 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpinstall/startsoftwareupdate.html @@ -0,0 +1,21 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> + +<html> + +<!-- This page will accept a url as the uri query and pass it to InstallTrigger.startSoftwareUpdate --> + +<head> +<title>InstallTrigger tests</title> +<script type="text/javascript"> +/* globals InstallTrigger */ +/* exported startInstall */ +function startInstall() { + InstallTrigger.startSoftwareUpdate(decodeURIComponent(document.location.search.substring(1))); +} +</script> +</head> +<body onload="startInstall()"> +<p>InstallTrigger tests</p> +</body> +</html> diff --git a/toolkit/mozapps/extensions/test/xpinstall/triggerredirect.html b/toolkit/mozapps/extensions/test/xpinstall/triggerredirect.html new file mode 100644 index 0000000000..1b098d6948 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpinstall/triggerredirect.html @@ -0,0 +1,37 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> + +<html> + +<!-- This page will attempt an install and then try to load a new page in the tab --> + +<head> +<title>InstallTrigger tests</title> +<script type="text/javascript"> +/* globals InstallTrigger */ +/* exported startInstall */ +function installCallback(url, status) { + document.location = "#foo"; + + dump("Sending InstallComplete\n"); + var event = new CustomEvent("InstallComplete"); + window.dispatchEvent(event); +} + +function startInstall() { + InstallTrigger.install({ + "Unsigned XPI": { + URL: "amosigned.xpi", + IconURL: "icon.png", + toString() { return this.URL; }, + }, + }, installCallback); +} +</script> +</head> +<body onload="startInstall()"> +<p>InstallTrigger tests</p> +<p id="return"></p> +<p id="status"></p> +</body> +</html> diff --git a/toolkit/mozapps/extensions/test/xpinstall/unsigned.xpi b/toolkit/mozapps/extensions/test/xpinstall/unsigned.xpi Binary files differnew file mode 100644 index 0000000000..95f99a748f --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpinstall/unsigned.xpi diff --git a/toolkit/mozapps/extensions/test/xpinstall/unsigned_mv3.xpi b/toolkit/mozapps/extensions/test/xpinstall/unsigned_mv3.xpi Binary files differnew file mode 100644 index 0000000000..7ef5534f45 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpinstall/unsigned_mv3.xpi diff --git a/toolkit/mozapps/extensions/test/xpinstall/webmidi_permission.xpi b/toolkit/mozapps/extensions/test/xpinstall/webmidi_permission.xpi Binary files differnew file mode 100644 index 0000000000..9a2effdd0f --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpinstall/webmidi_permission.xpi |