summaryrefslogtreecommitdiffstats
path: root/browser/components/preferences/tests/browser_extension_controlled.js
diff options
context:
space:
mode:
Diffstat (limited to 'browser/components/preferences/tests/browser_extension_controlled.js')
-rw-r--r--browser/components/preferences/tests/browser_extension_controlled.js1447
1 files changed, 1447 insertions, 0 deletions
diff --git a/browser/components/preferences/tests/browser_extension_controlled.js b/browser/components/preferences/tests/browser_extension_controlled.js
new file mode 100644
index 0000000000..6cec9ba93a
--- /dev/null
+++ b/browser/components/preferences/tests/browser_extension_controlled.js
@@ -0,0 +1,1447 @@
+/* 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);
+});