summaryrefslogtreecommitdiffstats
path: root/browser/base/content/test/webextensions/browser_extension_sideloading.js
diff options
context:
space:
mode:
Diffstat (limited to 'browser/base/content/test/webextensions/browser_extension_sideloading.js')
-rw-r--r--browser/base/content/test/webextensions/browser_extension_sideloading.js410
1 files changed, 410 insertions, 0 deletions
diff --git a/browser/base/content/test/webextensions/browser_extension_sideloading.js b/browser/base/content/test/webextensions/browser_extension_sideloading.js
new file mode 100644
index 0000000000..958393336f
--- /dev/null
+++ b/browser/base/content/test/webextensions/browser_extension_sideloading.js
@@ -0,0 +1,410 @@
+/* eslint-disable mozilla/no-arbitrary-setTimeout */
+const { AddonManagerPrivate } = ChromeUtils.import(
+ "resource://gre/modules/AddonManager.jsm"
+);
+
+const { AddonTestUtils } = ChromeUtils.import(
+ "resource://testing-common/AddonTestUtils.jsm"
+);
+
+AddonTestUtils.initMochitest(this);
+
+hookExtensionsTelemetry();
+AddonTestUtils.hookAMTelemetryEvents();
+
+const kSideloaded = true;
+
+async function createWebExtension(details) {
+ let options = {
+ manifest: {
+ browser_specific_settings: { gecko: { id: details.id } },
+
+ name: details.name,
+
+ permissions: details.permissions,
+ },
+ };
+
+ if (details.iconURL) {
+ options.manifest.icons = { "64": details.iconURL };
+ }
+
+ let xpi = AddonTestUtils.createTempWebExtensionFile(options);
+
+ await AddonTestUtils.manuallyInstall(xpi);
+}
+
+function promiseEvent(eventEmitter, event) {
+ return new Promise(resolve => {
+ eventEmitter.once(event, resolve);
+ });
+}
+
+function getAddonElement(managerWindow, addonId) {
+ return TestUtils.waitForCondition(
+ () =>
+ managerWindow.document.querySelector(`addon-card[addon-id="${addonId}"]`),
+ `Found entry for sideload extension addon "${addonId}" in HTML about:addons`
+ );
+}
+
+function assertSideloadedAddonElementState(addonElement, checked) {
+ const enableBtn = addonElement.querySelector('[action="toggle-disabled"]');
+ is(
+ enableBtn.checked,
+ checked,
+ `The enable button is ${!checked ? " not " : ""} checked`
+ );
+ is(enableBtn.localName, "input", "The enable button is an input");
+ is(enableBtn.type, "checkbox", "It's a checkbox");
+}
+
+function clickEnableExtension(addonElement) {
+ addonElement.querySelector('[action="toggle-disabled"]').click();
+}
+
+add_task(async function test_sideloading() {
+ const DEFAULT_ICON_URL =
+ "chrome://mozapps/skin/extensions/extensionGeneric.svg";
+
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["xpinstall.signatures.required", false],
+ ["extensions.autoDisableScopes", 15],
+ ["extensions.ui.ignoreUnsigned", true],
+ ],
+ });
+
+ const ID1 = "addon1@tests.mozilla.org";
+ await createWebExtension({
+ id: ID1,
+ name: "Test 1",
+ userDisabled: true,
+ permissions: ["history", "https://*/*"],
+ iconURL: "foo-icon.png",
+ });
+
+ const ID2 = "addon2@tests.mozilla.org";
+ await createWebExtension({
+ id: ID2,
+ name: "Test 2",
+ permissions: ["<all_urls>"],
+ });
+
+ const ID3 = "addon3@tests.mozilla.org";
+ await createWebExtension({
+ id: ID3,
+ name: "Test 3",
+ permissions: ["<all_urls>"],
+ });
+
+ testCleanup = async function() {
+ // clear out ExtensionsUI state about sideloaded extensions so
+ // subsequent tests don't get confused.
+ ExtensionsUI.sideloaded.clear();
+ ExtensionsUI.emit("change");
+ };
+
+ // Navigate away from the starting page to force about:addons to load
+ // in a new tab during the tests below.
+ BrowserTestUtils.loadURI(gBrowser.selectedBrowser, "about:robots");
+ await BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
+
+ registerCleanupFunction(async function() {
+ // Return to about:blank when we're done
+ BrowserTestUtils.loadURI(gBrowser.selectedBrowser, "about:blank");
+ await BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
+ });
+
+ let changePromise = new Promise(resolve => {
+ ExtensionsUI.on("change", function listener() {
+ ExtensionsUI.off("change", listener);
+ resolve();
+ });
+ });
+ ExtensionsUI._checkForSideloaded();
+ await changePromise;
+
+ // Check for the addons badge on the hamburger menu
+ let menuButton = document.getElementById("PanelUI-menu-button");
+ is(
+ menuButton.getAttribute("badge-status"),
+ "addon-alert",
+ "Should have addon alert badge"
+ );
+
+ // Find the menu entries for sideloaded extensions
+ await gCUITestUtils.openMainMenu();
+
+ let addons = PanelUI.addonNotificationContainer;
+ is(
+ addons.children.length,
+ 3,
+ "Have 3 menu entries for sideloaded extensions"
+ );
+
+ info(
+ "Test disabling sideloaded addon 1 using the permission prompt secondary button"
+ );
+
+ // Click the first sideloaded extension
+ let popupPromise = promisePopupNotificationShown("addon-webext-permissions");
+ addons.children[0].click();
+
+ // The click should hide the main menu. This is currently synchronous.
+ ok(PanelUI.panel.state != "open", "Main menu is closed or closing.");
+
+ // When we get the permissions prompt, we should be at the extensions
+ // list in about:addons
+ let panel = await popupPromise;
+ is(
+ gBrowser.currentURI.spec,
+ "about:addons",
+ "Foreground tab is at about:addons"
+ );
+
+ const VIEW = "addons://list/extension";
+ let win = gBrowser.selectedBrowser.contentWindow;
+
+ await TestUtils.waitForCondition(
+ () => !win.gViewController.isLoading,
+ "about:addons view is fully loaded"
+ );
+ is(
+ win.gViewController.currentViewId,
+ VIEW,
+ "about:addons is at extensions list"
+ );
+
+ // Check the contents of the notification, then choose "Cancel"
+ checkNotification(
+ panel,
+ /\/foo-icon\.png$/,
+ [
+ ["webextPerms.hostDescription.allUrls"],
+ ["webextPerms.description.history"],
+ ],
+ kSideloaded
+ );
+
+ panel.secondaryButton.click();
+
+ let [addon1, addon2, addon3] = await AddonManager.getAddonsByIDs([
+ ID1,
+ ID2,
+ ID3,
+ ]);
+ ok(addon1.seen, "Addon should be marked as seen");
+ is(addon1.userDisabled, true, "Addon 1 should still be disabled");
+ is(addon2.userDisabled, true, "Addon 2 should still be disabled");
+ is(addon3.userDisabled, true, "Addon 3 should still be disabled");
+
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+
+ // Should still have 2 entries in the hamburger menu
+ await gCUITestUtils.openMainMenu();
+
+ addons = PanelUI.addonNotificationContainer;
+ is(
+ addons.children.length,
+ 2,
+ "Have 2 menu entries for sideloaded extensions"
+ );
+
+ // Close the hamburger menu and go directly to the addons manager
+ await gCUITestUtils.hideMainMenu();
+
+ win = await BrowserOpenAddonsMgr(VIEW);
+ await waitAboutAddonsViewLoaded(win.document);
+
+ // about:addons addon entry element.
+ const addonElement = await getAddonElement(win, ID2);
+
+ assertSideloadedAddonElementState(addonElement, false);
+
+ info("Test enabling sideloaded addon 2 from about:addons enable button");
+
+ // When clicking enable we should see the permissions notification
+ popupPromise = promisePopupNotificationShown("addon-webext-permissions");
+ clickEnableExtension(addonElement);
+ panel = await popupPromise;
+ checkNotification(
+ panel,
+ DEFAULT_ICON_URL,
+ [["webextPerms.hostDescription.allUrls"]],
+ kSideloaded
+ );
+
+ // Test incognito checkbox in post install notification
+ function setupPostInstallNotificationTest() {
+ let promiseNotificationShown = promiseAppMenuNotificationShown(
+ "addon-installed"
+ );
+ return async function(addon) {
+ info(`Expect post install notification for "${addon.name}"`);
+ let postInstallPanel = await promiseNotificationShown;
+ let incognitoCheckbox = postInstallPanel.querySelector(
+ "#addon-incognito-checkbox"
+ );
+ is(
+ window.AppMenuNotifications.activeNotification.options.name,
+ addon.name,
+ "Got the expected addon name in the active notification"
+ );
+ ok(
+ incognitoCheckbox,
+ "Got an incognito checkbox in the post install notification panel"
+ );
+ ok(!incognitoCheckbox.hidden, "Incognito checkbox should not be hidden");
+ // Dismiss post install notification.
+ postInstallPanel.button.click();
+ };
+ }
+
+ // Setup async test for post install notification on addon 2
+ let testPostInstallIncognitoCheckbox = setupPostInstallNotificationTest();
+
+ // Accept the permissions
+ panel.button.click();
+ await promiseEvent(ExtensionsUI, "change");
+
+ addon2 = await AddonManager.getAddonByID(ID2);
+ is(addon2.userDisabled, false, "Addon 2 should be enabled");
+ assertSideloadedAddonElementState(addonElement, true);
+
+ // Test post install notification on addon 2.
+ await testPostInstallIncognitoCheckbox(addon2);
+
+ // Should still have 1 entry in the hamburger menu
+ await gCUITestUtils.openMainMenu();
+
+ addons = PanelUI.addonNotificationContainer;
+ is(addons.children.length, 1, "Have 1 menu entry for sideloaded extensions");
+
+ // Close the hamburger menu and go to the detail page for this addon
+ await gCUITestUtils.hideMainMenu();
+
+ win = await BrowserOpenAddonsMgr(
+ `addons://detail/${encodeURIComponent(ID3)}`
+ );
+
+ info("Test enabling sideloaded addon 3 from app menu");
+ // Trigger addon 3 install as triggered from the app menu, to be able to cover the
+ // post install notification that should be triggered when the permission
+ // dialog is accepted from that flow.
+ popupPromise = promisePopupNotificationShown("addon-webext-permissions");
+ ExtensionsUI.showSideloaded(gBrowser, addon3);
+
+ panel = await popupPromise;
+ checkNotification(
+ panel,
+ DEFAULT_ICON_URL,
+ [["webextPerms.hostDescription.allUrls"]],
+ kSideloaded
+ );
+
+ // Setup async test for post install notification on addon 3
+ testPostInstallIncognitoCheckbox = setupPostInstallNotificationTest();
+
+ // Accept the permissions
+ panel.button.click();
+ await promiseEvent(ExtensionsUI, "change");
+
+ addon3 = await AddonManager.getAddonByID(ID3);
+ is(addon3.userDisabled, false, "Addon 3 should be enabled");
+
+ // Test post install notification on addon 3.
+ await testPostInstallIncognitoCheckbox(addon3);
+
+ // We should have recorded 1 cancelled followed by 2 accepted sideloads.
+ expectTelemetry(["sideloadRejected", "sideloadAccepted", "sideloadAccepted"]);
+
+ isnot(
+ menuButton.getAttribute("badge-status"),
+ "addon-alert",
+ "Should no longer have addon alert badge"
+ );
+
+ await new Promise(resolve => setTimeout(resolve, 100));
+
+ for (let addon of [addon1, addon2, addon3]) {
+ await addon.uninstall();
+ }
+
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+
+ // Assert that the expected AddonManager telemetry are being recorded.
+ const expectedExtra = { source: "app-profile", method: "sideload" };
+
+ const baseEvent = { object: "extension", extra: expectedExtra };
+ const createBaseEventAddon = n => ({
+ ...baseEvent,
+ value: `addon${n}@tests.mozilla.org`,
+ });
+ const getEventsForAddonId = (events, addonId) =>
+ events.filter(ev => ev.value === addonId);
+
+ const amEvents = AddonTestUtils.getAMTelemetryEvents();
+
+ // Test telemetry events for addon1 (1 permission and 1 origin).
+ info("Test telemetry events collected for addon1");
+
+ const baseEventAddon1 = createBaseEventAddon(1);
+ const collectedEventsAddon1 = getEventsForAddonId(
+ amEvents,
+ baseEventAddon1.value
+ );
+ const expectedEventsAddon1 = [
+ {
+ ...baseEventAddon1,
+ method: "sideload_prompt",
+ extra: { ...expectedExtra, num_strings: "2" },
+ },
+ { ...baseEventAddon1, method: "uninstall" },
+ ];
+
+ let i = 0;
+ for (let event of collectedEventsAddon1) {
+ Assert.deepEqual(
+ event,
+ expectedEventsAddon1[i++],
+ "Got the expected telemetry event"
+ );
+ }
+
+ is(
+ collectedEventsAddon1.length,
+ expectedEventsAddon1.length,
+ "Got the expected number of telemetry events for addon1"
+ );
+
+ const baseEventAddon2 = createBaseEventAddon(2);
+ const collectedEventsAddon2 = getEventsForAddonId(
+ amEvents,
+ baseEventAddon2.value
+ );
+ const expectedEventsAddon2 = [
+ {
+ ...baseEventAddon2,
+ method: "sideload_prompt",
+ extra: { ...expectedExtra, num_strings: "1" },
+ },
+ { ...baseEventAddon2, method: "enable" },
+ { ...baseEventAddon2, method: "uninstall" },
+ ];
+
+ i = 0;
+ for (let event of collectedEventsAddon2) {
+ Assert.deepEqual(
+ event,
+ expectedEventsAddon2[i++],
+ "Got the expected telemetry event"
+ );
+ }
+
+ is(
+ collectedEventsAddon2.length,
+ expectedEventsAddon2.length,
+ "Got the expected number of telemetry events for addon2"
+ );
+});