/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */ /* vim: set sts=2 sw=2 et tw=80: */ "use strict"; const { AddonTestUtils } = ChromeUtils.importESModule( "resource://testing-common/AddonTestUtils.sys.mjs" ); const { ExtensionPermissions } = ChromeUtils.importESModule( "resource://gre/modules/ExtensionPermissions.sys.mjs" ); const { Management } = ChromeUtils.importESModule( "resource://gre/modules/Extension.sys.mjs" ); var gManagerWindow; AddonTestUtils.initMochitest(this); function get_test_items() { var items = {}; for (let item of gManagerWindow.document.querySelectorAll("addon-card")) { items[item.getAttribute("addon-id")] = item; } return items; } function getHtmlElem(selector) { return gManagerWindow.document.querySelector(selector); } function getPrivateBrowsingBadge(card) { return card.querySelector(".addon-badge-private-browsing-allowed"); } function getPreferencesButtonAtListView(card) { return card.querySelector("panel-item[action='preferences']"); } function getPreferencesButtonAtDetailsView() { return getHtmlElem("panel-item[action='preferences']"); } function isInlineOptionsVisible() { // The following button is used to open the inline options browser. return !getHtmlElem(".tab-button[name='preferences']").hidden; } function getPrivateBrowsingValue() { return getHtmlElem("input[type='radio'][name='private-browsing']:checked") .value; } async function setPrivateBrowsingValue(value, id) { let changePromise = new Promise(resolve => { const listener = (type, { extensionId, added, removed }) => { if (extensionId == id) { // Let's make sure we received the right message let { permissions } = value == "0" ? removed : added; ok(permissions.includes("internal:privateBrowsingAllowed")); Management.off("change-permissions", listener); resolve(); } }; Management.on("change-permissions", listener); }); let radio = getHtmlElem( `input[type="radio"][name="private-browsing"][value="${value}"]` ); // NOTE: not using EventUtils.synthesizeMouseAtCenter here because it // does make this test to fail intermittently in some jobs (e.g. TV jobs) radio.click(); // Let's make sure we wait until the change has peristed in the database return changePromise; } // Check whether the private browsing inputs are visible in the details view. function checkIsModifiable(expected) { if (expected) { is_element_visible( getHtmlElem(".addon-detail-row-private-browsing"), "Private browsing should be visible" ); } else { is_element_hidden( getHtmlElem(".addon-detail-row-private-browsing"), "Private browsing should be hidden" ); } checkHelpRow(".addon-detail-row-private-browsing", expected); } // Check whether the details view shows that private browsing is forcibly disallowed. function checkIsDisallowed(expected) { if (expected) { is_element_visible( getHtmlElem(".addon-detail-row-private-browsing-disallowed"), "Private browsing should be disallowed" ); } else { is_element_hidden( getHtmlElem(".addon-detail-row-private-browsing-disallowed"), "Private browsing should not be disallowed" ); } checkHelpRow(".addon-detail-row-private-browsing-disallowed", expected); } // Check whether the details view shows that private browsing is forcibly allowed. function checkIsRequired(expected) { if (expected) { is_element_visible( getHtmlElem(".addon-detail-row-private-browsing-required"), "Private browsing should be required" ); } else { is_element_hidden( getHtmlElem(".addon-detail-row-private-browsing-required"), "Private browsing should not be required" ); } checkHelpRow(".addon-detail-row-private-browsing-required", expected); } function checkHelpRow(selector, expected) { let helpRow = getHtmlElem(`${selector} + .addon-detail-help-row`); if (expected) { is_element_visible(helpRow, `Help row should be shown: ${selector}`); is_element_visible(helpRow.querySelector("a"), "Expected learn more link"); } else { is_element_hidden(helpRow, `Help row should be hidden: ${selector}`); } } async function hasPrivateAllowed(id) { let perms = await ExtensionPermissions.get(id); return perms.permissions.includes("internal:privateBrowsingAllowed"); } add_task(async function test_badge_and_toggle_incognito() { let addons = new Map([ [ "@test-default", { useAddonManager: "temporary", manifest: { browser_specific_settings: { gecko: { id: "@test-default" }, }, }, }, ], [ "@test-override", { useAddonManager: "temporary", manifest: { browser_specific_settings: { gecko: { id: "@test-override" }, }, }, incognitoOverride: "spanning", }, ], [ "@test-override-permanent", { useAddonManager: "permanent", manifest: { browser_specific_settings: { gecko: { id: "@test-override-permanent" }, }, }, incognitoOverride: "spanning", }, ], [ "@test-not-allowed", { useAddonManager: "temporary", manifest: { browser_specific_settings: { gecko: { id: "@test-not-allowed" }, }, incognito: "not_allowed", }, }, ], ]); let extensions = []; for (let definition of addons.values()) { let extension = ExtensionTestUtils.loadExtension(definition); extensions.push(extension); await extension.startup(); } gManagerWindow = await open_manager("addons://list/extension"); let items = get_test_items(); for (let [id, definition] of addons.entries()) { ok(items[id], `${id} listed`); let badge = getPrivateBrowsingBadge(items[id]); if (definition.incognitoOverride == "spanning") { is_element_visible(badge, `private browsing badge is visible`); } else { is_element_hidden(badge, `private browsing badge is hidden`); } } await close_manager(gManagerWindow); for (let [id, definition] of addons.entries()) { gManagerWindow = await open_manager( "addons://detail/" + encodeURIComponent(id) ); ok(true, `==== ${id} detail opened`); if (definition.manifest.incognito == "not_allowed") { checkIsModifiable(false); ok(!(await hasPrivateAllowed(id)), "Private browsing permission not set"); checkIsDisallowed(true); } else { // This assumes PERM_CAN_CHANGE_PRIVATEBROWSING_ACCESS, we test other options in a later test in this file. checkIsModifiable(true); if (definition.incognitoOverride == "spanning") { is(getPrivateBrowsingValue(), "1", "Private browsing should be on"); ok(await hasPrivateAllowed(id), "Private browsing permission set"); await setPrivateBrowsingValue("0", id); is(getPrivateBrowsingValue(), "0", "Private browsing should be off"); ok( !(await hasPrivateAllowed(id)), "Private browsing permission removed" ); } else { is(getPrivateBrowsingValue(), "0", "Private browsing should be off"); ok( !(await hasPrivateAllowed(id)), "Private browsing permission not set" ); await setPrivateBrowsingValue("1", id); is(getPrivateBrowsingValue(), "1", "Private browsing should be on"); ok(await hasPrivateAllowed(id), "Private browsing permission set"); } } await close_manager(gManagerWindow); } for (let extension of extensions) { await extension.unload(); } }); add_task(async function test_addon_preferences_button() { let addons = new Map([ [ "test-inline-options@mozilla.com", { useAddonManager: "temporary", manifest: { name: "Extension with inline options", browser_specific_settings: { gecko: { id: "test-inline-options@mozilla.com" }, }, options_ui: { page: "options.html", open_in_tab: false }, }, }, ], [ "test-newtab-options@mozilla.com", { useAddonManager: "temporary", manifest: { name: "Extension with options page in a new tab", browser_specific_settings: { gecko: { id: "test-newtab-options@mozilla.com" }, }, options_ui: { page: "options.html", open_in_tab: true }, }, }, ], [ "test-not-allowed@mozilla.com", { useAddonManager: "temporary", manifest: { name: "Extension not allowed in PB windows", incognito: "not_allowed", browser_specific_settings: { gecko: { id: "test-not-allowed@mozilla.com" }, }, options_ui: { page: "options.html", open_in_tab: true }, }, }, ], ]); async function runTest(openInPrivateWin) { const win = await BrowserTestUtils.openNewBrowserWindow({ private: openInPrivateWin, }); gManagerWindow = await open_manager( "addons://list/extension", undefined, undefined, undefined, win ); const checkPrefsVisibility = (id, hasInlinePrefs, expectVisible) => { if (!hasInlinePrefs) { const detailsPrefBtn = getPreferencesButtonAtDetailsView(); is( !detailsPrefBtn.hidden, expectVisible, `The ${id} prefs button in the addon details has the expected visibility` ); } else { is( isInlineOptionsVisible(), expectVisible, `The ${id} inline prefs in the addon details has the expected visibility` ); } }; const setAddonPrivateBrowsingAccess = async (id, allowPrivateBrowsing) => { const cardUpdatedPromise = BrowserTestUtils.waitForEvent( getHtmlElem("addon-card"), "update" ); is( getPrivateBrowsingValue(), allowPrivateBrowsing ? "0" : "1", `Private browsing should be initially ${ allowPrivateBrowsing ? "off" : "on" }` ); // Get the DOM element we want to click on (to allow or disallow the // addon on private browsing windows). await setPrivateBrowsingValue(allowPrivateBrowsing ? "1" : "0", id); info(`Waiting for details view of ${id} to be reloaded`); await cardUpdatedPromise; is( getPrivateBrowsingValue(), allowPrivateBrowsing ? "1" : "0", `Private browsing should be initially ${ allowPrivateBrowsing ? "on" : "off" }` ); is( await hasPrivateAllowed(id), allowPrivateBrowsing, `Private browsing permission ${ allowPrivateBrowsing ? "added" : "removed" }` ); let badge = getPrivateBrowsingBadge(getHtmlElem("addon-card")); is( !badge.hidden, allowPrivateBrowsing, `Expected private browsing badge at ${id}` ); }; const extensions = []; for (const definition of addons.values()) { const extension = ExtensionTestUtils.loadExtension(definition); extensions.push(extension); await extension.startup(); } const items = get_test_items(); for (const id of addons.keys()) { // Check the preferences button in the addon list page. is( getPreferencesButtonAtListView(items[id]).hidden, openInPrivateWin, `The ${id} prefs button in the addon list has the expected visibility` ); } for (const [id, definition] of addons.entries()) { // Check the preferences button or inline frame in the addon // details page. info(`Opening addon details for ${id}`); const hasInlinePrefs = !definition.manifest.options_ui.open_in_tab; const onceViewChanged = wait_for_view_load(gManagerWindow, null, true); gManagerWindow.loadView(`addons://detail/${encodeURIComponent(id)}`); await onceViewChanged; checkPrefsVisibility(id, hasInlinePrefs, !openInPrivateWin); // While testing in a private window, also check that the preferences // are going to be visible when we toggle the PB access for the addon. if (openInPrivateWin && definition.manifest.incognito !== "not_allowed") { await setAddonPrivateBrowsingAccess(id, true); checkPrefsVisibility(id, hasInlinePrefs, true); await setAddonPrivateBrowsingAccess(id, false); checkPrefsVisibility(id, hasInlinePrefs, false); } } for (const extension of extensions) { await extension.unload(); } await close_manager(gManagerWindow); await BrowserTestUtils.closeWindow(win); } // run tests in private and non-private windows. await runTest(true); await runTest(false); }); add_task(async function test_addon_postinstall_incognito_hidden_checkbox() { await SpecialPowers.pushPrefEnv({ set: [["extensions.langpacks.signatures.required", false]], }); const TEST_ADDONS = [ { manifest: { name: "Extension incognito default opt-in", browser_specific_settings: { gecko: { id: "ext-incognito-default-opt-in@mozilla.com" }, }, }, }, { manifest: { name: "Extension incognito not_allowed", browser_specific_settings: { gecko: { id: "ext-incognito-not-allowed@mozilla.com" }, }, incognito: "not_allowed", }, }, { manifest: { name: "Static Theme", browser_specific_settings: { gecko: { id: "static-theme@mozilla.com" }, }, theme: { colors: { frame: "#FFFFFF", tab_background_text: "#000", }, }, }, }, { manifest: { name: "Dictionary", browser_specific_settings: { gecko: { id: "dictionary@mozilla.com" } }, dictionaries: { und: "dictionaries/und.dic", }, }, files: { "dictionaries/und.dic": "", "dictionaries/und.aff": "", }, }, { manifest: { name: "Langpack", browser_specific_settings: { gecko: { id: "langpack@mozilla.com" } }, langpack_id: "und", languages: { und: { chrome_resources: { global: "chrome/und/locale/und/global", }, version: "20190326174300", }, }, }, }, ]; for (let definition of TEST_ADDONS) { let { id } = definition.manifest.browser_specific_settings.gecko; info( `Testing incognito checkbox visibility on ${id} post install notification` ); const xpi = AddonTestUtils.createTempWebExtensionFile(definition); let install = await AddonManager.getInstallForFile(xpi); await Promise.all([ waitAppMenuNotificationShown("addon-installed", id, true), install.install().then(() => { Services.obs.notifyObservers( { addon: install.addon, target: gBrowser.selectedBrowser, }, "webextension-install-notify" ); }), ]); const { addon } = install; const { permissions } = addon; const canChangePBAccess = Boolean( permissions & AddonManager.PERM_CAN_CHANGE_PRIVATEBROWSING_ACCESS ); if (id === "ext-incognito-default-opt-in@mozilla.com") { ok( canChangePBAccess, `${id} should have the PERM_CAN_CHANGE_PRIVATEBROWSING_ACCESS permission` ); } else { ok( !canChangePBAccess, `${id} should not have the PERM_CAN_CHANGE_PRIVATEBROWSING_ACCESS permission` ); } // This tests the visibility of various private detail rows. gManagerWindow = await open_manager( "addons://detail/" + encodeURIComponent(id) ); info(`addon ${id} detail opened`); if (addon.type === "extension") { checkIsModifiable(canChangePBAccess); let required = addon.incognito === "spanning"; checkIsRequired(!canChangePBAccess && required); checkIsDisallowed(!canChangePBAccess && !required); } else { checkIsModifiable(false); checkIsRequired(false); checkIsDisallowed(false); } await close_manager(gManagerWindow); await addon.uninstall(); } // It is not possible to create a privileged add-on and install it, so just // simulate an installed privileged add-on and check the UI. await test_incognito_of_privileged_addons(); }); // Checks that the private browsing flag of privileged add-ons cannot be modified. async function test_incognito_of_privileged_addons() { // In mochitests it is not possible to create and install a privileged add-on // or a system add-on, so create a mock provider that simulates privileged // add-ons (which lack the PERM_CAN_CHANGE_PRIVATEBROWSING_ACCESS permission). let provider = new MockProvider(); provider.createAddons([ { name: "default incognito", id: "default-incognito@mock", incognito: "spanning", // This is the default. // Anything without the PERM_CAN_CHANGE_PRIVATEBROWSING_ACCESS permission. permissions: 0, }, { name: "not_allowed incognito", id: "not-allowed-incognito@mock", incognito: "not_allowed", // Anything without the PERM_CAN_CHANGE_PRIVATEBROWSING_ACCESS permission. permissions: 0, }, ]); gManagerWindow = await open_manager( "addons://detail/default-incognito%40mock" ); checkIsModifiable(false); checkIsRequired(true); checkIsDisallowed(false); await close_manager(gManagerWindow); gManagerWindow = await open_manager( "addons://detail/not-allowed-incognito%40mock" ); checkIsModifiable(false); checkIsRequired(false); checkIsDisallowed(true); await close_manager(gManagerWindow); provider.unregister(); }