summaryrefslogtreecommitdiffstats
path: root/toolkit/mozapps/extensions/test/browser/browser_installtrigger_install.js
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/mozapps/extensions/test/browser/browser_installtrigger_install.js')
-rw-r--r--toolkit/mozapps/extensions/test/browser/browser_installtrigger_install.js350
1 files changed, 350 insertions, 0 deletions
diff --git a/toolkit/mozapps/extensions/test/browser/browser_installtrigger_install.js b/toolkit/mozapps/extensions/test/browser/browser_installtrigger_install.js
new file mode 100644
index 0000000000..cd2c30be7e
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/browser/browser_installtrigger_install.js
@@ -0,0 +1,350 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const { AddonTestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/AddonTestUtils.sys.mjs"
+);
+
+const { PermissionTestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/PermissionTestUtils.sys.mjs"
+);
+
+const XPI_URL = `${SECURE_TESTROOT}../xpinstall/amosigned.xpi`;
+const XPI_ADDON_ID = "amosigned-xpi@tests.mozilla.org";
+
+AddonTestUtils.initMochitest(this);
+
+AddonTestUtils.hookAMTelemetryEvents();
+
+add_setup(async function () {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["extensions.webapi.testing", true],
+ ["extensions.install.requireBuiltInCerts", false],
+ ["extensions.InstallTrigger.enabled", true],
+ ["extensions.InstallTriggerImpl.enabled", true],
+ // Relax the user input requirements while running this test.
+ ["xpinstall.userActivation.required", false],
+ ],
+ });
+
+ PermissionTestUtils.add(
+ "https://example.com/",
+ "install",
+ Services.perms.ALLOW_ACTION
+ );
+
+ registerCleanupFunction(async () => {
+ PermissionTestUtils.remove("https://example.com", "install");
+ await SpecialPowers.popPrefEnv();
+ });
+});
+
+async function testInstallTrigger(
+ msg,
+ tabURL,
+ contentFnArgs,
+ contentFn,
+ expectedTelemetryInfo,
+ expectBlockedOrigin
+) {
+ await BrowserTestUtils.withNewTab(tabURL, async browser => {
+ if (expectBlockedOrigin) {
+ const promiseOriginBlocked = TestUtils.topicObserved(
+ "addon-install-origin-blocked"
+ );
+ await SpecialPowers.spawn(browser, contentFnArgs, contentFn);
+ const [subject] = await promiseOriginBlocked;
+ const installId = subject.wrappedJSObject.installs[0].installId;
+
+ // Select all telemetry events related to the installId.
+ const telemetryEvents = AddonTestUtils.getAMTelemetryEvents().filter(
+ ev => {
+ return (
+ ev.method === "install" &&
+ ev.value === `${installId}` &&
+ ev.extra.step === "site_blocked"
+ );
+ }
+ );
+ ok(
+ !!telemetryEvents.length,
+ "Found telemetry events for the blocked install"
+ );
+
+ if (typeof expectedTelemetryInfo === "function") {
+ expectedTelemetryInfo(telemetryEvents);
+ } else {
+ const source = telemetryEvents[0]?.extra.source;
+ Assert.deepEqual(
+ { source },
+ expectedTelemetryInfo,
+ `Got expected telemetry on test case "${msg}"`
+ );
+ }
+ return;
+ }
+
+ let installPromptPromise = promisePopupNotificationShown(
+ "addon-webext-permissions"
+ ).then(panel => {
+ panel.button.click();
+ });
+
+ let promptPromise = acceptAppMenuNotificationWhenShown(
+ "addon-installed",
+ XPI_ADDON_ID
+ );
+
+ await SpecialPowers.spawn(browser, contentFnArgs, contentFn);
+
+ await Promise.all([installPromptPromise, promptPromise]);
+
+ let addon = await promiseAddonByID(XPI_ADDON_ID);
+
+ registerCleanupFunction(async () => {
+ await addon.uninstall();
+ });
+
+ // Check that the expected installTelemetryInfo has been stored in the
+ // addon details.
+ AddonTestUtils.checkInstallInfo(
+ addon,
+ { method: "installTrigger", ...expectedTelemetryInfo },
+ `on "${msg}"`
+ );
+
+ await addon.uninstall();
+ });
+}
+
+add_task(function testInstallAfterHistoryPushState() {
+ return testInstallTrigger(
+ "InstallTrigger after history.pushState",
+ SECURE_TESTROOT,
+ [SECURE_TESTROOT, XPI_URL],
+ (secureTestRoot, xpiURL) => {
+ // `sourceURL` should match the exact location, even after a location
+ // update using the history API. In this case, we update the URL with
+ // query parameters and expect `sourceURL` to contain those parameters.
+ content.history.pushState(
+ {}, // state
+ "", // title
+ `${secureTestRoot}?some=query&par=am`
+ );
+ content.InstallTrigger.install({ URL: xpiURL });
+ },
+ {
+ source: "test-host",
+ sourceURL:
+ "https://example.com/browser/toolkit/mozapps/extensions/test/browser/?some=query&par=am",
+ }
+ );
+});
+
+add_task(async function testInstallTriggerFromSubframe() {
+ function runTestCase(msg, tabURL, testFrameAttrs, expected) {
+ info(
+ `InstallTrigger from iframe test: ${msg} - frame attributes ${JSON.stringify(
+ testFrameAttrs
+ )}`
+ );
+ return testInstallTrigger(
+ msg,
+ tabURL,
+ [XPI_URL, testFrameAttrs],
+ async (xpiURL, frameAttrs) => {
+ const frame = content.document.createElement("iframe");
+ if (frameAttrs) {
+ for (const attr of Object.keys(frameAttrs)) {
+ let value = frameAttrs[attr];
+ if (value === "blob:") {
+ const blob = new content.Blob(["blob-testpage"]);
+ value = content.URL.createObjectURL(blob, "text/html");
+ }
+ frame[attr] = value;
+ }
+ }
+ const promiseLoaded = new Promise(resolve =>
+ frame.addEventListener("load", resolve, { once: true })
+ );
+ content.document.body.appendChild(frame);
+ await promiseLoaded;
+ frame.contentWindow.InstallTrigger.install({ URL: xpiURL });
+ },
+ expected.telemetryInfo,
+ expected.blockedOrigin
+ );
+ }
+
+ // On Windows "file:///" does not load the default files index html page
+ // and the test would get stuck.
+ const fileURL = AppConstants.platform === "win" ? "file:///C:/" : "file:///";
+
+ const expected = {
+ http: {
+ telemetryInfo: {
+ source: "test-host",
+ sourceURL:
+ "https://example.com/browser/toolkit/mozapps/extensions/test/browser/",
+ },
+ blockedOrigin: false,
+ },
+ httpBlob: {
+ telemetryInfo: {
+ source: "test-host",
+ // Example: "blob:https://example.com/BLOB_URL_UUID"
+ sourceURL: /^blob:https:\/\/example\.com\//,
+ },
+ blockedOrigin: false,
+ },
+ file: {
+ telemetryInfo: {
+ source: "unknown",
+ sourceURL: fileURL,
+ },
+ blockedOrigin: false,
+ },
+ fileBlob: {
+ telemetryInfo: {
+ source: "unknown",
+ // Example: "blob:null/BLOB_URL_UUID"
+ sourceURL: /^blob:null\//,
+ },
+ blockedOrigin: false,
+ },
+ httpBlockedOnOrigin: {
+ telemetryInfo: {
+ source: "test-host",
+ },
+ blockedOrigin: true,
+ },
+ otherBlockedOnOrigin: {
+ telemetryInfo: {
+ source: "unknown",
+ },
+ blockedOrigin: true,
+ },
+ };
+
+ const testCases = [
+ ["blank iframe with no attributes", SECURE_TESTROOT, {}, expected.http],
+
+ // These are blocked by a Firefox doorhanger and the user can't allow it neither.
+ [
+ "http page iframe src='blob:...'",
+ SECURE_TESTROOT,
+ { src: "blob:" },
+ expected.httpBlockedOnOrigin,
+ ],
+ [
+ "file page iframe src='blob:...'",
+ fileURL,
+ { src: "blob:" },
+ expected.otherBlockedOnOrigin,
+ ],
+ [
+ "iframe srcdoc=''",
+ SECURE_TESTROOT,
+ { srcdoc: "" },
+ expected.httpBlockedOnOrigin,
+ ],
+ [
+ "blank iframe embedded into a top-level sandbox page",
+ `${SECURE_TESTROOT}sandboxed.html`,
+ {},
+ expected.httpBlockedOnOrigin,
+ ],
+ [
+ "blank iframe with sandbox='allow-scripts'",
+ SECURE_TESTROOT,
+ { sandbox: "allow-scripts" },
+ expected.httpBlockedOnOrigin,
+ ],
+ [
+ "iframe srcdoc='' sandbox='allow-scripts'",
+ SECURE_TESTROOT,
+ { srcdoc: "", sandbox: "allow-scripts" },
+ expected.httpBlockedOnOrigin,
+ ],
+ [
+ "http page iframe src='blob:...' sandbox='allow-scripts'",
+ SECURE_TESTROOT,
+ { src: "blob:", sandbox: "allow-scripts" },
+ expected.httpBlockedOnOrigin,
+ ],
+ [
+ "iframe src='data:...'",
+ SECURE_TESTROOT,
+ { src: "data:text/html,data-testpage" },
+ expected.httpBlockedOnOrigin,
+ ],
+ [
+ "blank frame embedded in a data url",
+ "data:text/html,data-testpage",
+ {},
+ expected.otherBlockedOnOrigin,
+ ],
+ [
+ "blank frame embedded into a about:blank page",
+ "about:blank",
+ {},
+ expected.otherBlockedOnOrigin,
+ ],
+ ];
+
+ for (const testCase of testCases) {
+ await runTestCase(...testCase);
+ }
+});
+
+add_task(function testInstallBlankFrameNestedIntoBlobURLPage() {
+ return testInstallTrigger(
+ "Blank frame nested into a blob url page",
+ SECURE_TESTROOT,
+ [XPI_URL],
+ async xpiURL => {
+ const url = content.URL.createObjectURL(
+ new content.Blob(["blob-testpage"]),
+ "text/html"
+ );
+ const topframe = content.document.createElement("iframe");
+ topframe.src = url;
+ const topframeLoaded = new Promise(resolve => {
+ topframe.addEventListener("load", resolve, { once: true });
+ });
+ content.document.body.appendChild(topframe);
+ await topframeLoaded;
+ const subframe = topframe.contentDocument.createElement("iframe");
+ topframe.contentDocument.body.appendChild(subframe);
+ subframe.contentWindow.InstallTrigger.install({ URL: xpiURL });
+ },
+ {
+ source: "test-host",
+ },
+ /* expectBlockedOrigin */ true
+ );
+});
+
+add_task(function testInstallTriggerTopLevelDataURL() {
+ return testInstallTrigger(
+ "Blank frame nested into a blob url page",
+ "data:text/html,testpage",
+ [XPI_URL],
+ async xpiURL => {
+ this.content.InstallTrigger.install({ URL: xpiURL });
+ },
+ {
+ source: "unknown",
+ },
+ /* expectBlockedOrigin */ true
+ );
+});
+
+add_task(function teardown_clearUnexamitedTelemetry() {
+ // Clear collected telemetry events when we are not going to run any assertion on them.
+ // (otherwise the test will fail because of unexamined telemetry events).
+ AddonTestUtils.getAMTelemetryEvents();
+});