summaryrefslogtreecommitdiffstats
path: root/toolkit/mozapps/extensions/test/xpinstall
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/mozapps/extensions/test/xpinstall')
-rw-r--r--toolkit/mozapps/extensions/test/xpinstall/amosigned.xpibin0 -> 4287 bytes
-rw-r--r--toolkit/mozapps/extensions/test/xpinstall/authRedirect.sjs21
-rw-r--r--toolkit/mozapps/extensions/test/xpinstall/browser.ini122
-rw-r--r--toolkit/mozapps/extensions/test/xpinstall/browser_amosigned_trigger.js86
-rw-r--r--toolkit/mozapps/extensions/test/xpinstall/browser_amosigned_trigger_iframe.js77
-rw-r--r--toolkit/mozapps/extensions/test/xpinstall/browser_amosigned_url.js60
-rw-r--r--toolkit/mozapps/extensions/test/xpinstall/browser_auth.js68
-rw-r--r--toolkit/mozapps/extensions/test/xpinstall/browser_auth2.js73
-rw-r--r--toolkit/mozapps/extensions/test/xpinstall/browser_auth3.js72
-rw-r--r--toolkit/mozapps/extensions/test/xpinstall/browser_auth4.js71
-rw-r--r--toolkit/mozapps/extensions/test/xpinstall/browser_badargs.js49
-rw-r--r--toolkit/mozapps/extensions/test/xpinstall/browser_badargs2.js55
-rw-r--r--toolkit/mozapps/extensions/test/xpinstall/browser_badhash.js46
-rw-r--r--toolkit/mozapps/extensions/test/xpinstall/browser_badhashtype.js46
-rw-r--r--toolkit/mozapps/extensions/test/xpinstall/browser_block_fullscreen_prompt.js125
-rw-r--r--toolkit/mozapps/extensions/test/xpinstall/browser_bug540558.js31
-rw-r--r--toolkit/mozapps/extensions/test/xpinstall/browser_bug611242.js31
-rw-r--r--toolkit/mozapps/extensions/test/xpinstall/browser_bug638292.js51
-rw-r--r--toolkit/mozapps/extensions/test/xpinstall/browser_bug645699.js69
-rw-r--r--toolkit/mozapps/extensions/test/xpinstall/browser_bug645699_postDownload.js55
-rw-r--r--toolkit/mozapps/extensions/test/xpinstall/browser_bug672485.js63
-rw-r--r--toolkit/mozapps/extensions/test/xpinstall/browser_containers.js116
-rw-r--r--toolkit/mozapps/extensions/test/xpinstall/browser_cookies.js42
-rw-r--r--toolkit/mozapps/extensions/test/xpinstall/browser_cookies2.js64
-rw-r--r--toolkit/mozapps/extensions/test/xpinstall/browser_cookies3.js68
-rw-r--r--toolkit/mozapps/extensions/test/xpinstall/browser_cookies4.js68
-rw-r--r--toolkit/mozapps/extensions/test/xpinstall/browser_corrupt.js53
-rw-r--r--toolkit/mozapps/extensions/test/xpinstall/browser_datauri.js79
-rw-r--r--toolkit/mozapps/extensions/test/xpinstall/browser_doorhanger_installs.js1666
-rw-r--r--toolkit/mozapps/extensions/test/xpinstall/browser_empty.js39
-rw-r--r--toolkit/mozapps/extensions/test/xpinstall/browser_enabled.js103
-rw-r--r--toolkit/mozapps/extensions/test/xpinstall/browser_hash.js47
-rw-r--r--toolkit/mozapps/extensions/test/xpinstall/browser_hash2.js47
-rw-r--r--toolkit/mozapps/extensions/test/xpinstall/browser_httphash.js55
-rw-r--r--toolkit/mozapps/extensions/test/xpinstall/browser_httphash2.js52
-rw-r--r--toolkit/mozapps/extensions/test/xpinstall/browser_httphash3.js52
-rw-r--r--toolkit/mozapps/extensions/test/xpinstall/browser_httphash4.js49
-rw-r--r--toolkit/mozapps/extensions/test/xpinstall/browser_httphash5.js53
-rw-r--r--toolkit/mozapps/extensions/test/xpinstall/browser_httphash6.js107
-rw-r--r--toolkit/mozapps/extensions/test/xpinstall/browser_installchrome.js36
-rw-r--r--toolkit/mozapps/extensions/test/xpinstall/browser_localfile.js42
-rw-r--r--toolkit/mozapps/extensions/test/xpinstall/browser_localfile2.js60
-rw-r--r--toolkit/mozapps/extensions/test/xpinstall/browser_localfile3.js42
-rw-r--r--toolkit/mozapps/extensions/test/xpinstall/browser_localfile4.js55
-rw-r--r--toolkit/mozapps/extensions/test/xpinstall/browser_localfile4_postDownload.js54
-rw-r--r--toolkit/mozapps/extensions/test/xpinstall/browser_newwindow.js89
-rw-r--r--toolkit/mozapps/extensions/test/xpinstall/browser_offline.js79
-rw-r--r--toolkit/mozapps/extensions/test/xpinstall/browser_privatebrowsing.js133
-rw-r--r--toolkit/mozapps/extensions/test/xpinstall/browser_relative.js67
-rw-r--r--toolkit/mozapps/extensions/test/xpinstall/browser_required_useractivation.js156
-rw-r--r--toolkit/mozapps/extensions/test/xpinstall/browser_signed_url.js29
-rw-r--r--toolkit/mozapps/extensions/test/xpinstall/browser_softwareupdate.js36
-rw-r--r--toolkit/mozapps/extensions/test/xpinstall/browser_trigger_redirect.js45
-rw-r--r--toolkit/mozapps/extensions/test/xpinstall/browser_unsigned_trigger.js68
-rw-r--r--toolkit/mozapps/extensions/test/xpinstall/browser_unsigned_trigger_iframe.js77
-rw-r--r--toolkit/mozapps/extensions/test/xpinstall/browser_unsigned_trigger_xorigin.js58
-rw-r--r--toolkit/mozapps/extensions/test/xpinstall/browser_unsigned_url.js43
-rw-r--r--toolkit/mozapps/extensions/test/xpinstall/bug540558.html24
-rw-r--r--toolkit/mozapps/extensions/test/xpinstall/bug638292.html17
-rw-r--r--toolkit/mozapps/extensions/test/xpinstall/bug645699.html32
-rw-r--r--toolkit/mozapps/extensions/test/xpinstall/cookieRedirect.sjs23
-rw-r--r--toolkit/mozapps/extensions/test/xpinstall/corrupt.xpi1
-rw-r--r--toolkit/mozapps/extensions/test/xpinstall/empty.xpibin0 -> 197 bytes
-rw-r--r--toolkit/mozapps/extensions/test/xpinstall/enabled.html25
-rw-r--r--toolkit/mozapps/extensions/test/xpinstall/hashRedirect.sjs14
-rw-r--r--toolkit/mozapps/extensions/test/xpinstall/head.js575
-rw-r--r--toolkit/mozapps/extensions/test/xpinstall/incompatible.xpibin0 -> 428 bytes
-rw-r--r--toolkit/mozapps/extensions/test/xpinstall/installchrome.html23
-rw-r--r--toolkit/mozapps/extensions/test/xpinstall/installtrigger.html57
-rw-r--r--toolkit/mozapps/extensions/test/xpinstall/installtrigger_frame.html30
-rw-r--r--toolkit/mozapps/extensions/test/xpinstall/navigate.html25
-rw-r--r--toolkit/mozapps/extensions/test/xpinstall/recommended.xpibin0 -> 7884 bytes
-rw-r--r--toolkit/mozapps/extensions/test/xpinstall/redirect.sjs39
-rw-r--r--toolkit/mozapps/extensions/test/xpinstall/restartless.xpibin0 -> 4447 bytes
-rw-r--r--toolkit/mozapps/extensions/test/xpinstall/slowinstall.sjs100
-rw-r--r--toolkit/mozapps/extensions/test/xpinstall/startsoftwareupdate.html21
-rw-r--r--toolkit/mozapps/extensions/test/xpinstall/triggerredirect.html37
-rw-r--r--toolkit/mozapps/extensions/test/xpinstall/unsigned.xpibin0 -> 312 bytes
-rw-r--r--toolkit/mozapps/extensions/test/xpinstall/unsigned_mv3.xpibin0 -> 316 bytes
-rw-r--r--toolkit/mozapps/extensions/test/xpinstall/webmidi_permission.xpibin0 -> 7533 bytes
80 files changed, 6243 insertions, 0 deletions
diff --git a/toolkit/mozapps/extensions/test/xpinstall/amosigned.xpi b/toolkit/mozapps/extensions/test/xpinstall/amosigned.xpi
new file mode 100644
index 0000000000..f2948e6994
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/xpinstall/amosigned.xpi
Binary files differ
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.ini b/toolkit/mozapps/extensions/test/xpinstall/browser.ini
new file mode 100644
index 0000000000..7156e5e2c9
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/xpinstall/browser.ini
@@ -0,0 +1,122 @@
+[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
+skip-if =
+ os == "win" && os_version == "6.1" # Skip on Azure - frequent failure
+[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..57d7651898
--- /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.import(
+ "resource://testing-common/AddonTestUtils.jsm"
+);
+
+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.loadURI(
+ 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..c195aa01de
--- /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.loadURI(
+ 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..14dc448f68
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/xpinstall/browser_amosigned_url.js
@@ -0,0 +1,60 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+"use strict";
+
+const { AddonTestUtils } = ChromeUtils.import(
+ "resource://testing-common/AddonTestUtils.jsm"
+);
+
+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.loadURI(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..7c67cd75dd
--- /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.loadURI(
+ 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..add8bf9905
--- /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.loadURI(
+ 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..a753fe0ea5
--- /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.loadURI(
+ 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..85883fccf4
--- /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.loadURI(
+ 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..b413b18244
--- /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.loadURI(
+ 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..fb67664f99
--- /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.loadURI(
+ 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..e23d4f0951
--- /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.loadURI(
+ 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..ce3e547a5b
--- /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.loadURI(
+ 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..295afc7a8b
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/xpinstall/browser_block_fullscreen_prompt.js
@@ -0,0 +1,125 @@
+/* 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..0004d53713
--- /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.loadURI(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..94442d730c
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/xpinstall/browser_bug611242.js
@@ -0,0 +1,31 @@
+// ----------------------------------------------------------------------------
+// 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.loadURI(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..8587c5a396
--- /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..ecbf27ffbb
--- /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.loadURI(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..3fe8fb2e72
--- /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.loadURI(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..1cebdc0fd3
--- /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.loadURI(
+ 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..542af21e3e
--- /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.import(
+ "resource://testing-common/AddonTestUtils.jsm"
+);
+
+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.loadURI(
+ 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..658c80ea77
--- /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.loadURI(
+ 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..34b20cbd65
--- /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.loadURI(
+ 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..c6be96b609
--- /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.loadURI(
+ 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..07932a5563
--- /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.loadURI(
+ 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..ef7f404077
--- /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.loadURI(
+ 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..9930755e58
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/xpinstall/browser_datauri.js
@@ -0,0 +1,79 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+"use strict";
+
+const { AddonTestUtils } = ChromeUtils.import(
+ "resource://testing-common/AddonTestUtils.jsm"
+);
+
+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: [
+ ["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..5fb4b35169
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/xpinstall/browser_doorhanger_installs.js
@@ -0,0 +1,1666 @@
+/* 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.import(
+ "resource://testing-common/AddonTestUtils.jsm"
+);
+const { ExtensionPermissions } = ChromeUtils.import(
+ "resource://gre/modules/ExtensionPermissions.jsm"
+);
+const { Management } = ChromeUtils.import(
+ "resource://gre/modules/Extension.jsm"
+);
+
+const SECUREROOT =
+ "https://example.com/browser/toolkit/mozapps/extensions/test/xpinstall/";
+const PROGRESS_NOTIFICATION = "addon-progress";
+
+const CHROMEROOT = extractChromeRoot(gTestPath);
+
+AddonTestUtils.initMochitest(this);
+AddonTestUtils.hookAMTelemetryEvents();
+
+// Assert on the expected "addonsManager.action" telemetry events (and optional filter events to verify
+// by using a given actionType).
+function assertActionAMTelemetryEvent(
+ expectedActionEvents,
+ assertMessage,
+ { actionType } = {}
+) {
+ const events = AddonTestUtils.getAMTelemetryEvents().filter(
+ ({ method, extra }) => {
+ return (
+ method === "action" &&
+ (!actionType ? true : extra && extra.action === actionType)
+ );
+ }
+ );
+
+ Assert.deepEqual(events, expectedActionEvents, assertMessage);
+}
+
+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-install-restart") {
+ topic = "addon-install-complete";
+ } else if (topic == "addon-installed") {
+ topic = "webextension-install-notify";
+ }
+ return topic;
+}
+
+async function waitForProgressNotification(
+ aPanelOpen = false,
+ aExpectedCount = 1,
+ wantDisabled = true,
+ expectedAnchorID = "addons-notification-icon",
+ 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 = "addons-notification-icon",
+ 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"
+ );
+
+ assertActionAMTelemetryEvent(
+ [
+ {
+ method: "action",
+ object: "doorhanger",
+ value: "on",
+ extra: {
+ action: "privateBrowsingAllowed",
+ view: "postInstall",
+ addonId: addon.id,
+ type: "sitepermission-deprecated",
+ },
+ },
+ ],
+ "Expect telemetry events for privateBrowsingAllowed action",
+ { actionType: "privateBrowsingAllowed" }
+ );
+
+ 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.loadURI(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");
+
+ // Verify that the expected telemetry event has been collected for the extension allowed on
+ // PB windows from the "post install" notification doorhanger.
+ assertActionAMTelemetryEvent(
+ [
+ {
+ method: "action",
+ object: "doorhanger",
+ value: "on",
+ extra: {
+ action: "privateBrowsingAllowed",
+ view: "postInstall",
+ addonId: addon.id,
+ type: "extension",
+ },
+ },
+ ],
+ "Expect telemetry events for privateBrowsingAllowed action",
+ { actionType: "privateBrowsingAllowed" }
+ );
+
+ 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.loadURI(gBrowser, TESTROOT2 + "enabled.html");
+ await loadedPromise;
+
+ let progressPromise = waitForProgressNotification();
+ let notificationPromise = waitForNotification("addon-install-failed");
+ BrowserTestUtils.loadURI(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.loadURI(
+ 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];
+ // Close the notification
+ let anchor = document.getElementById("addons-notification-icon");
+ anchor.click();
+ // Reopen the notification
+ anchor.click();
+
+ ok(PopupNotifications.isPanelOpen, "Notification should still be open");
+ is(
+ PopupNotifications.panel.childNodes.length,
+ 1,
+ "Should be only one notification"
+ );
+ notification = panel.childNodes[0];
+ is(
+ notification.id,
+ "addon-progress-notification",
+ "Should have seen the progress notification"
+ );
+
+ // 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 new Promise(resolve => executeSoon(resolve));
+
+ 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");
+
+ // Verify that the expected telemetry event has been collected for the extension allowed on
+ // PB windows from the "post install" notification doorhanger.
+ assertActionAMTelemetryEvent(
+ [
+ {
+ method: "action",
+ object: "doorhanger",
+ value: "off",
+ extra: {
+ action: "privateBrowsingAllowed",
+ view: "postInstall",
+ addonId: addon.id,
+ type: "extension",
+ },
+ },
+ ],
+ "Expect telemetry events for privateBrowsingAllowed action",
+ { actionType: "privateBrowsingAllowed" }
+ );
+
+ 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");
+
+ // Verify that the expected telemetry event has been collected for the extension allowed on
+ // PB windows from the "post install" notification doorhanger.
+ assertActionAMTelemetryEvent(
+ [
+ {
+ method: "action",
+ object: "doorhanger",
+ value: "off",
+ extra: {
+ action: "privateBrowsingAllowed",
+ view: "postInstall",
+ addonId: addon.id,
+ type: "extension",
+ },
+ },
+ ],
+ "Expect telemetry events for privateBrowsingAllowed action",
+ { actionType: "privateBrowsingAllowed" }
+ );
+
+ await addon.uninstall();
+
+ await BrowserTestUtils.closeWindow(win);
+ },
+
+ async function test_blockedInstallDomain_with_unified_extensions() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["extensions.unifiedExtensions.enabled", true],
+ ["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: [
+ ["extensions.unifiedExtensions.enabled", true],
+ // 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: [
+ ["extensions.unifiedExtensions.enabled", true],
+ // 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],
+ // This is needed to allow most of the tests to pass no matter the value
+ // of this pref. In the future, we'll want to enable this pref by default
+ // and adjust the assertions on the anchor IDs.
+ ["extensions.unifiedExtensions.enabled", 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]();
+
+ // Check that no unexpected telemetry events for the privateBrowsingAllowed action has been
+ // collected while running the test case.
+ assertActionAMTelemetryEvent(
+ [],
+ "Expect no telemetry events for privateBrowsingAllowed actions",
+ { actionType: "privateBrowsingAllowed" }
+ );
+ }
+});
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..3279f59b87
--- /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.loadURI(
+ 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..a1863cc25c
--- /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..974dce97ec
--- /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.loadURI(
+ 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..dd887c76d5
--- /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.loadURI(
+ 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..d3f08c955c
--- /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.loadURI(
+ 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..aff4e24156
--- /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.loadURI(
+ 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..66c0339d97
--- /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.loadURI(
+ 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..70675387ec
--- /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.loadURI(
+ 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..89d5eae1a8
--- /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.loadURI(
+ 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..84c8a18409
--- /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.loadURI(
+ 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..8916428863
--- /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.loadURI(
+ 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..2e750ae80e
--- /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.loadURI(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..988704b952
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/xpinstall/browser_localfile2.js
@@ -0,0 +1,60 @@
+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..e9adfa462b
--- /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.loadURI(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..4746bf3417
--- /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.loadURI(
+ 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..31ef21d823
--- /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.loadURI(
+ 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..d105af55ad
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/xpinstall/browser_offline.js
@@ -0,0 +1,79 @@
+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.loadURI(
+ 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.loadURI(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..b02f88b299
--- /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.import(
+ "resource://testing-common/AddonTestUtils.jsm"
+);
+
+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.loadURI(
+ 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..cf08e8856d
--- /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.loadURI(
+ 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..064249f0cb
--- /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.import(
+ "resource://testing-common/AddonTestUtils.jsm"
+);
+
+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..08acce0f8e
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/xpinstall/browser_signed_url.js
@@ -0,0 +1,29 @@
+// ----------------------------------------------------------------------------
+// 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.loadURI(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..b5d0d23d34
--- /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.loadURI(
+ 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..84bf117b51
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/xpinstall/browser_trigger_redirect.js
@@ -0,0 +1,45 @@
+// ----------------------------------------------------------------------------
+// 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.loadURI(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..1216f84561
--- /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.loadURI(
+ 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..dc2c786aba
--- /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.loadURI(
+ 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..b15637514c
--- /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.loadURI(
+ 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..75840be7c6
--- /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.loadURI(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
new file mode 100644
index 0000000000..74ed2b8174
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/xpinstall/empty.xpi
Binary files differ
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..09c26d3bc8
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/xpinstall/head.js
@@ -0,0 +1,575 @@
+/* eslint no-unused-vars: ["error", {vars: "local", args: "none"}] */
+
+const { PermissionTestUtils } = ChromeUtils.import(
+ "resource://testing-common/PermissionTestUtils.jsm"
+);
+const { PromptTestUtils } = ChromeUtils.import(
+ "resource://testing-common/PromptTestUtils.jsm"
+);
+
+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");
+ Services.obs.addObserver(this, "addon-install-complete");
+
+ // 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, "addon-install-complete");
+
+ 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;
+ }
+ }
+
+ if (!result) {
+ panel.secondaryButton.click();
+ } else {
+ panel.button.click();
+ }
+ },
+
+ 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 "addon-install-complete":
+ installInfo.installs.forEach(function(aInstall) {
+ isnot(
+ this.runningInstalls.indexOf(aInstall),
+ -1,
+ "Should only see completed events for started installs"
+ );
+
+ is(aInstall.error, 0, "Completed installs should have no error");
+ ok(
+ !aInstall.appDisabled,
+ "Completed installs should not be appDisabled"
+ );
+
+ // Complete installs are either in the INSTALLED or CANCELLED state
+ // since the test may cancel installs the moment they complete.
+ ok(
+ aInstall.state == AddonManager.STATE_INSTALLED ||
+ aInstall.state == AddonManager.STATE_CANCELLED,
+ "Completed installs should be in the right state"
+ );
+
+ 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
new file mode 100644
index 0000000000..de895fd1d9
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/xpinstall/incompatible.xpi
Binary files differ
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
new file mode 100644
index 0000000000..e180decfc5
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/xpinstall/recommended.xpi
Binary files differ
diff --git a/toolkit/mozapps/extensions/test/xpinstall/redirect.sjs b/toolkit/mozapps/extensions/test/xpinstall/redirect.sjs
new file mode 100644
index 0000000000..0466b48586
--- /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
new file mode 100644
index 0000000000..9fee8f60b1
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/xpinstall/restartless.xpi
Binary files differ
diff --git a/toolkit/mozapps/extensions/test/xpinstall/slowinstall.sjs b/toolkit/mozapps/extensions/test/xpinstall/slowinstall.sjs
new file mode 100644
index 0000000000..119a259463
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/xpinstall/slowinstall.sjs
@@ -0,0 +1,100 @@
+let { NetUtil } = ChromeUtils.import("resource://gre/modules/NetUtil.jsm");
+
+Cu.importGlobalProperties(["IOUtils", "PathUtils"]);
+
+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
new file mode 100644
index 0000000000..95f99a748f
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/xpinstall/unsigned.xpi
Binary files differ
diff --git a/toolkit/mozapps/extensions/test/xpinstall/unsigned_mv3.xpi b/toolkit/mozapps/extensions/test/xpinstall/unsigned_mv3.xpi
new file mode 100644
index 0000000000..7ef5534f45
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/xpinstall/unsigned_mv3.xpi
Binary files differ
diff --git a/toolkit/mozapps/extensions/test/xpinstall/webmidi_permission.xpi b/toolkit/mozapps/extensions/test/xpinstall/webmidi_permission.xpi
new file mode 100644
index 0000000000..9a2effdd0f
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/xpinstall/webmidi_permission.xpi
Binary files differ