diff options
Diffstat (limited to 'browser/base/content/test/webextensions/browser_extension_sideloading.js')
-rw-r--r-- | browser/base/content/test/webextensions/browser_extension_sideloading.js | 410 |
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" + ); +}); |