/* eslint-env webextensions */ const PROXY_PREF = "network.proxy.type"; const HOMEPAGE_URL_PREF = "browser.startup.homepage"; const HOMEPAGE_OVERRIDE_KEY = "homepage_override"; const URL_OVERRIDES_TYPE = "url_overrides"; const NEW_TAB_KEY = "newTabURL"; const PREF_SETTING_TYPE = "prefs"; ChromeUtils.defineESModuleGetters(this, { ExtensionSettingsStore: "resource://gre/modules/ExtensionSettingsStore.sys.mjs", }); ChromeUtils.defineModuleGetter( this, "AboutNewTab", "resource:///modules/AboutNewTab.jsm" ); XPCOMUtils.defineLazyPreferenceGetter(this, "proxyType", PROXY_PREF); const { AddonTestUtils } = ChromeUtils.importESModule( "resource://testing-common/AddonTestUtils.sys.mjs" ); AddonTestUtils.initMochitest(this); const { ExtensionPreferencesManager } = ChromeUtils.importESModule( "resource://gre/modules/ExtensionPreferencesManager.sys.mjs" ); const TEST_DIR = gTestPath.substr(0, gTestPath.lastIndexOf("/")); const CHROME_URL_ROOT = TEST_DIR + "/"; const PERMISSIONS_URL = "chrome://browser/content/preferences/dialogs/sitePermissions.xhtml"; let sitePermissionsDialog; function getSupportsFile(path) { let cr = Cc["@mozilla.org/chrome/chrome-registry;1"].getService( Ci.nsIChromeRegistry ); let uri = Services.io.newURI(CHROME_URL_ROOT + path); let fileurl = cr.convertChromeURL(uri); return fileurl.QueryInterface(Ci.nsIFileURL); } function waitForMessageChange( element, cb, opts = { attributes: true, attributeFilter: ["hidden"] } ) { return waitForMutation(element, opts, cb); } function getElement(id, doc = gBrowser.contentDocument) { return doc.getElementById(id); } function waitForMessageHidden(messageId, doc) { return waitForMessageChange( getElement(messageId, doc), target => target.hidden ); } function waitForMessageShown(messageId, doc) { return waitForMessageChange( getElement(messageId, doc), target => !target.hidden ); } function waitForEnableMessage(messageId, doc) { return waitForMessageChange( getElement(messageId, doc), target => target.classList.contains("extension-controlled-disabled"), { attributeFilter: ["class"], attributes: true } ); } function waitForMessageContent(messageId, l10nId, doc) { return waitForMessageChange( getElement(messageId, doc), target => doc.l10n.getAttributes(target).id === l10nId, { childList: true } ); } async function openNotificationsPermissionDialog() { let dialogOpened = promiseLoadSubDialog(PERMISSIONS_URL); await SpecialPowers.spawn(gBrowser.selectedBrowser, [], function () { let doc = content.document; let settingsButton = doc.getElementById("notificationSettingsButton"); settingsButton.click(); }); sitePermissionsDialog = await dialogOpened; await sitePermissionsDialog.document.mozSubdialogReady; } async function disableExtensionViaClick(labelId, disableButtonId, doc) { let controlledLabel = doc.getElementById(labelId); let enableMessageShown = waitForEnableMessage(labelId, doc); doc.getElementById(disableButtonId).click(); await enableMessageShown; let controlledDescription = controlledLabel.querySelector("description"); is( doc.l10n.getAttributes(controlledDescription.querySelector("label")).id, "extension-controlled-enable", "The user is notified of how to enable the extension again." ); // The user can dismiss the enable instructions. let hidden = waitForMessageHidden(labelId, doc); controlledLabel.querySelector("image:last-of-type").click(); await hidden; } async function reEnableExtension(addon, labelId) { let controlledMessageShown = waitForMessageShown(labelId); await addon.enable(); await controlledMessageShown; } add_task(async function testExtensionControlledHomepage() { const ADDON_ID = "@set_homepage"; const SECOND_ADDON_ID = "@second_set_homepage"; await openPreferencesViaOpenPreferencesAPI("paneHome", { leaveOpen: true }); let homepagePref = () => Services.prefs.getCharPref(HOMEPAGE_URL_PREF); let originalHomepagePref = homepagePref(); is( gBrowser.currentURI.spec, "about:preferences#home", "#home should be in the URI for about:preferences" ); let doc = gBrowser.contentDocument; let homeModeEl = doc.getElementById("homeMode"); let customSettingsSection = doc.getElementById("customSettings"); is(homeModeEl.itemCount, 3, "The menu list starts with 3 options"); let promise = TestUtils.waitForCondition( () => homeModeEl.itemCount === 4, "wait for the addon option to be added as an option in the menu list" ); let extension = ExtensionTestUtils.loadExtension({ useAddonManager: "permanent", manifest: { version: "1.0", name: "set_homepage", browser_specific_settings: { gecko: { id: ADDON_ID, }, }, chrome_settings_overrides: { homepage: "/home.html" }, }, }); await extension.startup(); await promise; // The homepage is set to the default and the custom settings section is hidden is(homeModeEl.disabled, false, "The homepage menulist is enabled"); is( customSettingsSection.hidden, true, "The custom settings element is hidden" ); let addon = await AddonManager.getAddonByID(ADDON_ID); is( homeModeEl.value, addon.id, "the home select menu's value is set to the addon" ); promise = TestUtils.waitForPrefChange(HOMEPAGE_URL_PREF); // Set the Menu to the default value homeModeEl.value = "0"; homeModeEl.dispatchEvent(new Event("command")); await promise; is(homepagePref(), originalHomepagePref, "homepage is set back to default"); let levelOfControl = await ExtensionPreferencesManager.getLevelOfControl( addon.id, HOMEPAGE_OVERRIDE_KEY, PREF_SETTING_TYPE ); is( levelOfControl, "not_controllable", "getLevelOfControl returns not_controllable." ); let setting = await ExtensionPreferencesManager.getSetting( HOMEPAGE_OVERRIDE_KEY ); ok(!setting.value, "the setting is not set."); promise = TestUtils.waitForPrefChange(HOMEPAGE_URL_PREF); // Set the menu to the addon value homeModeEl.value = ADDON_ID; homeModeEl.dispatchEvent(new Event("command")); await promise; ok( homepagePref().startsWith("moz-extension") && homepagePref().endsWith("home.html"), "Home url should be provided by the extension." ); levelOfControl = await ExtensionPreferencesManager.getLevelOfControl( addon.id, HOMEPAGE_OVERRIDE_KEY, PREF_SETTING_TYPE ); is( levelOfControl, "controlled_by_this_extension", "getLevelOfControl returns controlled_by_this_extension." ); setting = await ExtensionPreferencesManager.getSetting(HOMEPAGE_OVERRIDE_KEY); ok( setting.value.startsWith("moz-extension") && setting.value.endsWith("home.html"), "The setting value is the same as the extension." ); // Add a second extension, ensure it is added to the menulist and selected. promise = TestUtils.waitForCondition( () => homeModeEl.itemCount == 5, "addon option is added as an option in the menu list" ); let secondExtension = ExtensionTestUtils.loadExtension({ useAddonManager: "permanent", manifest: { version: "1.0", name: "second_set_homepage", browser_specific_settings: { gecko: { id: SECOND_ADDON_ID, }, }, chrome_settings_overrides: { homepage: "/home2.html" }, }, }); await secondExtension.startup(); await promise; let secondAddon = await AddonManager.getAddonByID(SECOND_ADDON_ID); is(homeModeEl.value, SECOND_ADDON_ID, "home menulist is set to the add-on"); levelOfControl = await ExtensionPreferencesManager.getLevelOfControl( secondAddon.id, HOMEPAGE_OVERRIDE_KEY, PREF_SETTING_TYPE ); is( levelOfControl, "controlled_by_this_extension", "getLevelOfControl returns controlled_by_this_extension." ); setting = await ExtensionPreferencesManager.getSetting(HOMEPAGE_OVERRIDE_KEY); ok( setting.value.startsWith("moz-extension") && setting.value.endsWith("home2.html"), "The setting value is the same as the extension." ); promise = TestUtils.waitForCondition( () => homeModeEl.itemCount == 4, "addon option is no longer an option in the menu list after disable, even if it was not selected" ); await addon.disable(); await promise; // Ensure that re-enabling an addon adds it back to the menulist promise = TestUtils.waitForCondition( () => homeModeEl.itemCount == 5, "addon option is added again to the menulist when enabled" ); await addon.enable(); await promise; promise = TestUtils.waitForCondition( () => homeModeEl.itemCount == 4, "addon option is no longer an option in the menu list after disable" ); await secondAddon.disable(); await promise; promise = TestUtils.waitForCondition( () => homeModeEl.itemCount == 5, "addon option is added again to the menulist when enabled" ); await secondAddon.enable(); await promise; promise = TestUtils.waitForCondition( () => homeModeEl.itemCount == 3, "addon options are no longer an option in the menu list after disabling all addons" ); await secondAddon.disable(); await addon.disable(); await promise; is(homeModeEl.value, "0", "addon option is not selected in the menu list"); levelOfControl = await ExtensionPreferencesManager.getLevelOfControl( secondAddon.id, HOMEPAGE_OVERRIDE_KEY, PREF_SETTING_TYPE ); is( levelOfControl, "controllable_by_this_extension", "getLevelOfControl returns controllable_by_this_extension." ); setting = await ExtensionPreferencesManager.getSetting(HOMEPAGE_OVERRIDE_KEY); ok(!setting.value, "The setting value is back to default."); // The homepage elements are reset to their original state. is(homepagePref(), originalHomepagePref, "homepage is set back to default"); is(homeModeEl.disabled, false, "The homepage menulist is enabled"); BrowserTestUtils.removeTab(gBrowser.selectedTab); await extension.unload(); await secondExtension.unload(); }); add_task(async function testPrefLockedHomepage() { const ADDON_ID = "@set_homepage"; await openPreferencesViaOpenPreferencesAPI("paneHome", { leaveOpen: true }); let doc = gBrowser.contentDocument; is( gBrowser.currentURI.spec, "about:preferences#home", "#home should be in the URI for about:preferences" ); let homePagePref = "browser.startup.homepage"; let buttonPrefs = [ "pref.browser.homepage.disable_button.current_page", "pref.browser.homepage.disable_button.bookmark_page", "pref.browser.homepage.disable_button.restore_default", ]; let homeModeEl = doc.getElementById("homeMode"); let homePageInput = doc.getElementById("homePageUrl"); let prefs = Services.prefs.getDefaultBranch(null); let mutationOpts = { attributes: true, attributeFilter: ["disabled"] }; // Helper functions. let getButton = pref => doc.querySelector(`.homepage-button[preference="${pref}"`); let waitForAllMutations = () => Promise.all( buttonPrefs .map(pref => waitForMutation(getButton(pref), mutationOpts)) .concat([ waitForMutation(homeModeEl, mutationOpts), waitForMutation(homePageInput, mutationOpts), ]) ); let getHomepage = () => Services.prefs.getCharPref("browser.startup.homepage"); let originalHomepage = getHomepage(); let extensionHomepage = "https://developer.mozilla.org/"; let lockedHomepage = "http://www.yahoo.com"; let lockPrefs = () => { buttonPrefs.forEach(pref => { prefs.setBoolPref(pref, true); prefs.lockPref(pref); }); // Do the homepage last since that's the only pref that triggers a UI update. prefs.setCharPref(homePagePref, lockedHomepage); prefs.lockPref(homePagePref); }; let unlockPrefs = () => { buttonPrefs.forEach(pref => { prefs.unlockPref(pref); prefs.setBoolPref(pref, false); }); // Do the homepage last since that's the only pref that triggers a UI update. prefs.unlockPref(homePagePref); prefs.setCharPref(homePagePref, originalHomepage); }; // Lock or unlock prefs then wait for all mutations to finish. // Expects a bool indicating if we should lock or unlock. let waitForLockMutations = lock => { let mutationsDone = waitForAllMutations(); if (lock) { lockPrefs(); } else { unlockPrefs(); } return mutationsDone; }; ok( originalHomepage != extensionHomepage, "The extension will change the homepage" ); // Install an extension that sets the homepage to MDN. let promise = TestUtils.waitForPrefChange(HOMEPAGE_URL_PREF); let extension = ExtensionTestUtils.loadExtension({ useAddonManager: "permanent", manifest: { version: "1.0", name: "set_homepage", browser_specific_settings: { gecko: { id: ADDON_ID, }, }, chrome_settings_overrides: { homepage: "https://developer.mozilla.org/" }, }, }); await extension.startup(); await promise; // Check that everything is still disabled, homepage didn't change. is( getHomepage(), extensionHomepage, "The reported homepage is set by the extension" ); is( homePageInput.value, extensionHomepage, "The homepage is set by the extension" ); // Lock all of the prefs, wait for the UI to update. await waitForLockMutations(true); // Check that everything is now disabled. is(getHomepage(), lockedHomepage, "The reported homepage is set by the pref"); is(homePageInput.value, lockedHomepage, "The homepage is set by the pref"); is( homePageInput.disabled, true, "The homepage is disabed when the pref is locked" ); buttonPrefs.forEach(pref => { is( getButton(pref).disabled, true, `The ${pref} button is disabled when locked` ); }); let levelOfControl = await ExtensionPreferencesManager.getLevelOfControl( ADDON_ID, HOMEPAGE_OVERRIDE_KEY, PREF_SETTING_TYPE ); is( levelOfControl, "not_controllable", "getLevelOfControl returns not_controllable, the pref is locked." ); // Verify that the UI is selecting the extension's Id in the menulist let unlockedPromise = TestUtils.waitForCondition( () => homeModeEl.value == ADDON_ID, "Homepage menulist value is equal to the extension ID" ); // Unlock the prefs, wait for the UI to update. unlockPrefs(); await unlockedPromise; is( homeModeEl.disabled, false, "the home select element is not disabled when the pref is not locked" ); is( homePageInput.disabled, false, "The homepage is enabled when the pref is unlocked" ); is( getHomepage(), extensionHomepage, "The homepage is reset to extension page" ); levelOfControl = await ExtensionPreferencesManager.getLevelOfControl( ADDON_ID, HOMEPAGE_OVERRIDE_KEY, PREF_SETTING_TYPE ); is( levelOfControl, "controlled_by_this_extension", "getLevelOfControl returns controlled_by_this_extension after prefs are unlocked." ); let setting = await ExtensionPreferencesManager.getSetting( HOMEPAGE_OVERRIDE_KEY ); is( setting.value, extensionHomepage, "The setting value is equal to the extensionHomepage." ); // Uninstall the add-on. promise = TestUtils.waitForPrefChange(HOMEPAGE_URL_PREF); await extension.unload(); await promise; setting = await ExtensionPreferencesManager.getSetting(HOMEPAGE_OVERRIDE_KEY); ok(!setting, "The setting is gone after the addon is uninstalled."); // Check that everything is now enabled again. is( getHomepage(), originalHomepage, "The reported homepage is reset to original value" ); is(homePageInput.value, "", "The homepage is empty"); is( homePageInput.disabled, false, "The homepage is enabled after clearing lock" ); is( homeModeEl.disabled, false, "Homepage menulist is enabled after clearing lock" ); buttonPrefs.forEach(pref => { is( getButton(pref).disabled, false, `The ${pref} button is enabled when unlocked` ); }); // Lock the prefs without an extension. await waitForLockMutations(true); // Check that everything is now disabled. is(getHomepage(), lockedHomepage, "The reported homepage is set by the pref"); is(homePageInput.value, lockedHomepage, "The homepage is set by the pref"); is( homePageInput.disabled, true, "The homepage is disabed when the pref is locked" ); is( homeModeEl.disabled, true, "Homepage menulist is disabled when pref is locked" ); buttonPrefs.forEach(pref => { is( getButton(pref).disabled, true, `The ${pref} button is disabled when locked` ); }); // Unlock the prefs without an extension. await waitForLockMutations(false); // Check that everything is enabled again. is( getHomepage(), originalHomepage, "The homepage is reset to the original value" ); is(homePageInput.value, "", "The homepage is clear after being unlocked"); is( homePageInput.disabled, false, "The homepage is enabled after clearing lock" ); is( homeModeEl.disabled, false, "Homepage menulist is enabled after clearing lock" ); buttonPrefs.forEach(pref => { is( getButton(pref).disabled, false, `The ${pref} button is enabled when unlocked` ); }); BrowserTestUtils.removeTab(gBrowser.selectedTab); }); add_task(async function testExtensionControlledNewTab() { const ADDON_ID = "@set_newtab"; const SECOND_ADDON_ID = "@second_set_newtab"; const DEFAULT_NEWTAB = "about:newtab"; const NEWTAB_CONTROLLED_PREF = "browser.newtab.extensionControlled"; await openPreferencesViaOpenPreferencesAPI("paneHome", { leaveOpen: true }); is( gBrowser.currentURI.spec, "about:preferences#home", "#home should be in the URI for about:preferences" ); let doc = gBrowser.contentDocument; let newTabMenuList = doc.getElementById("newTabMode"); // The new tab page is set to the default. is(AboutNewTab.newTabURL, DEFAULT_NEWTAB, "new tab is set to default"); let promise = TestUtils.waitForCondition( () => newTabMenuList.itemCount == 3, "addon option is added as an option in the menu list" ); // Install an extension that will set the new tab page. let extension = ExtensionTestUtils.loadExtension({ useAddonManager: "permanent", manifest: { version: "1.0", name: "set_newtab", browser_specific_settings: { gecko: { id: ADDON_ID, }, }, chrome_url_overrides: { newtab: "/newtab.html" }, }, }); await extension.startup(); await promise; let addon = await AddonManager.getAddonByID(ADDON_ID); is(newTabMenuList.value, ADDON_ID, "New tab menulist is set to the add-on"); let levelOfControl = await ExtensionPreferencesManager.getLevelOfControl( addon.id, NEW_TAB_KEY, URL_OVERRIDES_TYPE ); is( levelOfControl, "controlled_by_this_extension", "getLevelOfControl returns controlled_by_this_extension." ); let setting = ExtensionSettingsStore.getSetting( URL_OVERRIDES_TYPE, NEW_TAB_KEY ); ok( setting.value.startsWith("moz-extension") && setting.value.endsWith("newtab.html"), "The url_overrides is set by this extension" ); promise = TestUtils.waitForPrefChange(NEWTAB_CONTROLLED_PREF); // Set the menu to the default value newTabMenuList.value = "0"; newTabMenuList.dispatchEvent(new Event("command")); await promise; let newTabPref = Services.prefs.getBoolPref(NEWTAB_CONTROLLED_PREF, false); is(newTabPref, false, "the new tab is not controlled"); levelOfControl = await ExtensionPreferencesManager.getLevelOfControl( addon.id, NEW_TAB_KEY, URL_OVERRIDES_TYPE ); is( levelOfControl, "not_controllable", "getLevelOfControl returns not_controllable." ); setting = ExtensionSettingsStore.getSetting(URL_OVERRIDES_TYPE, NEW_TAB_KEY); ok(!setting.value, "The url_overrides is not set by this extension"); promise = TestUtils.waitForPrefChange(NEWTAB_CONTROLLED_PREF); // Set the menu to a the addon value newTabMenuList.value = ADDON_ID; newTabMenuList.dispatchEvent(new Event("command")); await promise; newTabPref = Services.prefs.getBoolPref(NEWTAB_CONTROLLED_PREF, false); is(newTabPref, true, "the new tab is controlled"); // Add a second extension, ensure it is added to the menulist and selected. promise = TestUtils.waitForCondition( () => newTabMenuList.itemCount == 4, "addon option is added as an option in the menu list" ); let secondExtension = ExtensionTestUtils.loadExtension({ useAddonManager: "permanent", manifest: { version: "1.0", name: "second_set_newtab", browser_specific_settings: { gecko: { id: SECOND_ADDON_ID, }, }, chrome_url_overrides: { newtab: "/newtab2.html" }, }, }); await secondExtension.startup(); await promise; let secondAddon = await AddonManager.getAddonByID(SECOND_ADDON_ID); is( newTabMenuList.value, SECOND_ADDON_ID, "New tab menulist is set to the add-on" ); levelOfControl = await ExtensionPreferencesManager.getLevelOfControl( secondAddon.id, NEW_TAB_KEY, URL_OVERRIDES_TYPE ); is( levelOfControl, "controlled_by_this_extension", "getLevelOfControl returns controlled_by_this_extension." ); setting = ExtensionSettingsStore.getSetting(URL_OVERRIDES_TYPE, NEW_TAB_KEY); ok( setting.value.startsWith("moz-extension") && setting.value.endsWith("newtab2.html"), "The url_overrides is set by the second extension" ); promise = TestUtils.waitForCondition( () => newTabMenuList.itemCount == 3, "addon option is no longer an option in the menu list after disable, even if it was not selected" ); await addon.disable(); await promise; // Ensure that re-enabling an addon adds it back to the menulist promise = TestUtils.waitForCondition( () => newTabMenuList.itemCount == 4, "addon option is added again to the menulist when enabled" ); await addon.enable(); await promise; promise = TestUtils.waitForCondition( () => newTabMenuList.itemCount == 3, "addon option is no longer an option in the menu list after disable" ); await secondAddon.disable(); await promise; promise = TestUtils.waitForCondition( () => newTabMenuList.itemCount == 4, "addon option is added again to the menulist when enabled" ); await secondAddon.enable(); await promise; promise = TestUtils.waitForCondition( () => newTabMenuList.itemCount == 2, "addon options are all removed after disabling all" ); await addon.disable(); await secondAddon.disable(); await promise; is( AboutNewTab.newTabURL, DEFAULT_NEWTAB, "new tab page is set back to default" ); // Cleanup the tabs and add-on. BrowserTestUtils.removeTab(gBrowser.selectedTab); await extension.unload(); await secondExtension.unload(); }); add_task(async function testExtensionControlledWebNotificationsPermission() { let manifest = { manifest_version: 2, name: "TestExtension", version: "1.0", description: "Testing WebNotificationsDisable", browser_specific_settings: { gecko: { id: "@web_notifications_disable" } }, permissions: ["browserSettings"], browser_action: { default_title: "Testing", }, }; await openPreferencesViaOpenPreferencesAPI("privacy", { leaveOpen: true }); await openNotificationsPermissionDialog(); let doc = sitePermissionsDialog.document; let extensionControlledContent = doc.getElementById( "browserNotificationsPermissionExtensionContent" ); // Test that extension content is initially hidden. ok( extensionControlledContent.hidden, "Extension content is initially hidden" ); // Install an extension that will disable web notifications permission. let messageShown = waitForMessageShown( "browserNotificationsPermissionExtensionContent", doc ); let extension = ExtensionTestUtils.loadExtension({ manifest, useAddonManager: "permanent", background() { browser.browserSettings.webNotificationsDisabled.set({ value: true }); browser.test.sendMessage("load-extension"); }, }); await extension.startup(); await extension.awaitMessage("load-extension"); await messageShown; let controlledDesc = extensionControlledContent.querySelector("description"); Assert.deepEqual( doc.l10n.getAttributes(controlledDesc), { id: "extension-controlling-web-notifications", args: { name: "TestExtension", }, }, "The user is notified that an extension is controlling the web notifications permission" ); is( extensionControlledContent.hidden, false, "The extension controlled row is not hidden" ); // Disable the extension. doc.getElementById("disableNotificationsPermissionExtension").click(); // Verify the user is notified how to enable the extension. await waitForEnableMessage(extensionControlledContent.id, doc); is( doc.l10n.getAttributes(controlledDesc.querySelector("label")).id, "extension-controlled-enable", "The user is notified of how to enable the extension again" ); // Verify the enable message can be dismissed. let hidden = waitForMessageHidden(extensionControlledContent.id, doc); let dismissButton = controlledDesc.querySelector("image:last-of-type"); dismissButton.click(); await hidden; // Verify that the extension controlled content in hidden again. is( extensionControlledContent.hidden, true, "The extension controlled row is now hidden" ); await extension.unload(); BrowserTestUtils.removeTab(gBrowser.selectedTab); }); add_task(async function testExtensionControlledHomepageUninstalledAddon() { async function checkHomepageEnabled() { await openPreferencesViaOpenPreferencesAPI("paneHome", { leaveOpen: true }); let doc = gBrowser.contentDocument; is( gBrowser.currentURI.spec, "about:preferences#home", "#home should be in the URI for about:preferences" ); // The homepage is enabled. let homepageInput = doc.getElementById("homePageUrl"); is(homepageInput.disabled, false, "The homepage input is enabled"); is(homepageInput.value, "", "The homepage input is empty"); BrowserTestUtils.removeTab(gBrowser.selectedTab); } await ExtensionSettingsStore.initialize(); // Verify the setting isn't reported as controlled and the inputs are enabled. is( ExtensionSettingsStore.getSetting("prefs", "homepage_override"), null, "The homepage_override is not set" ); await checkHomepageEnabled(); // Disarm any pending writes before we modify the JSONFile directly. await ExtensionSettingsStore._reloadFile(false); // Write out a bad store file. let storeData = { prefs: { homepage_override: { initialValue: "", precedenceList: [ { id: "bad@mochi.test", installDate: 1508802672, value: "https://developer.mozilla.org", enabled: true, }, ], }, }, }; let jsonFileName = "extension-settings.json"; let storePath = PathUtils.join(PathUtils.profileDir, jsonFileName); await IOUtils.writeUTF8(storePath, JSON.stringify(storeData)); // Reload the ExtensionSettingsStore so it will read the file on disk. Don't // finalize the current store since it will overwrite our file. await ExtensionSettingsStore._reloadFile(false); // Verify that the setting is reported as set, but the homepage is still enabled // since there is no matching installed extension. is( ExtensionSettingsStore.getSetting("prefs", "homepage_override").value, "https://developer.mozilla.org", "The homepage_override appears to be set" ); await checkHomepageEnabled(); // Remove the bad store file that we used. await IOUtils.remove(storePath); // Reload the ExtensionSettingsStore again so it clears the data we added. // Don't finalize the current store since it will write out the bad data. await ExtensionSettingsStore._reloadFile(false); is( ExtensionSettingsStore.getSetting("prefs", "homepage_override"), null, "The ExtensionSettingsStore is left empty." ); }); add_task(async function testExtensionControlledTrackingProtection() { const TP_PREF = "privacy.trackingprotection.enabled"; const TP_DEFAULT = false; const EXTENSION_ID = "@set_tp"; const CONTROLLED_LABEL_ID = "contentBlockingTrackingProtectionExtensionContentLabel"; const CONTROLLED_BUTTON_ID = "contentBlockingDisableTrackingProtectionExtension"; let tpEnabledPref = () => Services.prefs.getBoolPref(TP_PREF); await SpecialPowers.pushPrefEnv({ set: [[TP_PREF, TP_DEFAULT]] }); function background() { browser.privacy.websites.trackingProtectionMode.set({ value: "always" }); } function verifyState(isControlled) { is(tpEnabledPref(), isControlled, "TP pref is set to the expected value."); let controlledLabel = doc.getElementById(CONTROLLED_LABEL_ID); let controlledButton = doc.getElementById(CONTROLLED_BUTTON_ID); is( controlledLabel.hidden, !isControlled, "The extension controlled row's visibility is as expected." ); is( controlledButton.hidden, !isControlled, "The disable extension button's visibility is as expected." ); if (isControlled) { let controlledDesc = controlledLabel.querySelector("description"); Assert.deepEqual( doc.l10n.getAttributes(controlledDesc), { id: "extension-controlling-websites-content-blocking-all-trackers", args: { name: "set_tp", }, }, "The user is notified that an extension is controlling TP." ); } is( doc.getElementById("trackingProtectionMenu").disabled, isControlled, "TP control is enabled." ); } await openPreferencesViaOpenPreferencesAPI("panePrivacy", { leaveOpen: true, }); let doc = gBrowser.contentDocument; is( gBrowser.currentURI.spec, "about:preferences#privacy", "#privacy should be in the URI for about:preferences" ); verifyState(false); // Install an extension that sets Tracking Protection. let extension = ExtensionTestUtils.loadExtension({ useAddonManager: "permanent", manifest: { name: "set_tp", browser_specific_settings: { gecko: { id: EXTENSION_ID } }, permissions: ["privacy"], }, background, }); let messageShown = waitForMessageShown(CONTROLLED_LABEL_ID); await extension.startup(); await messageShown; let addon = await AddonManager.getAddonByID(EXTENSION_ID); verifyState(true); await disableExtensionViaClick( CONTROLLED_LABEL_ID, CONTROLLED_BUTTON_ID, doc ); verifyState(false); // Enable the extension so we get the UNINSTALL event, which is needed by // ExtensionPreferencesManager to clean up properly. // TODO: BUG 1408226 await reEnableExtension(addon, CONTROLLED_LABEL_ID); await extension.unload(); BrowserTestUtils.removeTab(gBrowser.selectedTab); }); add_task(async function testExtensionControlledPasswordManager() { const PASSWORD_MANAGER_ENABLED_PREF = "signon.rememberSignons"; const PASSWORD_MANAGER_ENABLED_DEFAULT = true; const CONTROLLED_BUTTON_ID = "disablePasswordManagerExtension"; const CONTROLLED_LABEL_ID = "passwordManagerExtensionContent"; const EXTENSION_ID = "@remember_signons"; let manifest = { manifest_version: 2, name: "testPasswordManagerExtension", version: "1.0", description: "Testing rememberSignons", browser_specific_settings: { gecko: { id: EXTENSION_ID } }, permissions: ["privacy"], browser_action: { default_title: "Testing rememberSignons", }, }; let passwordManagerEnabledPref = () => Services.prefs.getBoolPref(PASSWORD_MANAGER_ENABLED_PREF); await SpecialPowers.pushPrefEnv({ set: [[PASSWORD_MANAGER_ENABLED_PREF, PASSWORD_MANAGER_ENABLED_DEFAULT]], }); is( passwordManagerEnabledPref(), true, "Password manager is enabled by default." ); function verifyState(isControlled) { is( passwordManagerEnabledPref(), !isControlled, "Password manager pref is set to the expected value." ); let controlledLabel = gBrowser.contentDocument.getElementById(CONTROLLED_LABEL_ID); let controlledButton = gBrowser.contentDocument.getElementById(CONTROLLED_BUTTON_ID); is( controlledLabel.hidden, !isControlled, "The extension's controlled row visibility is as expected." ); is( controlledButton.hidden, !isControlled, "The extension's controlled button visibility is as expected." ); if (isControlled) { let controlledDesc = controlledLabel.querySelector("description"); Assert.deepEqual( gBrowser.contentDocument.l10n.getAttributes(controlledDesc), { id: "extension-controlling-password-saving", args: { name: "testPasswordManagerExtension", }, }, "The user is notified that an extension is controlling the remember signons pref." ); } } await openPreferencesViaOpenPreferencesAPI("panePrivacy", { leaveOpen: true, }); info("Verify that no extension is controlling the password manager pref."); verifyState(false); let extension = ExtensionTestUtils.loadExtension({ manifest, useAddonManager: "permanent", background() { browser.privacy.services.passwordSavingEnabled.set({ value: false }); }, }); let messageShown = waitForMessageShown(CONTROLLED_LABEL_ID); await extension.startup(); await messageShown; info( "Verify that the test extension is controlling the password manager pref." ); verifyState(true); info("Verify that the extension shows as controlled when loaded again."); BrowserTestUtils.removeTab(gBrowser.selectedTab); await openPreferencesViaOpenPreferencesAPI("panePrivacy", { leaveOpen: true, }); verifyState(true); await disableExtensionViaClick( CONTROLLED_LABEL_ID, CONTROLLED_BUTTON_ID, gBrowser.contentDocument ); info( "Verify that disabling the test extension removes the lock on the password manager pref." ); verifyState(false); await extension.unload(); BrowserTestUtils.removeTab(gBrowser.selectedTab); }); add_task(async function testExtensionControlledProxyConfig() { const proxySvc = Ci.nsIProtocolProxyService; const PROXY_DEFAULT = proxySvc.PROXYCONFIG_SYSTEM; const EXTENSION_ID = "@set_proxy"; const CONTROLLED_SECTION_ID = "proxyExtensionContent"; const CONTROLLED_BUTTON_ID = "disableProxyExtension"; const CONNECTION_SETTINGS_DESC_ID = "connectionSettingsDescription"; const PANEL_URL = "chrome://browser/content/preferences/dialogs/connection.xhtml"; await SpecialPowers.pushPrefEnv({ set: [[PROXY_PREF, PROXY_DEFAULT]] }); function background() { browser.proxy.settings.set({ value: { proxyType: "none" } }); } function expectedConnectionSettingsMessage(doc, isControlled) { return isControlled ? "extension-controlling-proxy-config" : "network-proxy-connection-description"; } function connectionSettingsMessagePromise(doc, isControlled) { return waitForMessageContent( CONNECTION_SETTINGS_DESC_ID, expectedConnectionSettingsMessage(doc, isControlled), doc ); } function verifyProxyState(doc, isControlled) { let isPanel = doc.getElementById(CONTROLLED_BUTTON_ID); is( proxyType === proxySvc.PROXYCONFIG_DIRECT, isControlled, "Proxy pref is set to the expected value." ); if (isPanel) { let controlledSection = doc.getElementById(CONTROLLED_SECTION_ID); is( controlledSection.hidden, !isControlled, "The extension controlled row's visibility is as expected." ); if (isPanel) { is( doc.getElementById(CONTROLLED_BUTTON_ID).hidden, !isControlled, "The disable extension button's visibility is as expected." ); } if (isControlled) { let controlledDesc = controlledSection.querySelector("description"); Assert.deepEqual( doc.l10n.getAttributes(controlledDesc), { id: "extension-controlling-proxy-config", args: { name: "set_proxy", }, }, "The user is notified that an extension is controlling proxy settings." ); } function getProxyControls() { let controlGroup = doc.getElementById("networkProxyType"); let manualControlContainer = controlGroup.querySelector("#proxy-grid"); return { manualControls: [ ...manualControlContainer.querySelectorAll( "label[data-l10n-id]:not([control=networkProxyNone])" ), ...manualControlContainer.querySelectorAll("input"), ...manualControlContainer.querySelectorAll("checkbox"), ...doc.querySelectorAll("#networkProxySOCKSVersion > radio"), ], pacControls: [doc.getElementById("networkProxyAutoconfigURL")], otherControls: [ doc.querySelector("label[control=networkProxyNone]"), doc.getElementById("networkProxyNone"), ...controlGroup.querySelectorAll(":scope > radio"), ...doc.querySelectorAll("#ConnectionsDialogPane > checkbox"), ], }; } let controlState = isControlled ? "disabled" : "enabled"; let controls = getProxyControls(); for (let element of controls.manualControls) { let disabled = isControlled || proxyType !== proxySvc.PROXYCONFIG_MANUAL; is( element.disabled, disabled, `Manual proxy controls should be ${controlState} - control: ${element.outerHTML}.` ); } for (let element of controls.pacControls) { let disabled = isControlled || proxyType !== proxySvc.PROXYCONFIG_PAC; is( element.disabled, disabled, `PAC proxy controls should be ${controlState} - control: ${element.outerHTML}.` ); } for (let element of controls.otherControls) { is( element.disabled, isControlled, `Other proxy controls should be ${controlState} - control: ${element.outerHTML}.` ); } } else { let elem = doc.getElementById(CONNECTION_SETTINGS_DESC_ID); is( doc.l10n.getAttributes(elem).id, expectedConnectionSettingsMessage(doc, isControlled), "The connection settings description is as expected." ); } } async function reEnableProxyExtension(addon) { let messageChanged = connectionSettingsMessagePromise(mainDoc, true); await addon.enable(); await messageChanged; } async function openProxyPanel() { let panel = await openAndLoadSubDialog(PANEL_URL); let closingPromise = BrowserTestUtils.waitForEvent( panel.document.getElementById("ConnectionsDialog"), "dialogclosing" ); ok(panel, "Proxy panel opened."); return { panel, closingPromise }; } async function closeProxyPanel(panelObj) { let dialog = panelObj.panel.document.getElementById("ConnectionsDialog"); dialog.cancelDialog(); let panelClosingEvent = await panelObj.closingPromise; ok(panelClosingEvent, "Proxy panel closed."); } await openPreferencesViaOpenPreferencesAPI("paneGeneral", { leaveOpen: true, }); let mainDoc = gBrowser.contentDocument; is( gBrowser.currentURI.spec, "about:preferences#general", "#general should be in the URI for about:preferences" ); verifyProxyState(mainDoc, false); // Open the connections panel. let panelObj = await openProxyPanel(); let panelDoc = panelObj.panel.document; verifyProxyState(panelDoc, false); await closeProxyPanel(panelObj); verifyProxyState(mainDoc, false); // Install an extension that controls proxy settings. The extension needs // incognitoOverride because controlling the proxy.settings requires private // browsing access. let extension = ExtensionTestUtils.loadExtension({ incognitoOverride: "spanning", useAddonManager: "permanent", manifest: { name: "set_proxy", browser_specific_settings: { gecko: { id: EXTENSION_ID } }, permissions: ["proxy"], }, background, }); let messageChanged = connectionSettingsMessagePromise(mainDoc, true); await extension.startup(); await messageChanged; let addon = await AddonManager.getAddonByID(EXTENSION_ID); verifyProxyState(mainDoc, true); messageChanged = connectionSettingsMessagePromise(mainDoc, false); panelObj = await openProxyPanel(); panelDoc = panelObj.panel.document; verifyProxyState(panelDoc, true); await disableExtensionViaClick( CONTROLLED_SECTION_ID, CONTROLLED_BUTTON_ID, panelDoc ); verifyProxyState(panelDoc, false); await closeProxyPanel(panelObj); await messageChanged; verifyProxyState(mainDoc, false); await reEnableProxyExtension(addon); verifyProxyState(mainDoc, true); messageChanged = connectionSettingsMessagePromise(mainDoc, false); panelObj = await openProxyPanel(); panelDoc = panelObj.panel.document; verifyProxyState(panelDoc, true); await disableExtensionViaClick( CONTROLLED_SECTION_ID, CONTROLLED_BUTTON_ID, panelDoc ); verifyProxyState(panelDoc, false); await closeProxyPanel(panelObj); await messageChanged; verifyProxyState(mainDoc, false); // Enable the extension so we get the UNINSTALL event, which is needed by // ExtensionPreferencesManager to clean up properly. // TODO: BUG 1408226 await reEnableProxyExtension(addon); await extension.unload(); BrowserTestUtils.removeTab(gBrowser.selectedTab); }); // Test that the newtab menu selection is correct when loading about:preferences add_task(async function testMenuSyncFromPrefs() { const DEFAULT_NEWTAB = "about:newtab"; await openPreferencesViaOpenPreferencesAPI("paneHome", { leaveOpen: true }); is( gBrowser.currentURI.spec, "about:preferences#home", "#home should be in the URI for about:preferences" ); let doc = gBrowser.contentDocument; let newTabMenuList = doc.getElementById("newTabMode"); // The new tab page is set to the default. is(AboutNewTab.newTabURL, DEFAULT_NEWTAB, "new tab is set to default"); is(newTabMenuList.value, "0", "New tab menulist is set to the default"); newTabMenuList.value = "1"; newTabMenuList.dispatchEvent(new Event("command")); is(newTabMenuList.value, "1", "New tab menulist is set to blank"); gBrowser.reloadTab(gBrowser.selectedTab); await TestUtils.waitForCondition( () => gBrowser.contentDocument.getElementById("newTabMode"), "wait until element exists in new contentDoc" ); is( gBrowser.contentDocument.getElementById("newTabMode").value, "1", "New tab menulist is still set to blank" ); // Cleanup newTabMenuList.value = "0"; newTabMenuList.dispatchEvent(new Event("command")); is(AboutNewTab.newTabURL, DEFAULT_NEWTAB, "new tab is set to default"); BrowserTestUtils.removeTab(gBrowser.selectedTab); });