summaryrefslogtreecommitdiffstats
path: root/comm/mail/base/test/webextensions/browser_extension_sideloading.js
diff options
context:
space:
mode:
Diffstat (limited to 'comm/mail/base/test/webextensions/browser_extension_sideloading.js')
-rw-r--r--comm/mail/base/test/webextensions/browser_extension_sideloading.js352
1 files changed, 352 insertions, 0 deletions
diff --git a/comm/mail/base/test/webextensions/browser_extension_sideloading.js b/comm/mail/base/test/webextensions/browser_extension_sideloading.js
new file mode 100644
index 0000000000..eb0754a922
--- /dev/null
+++ b/comm/mail/base/test/webextensions/browser_extension_sideloading.js
@@ -0,0 +1,352 @@
+/* eslint-disable mozilla/no-arbitrary-setTimeout */
+const { AddonManagerPrivate } = ChromeUtils.importESModule(
+ "resource://gre/modules/AddonManager.sys.mjs"
+);
+
+var { AddonTestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/AddonTestUtils.sys.mjs"
+);
+
+AddonTestUtils.initMochitest(this);
+
+AddonTestUtils.hookAMTelemetryEvents();
+
+const kSideloaded = true;
+
+async function createWebExtension(details) {
+ let options = {
+ manifest: {
+ applications: { 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 BrowserTestUtils.waitForCondition(
+ () =>
+ managerWindow.document.querySelector(`addon-card[addon-id="${addonId}"]`),
+ `Found entry for sideload extension addon "${addonId}" in HTML about:addons`
+ );
+}
+
+function assertSideloadedAddonElementState(addonElement, pressed) {
+ const enableBtn = addonElement.querySelector('[action="toggle-disabled"]');
+ is(
+ enableBtn.pressed,
+ pressed,
+ `The enable button is ${!pressed ? " not " : ""} pressed`
+ );
+ is(enableBtn.localName, "moz-toggle", "The enable button is a toggle");
+}
+
+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],
+ ["extensions.allowPrivateBrowsingByDefault", false],
+ ],
+ });
+
+ const ID1 = "addon1@tests.mozilla.org";
+ await createWebExtension({
+ id: ID1,
+ name: "Test 1",
+ userDisabled: true,
+ permissions: ["accountsRead", "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");
+ };
+
+ 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("button-appmenu");
+ 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.");
+
+ let panel = await popupPromise;
+
+ // Check the contents of the notification, then choose "Cancel"
+ await checkNotification(
+ panel,
+ /\/foo-icon\.png$/,
+ [
+ ["webext-perms-host-description-all-urls"],
+ ["webext-perms-description-accountsRead"],
+ ],
+ 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");
+
+ // 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();
+
+ const VIEW = "addons://list/extension";
+ let win = await openAddonsMgr(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;
+ await checkNotification(
+ panel,
+ DEFAULT_ICON_URL,
+ [["webext-perms-host-description-all-urls"]],
+ kSideloaded
+ );
+
+ // Setup async test for post install notification on addon 2
+ popupPromise = promisePopupNotificationShown("addon-installed");
+
+ // 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.
+ panel = await popupPromise;
+ panel.button.click();
+
+ let tabmail = document.getElementById("tabmail");
+ tabmail.closeTab(tabmail.currentTabInfo);
+
+ // 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");
+
+ PanelUI.hide();
+
+ // Open the Add-Ons Manager
+ win = await openAddonsMgr(`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(tabmail, addon3);
+
+ panel = await popupPromise;
+ await checkNotification(
+ panel,
+ DEFAULT_ICON_URL,
+ [["webext-perms-host-description-all-urls"]],
+ kSideloaded
+ );
+
+ // Setup async test for post install notification on addon 3
+ popupPromise = promisePopupNotificationShown("addon-installed");
+
+ // 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.
+ panel = await popupPromise;
+ panel.button.click();
+
+ 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();
+ }
+
+ tabmail.closeTab(tabmail.currentTabInfo);
+
+ // 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"
+ );
+});