Bug 1322308 - Allow WebExtensions to read the overriden homepage and newTab values, r?mixedpuppy draft
authorBob Silverberg <bsilverberg@mozilla.com>
Mon, 17 Jul 2017 14:16:02 -0400
changeset 620615 13ef1991b63011f493cffc86ec11732d38925c51
parent 619581 52285ea5e54c73d3ed824544cef2ee3f195f05e6
child 640765 6e047e154f06a3da7af1bb300c09970869778bb1
push id72110
push userbmo:bob.silverberg@gmail.com
push dateThu, 03 Aug 2017 18:09:22 +0000
reviewersmixedpuppy
bugs1322308
milestone57.0a1
Bug 1322308 - Allow WebExtensions to read the overriden homepage and newTab values, r?mixedpuppy This introduces browser.chrome_settings_overrides.getHomepage() which will return the value of the overridden home page and browser.chrome_url_overrides.getNewTabPage() which will return the value of the overridden new tab page. MozReview-Commit-ID: A9vJP2QIaoA
browser/components/extensions/ext-browser.js
browser/components/extensions/ext-chrome-settings-overrides.js
browser/components/extensions/ext-url-overrides.js
browser/components/extensions/schemas/chrome_settings_overrides.json
browser/components/extensions/schemas/url_overrides.json
browser/components/extensions/test/browser/browser-common.ini
browser/components/extensions/test/browser/browser_ext_chrome_settings_overrides_home.js
browser/components/extensions/test/browser/browser_ext_url_overrides_home.js
browser/components/extensions/test/xpcshell/test_ext_url_overrides_newtab.js
toolkit/components/extensions/ExtensionPreferencesManager.jsm
toolkit/components/extensions/test/xpcshell/test_ext_extensionPreferencesManager.js
--- a/browser/components/extensions/ext-browser.js
+++ b/browser/components/extensions/ext-browser.js
@@ -99,19 +99,22 @@ extensions.registerModules({
     schema: "chrome://browser/content/schemas/browsing_data.json",
     scopes: ["addon_parent"],
     paths: [
       ["browsingData"],
     ],
   },
   chrome_settings_overrides: {
     url: "chrome://browser/content/ext-chrome-settings-overrides.js",
-    scopes: [],
+    scopes: ["addon_parent"],
     schema: "chrome://browser/content/schemas/chrome_settings_overrides.json",
     manifest: ["chrome_settings_overrides"],
+    paths: [
+      ["chrome_settings_overrides"],
+    ],
   },
   commands: {
     url: "chrome://browser/content/ext-commands.js",
     schema: "chrome://browser/content/schemas/commands.json",
     scopes: ["addon_parent"],
     manifest: ["commands"],
     paths: [
       ["commands"],
@@ -214,23 +217,23 @@ extensions.registerModules({
   tabs: {
     url: "chrome://browser/content/ext-tabs.js",
     schema: "chrome://browser/content/schemas/tabs.json",
     scopes: ["addon_parent"],
     paths: [
       ["tabs"],
     ],
   },
-  urlOverrides: {
+  chrome_url_overrides: {
     url: "chrome://browser/content/ext-url-overrides.js",
     schema: "chrome://browser/content/schemas/url_overrides.json",
     scopes: ["addon_parent"],
     manifest: ["chrome_url_overrides"],
     paths: [
-      ["urlOverrides"],
+      ["chrome_url_overrides"],
     ],
   },
   windows: {
     url: "chrome://browser/content/ext-windows.js",
     schema: "chrome://browser/content/schemas/windows.json",
     scopes: ["addon_parent"],
     paths: [
       ["windows"],
--- a/browser/components/extensions/ext-chrome-settings-overrides.js
+++ b/browser/components/extensions/ext-chrome-settings-overrides.js
@@ -63,16 +63,17 @@ this.chrome_settings_overrides = class e
             Services.search.moveEngine(engine, index);
           }
         }
       } catch (e) {
         Components.utils.reportError(e);
       }
     }
   }
+
   async onShutdown(reason) {
     let {extension} = this;
     if (reason == "ADDON_DISABLE" ||
         reason == "ADDON_UNINSTALL") {
       if (extension.manifest.chrome_settings_overrides.search_provider) {
         await searchInitialized();
         let engines = Services.search.getEnginesByExtensionID(extension.id);
         for (let engine of engines) {
@@ -80,16 +81,30 @@ this.chrome_settings_overrides = class e
             Services.search.removeEngine(engine);
           } catch (e) {
             Components.utils.reportError(e);
           }
         }
       }
     }
   }
+
+  getAPI(context) {
+    return {
+      chrome_settings_overrides: {
+        getHomepage: async () => {
+          let homepageSetting = await ExtensionPreferencesManager.getSetting("homepage_override");
+          if (homepageSetting) {
+            return homepageSetting.value;
+          }
+          return null;
+        },
+      },
+    };
+  }
 };
 
 ExtensionPreferencesManager.addSetting("homepage_override", {
   prefNames: [
     "browser.startup.homepage",
   ],
   setCallback(value) {
     return {
--- a/browser/components/extensions/ext-url-overrides.js
+++ b/browser/components/extensions/ext-url-overrides.js
@@ -9,17 +9,17 @@ XPCOMUtils.defineLazyModuleGetter(this, 
 
 XPCOMUtils.defineLazyServiceGetter(this, "aboutNewTabService",
                                    "@mozilla.org/browser/aboutnewtab-service;1",
                                    "nsIAboutNewTabService");
 
 const STORE_TYPE = "url_overrides";
 const NEW_TAB_SETTING_NAME = "newTabURL";
 
-this.urlOverrides = class extends ExtensionAPI {
+this.chrome_url_overrides = class extends ExtensionAPI {
   processNewTabSetting(action) {
     let {extension} = this;
     let item = ExtensionSettingsStore[action](extension, STORE_TYPE, NEW_TAB_SETTING_NAME);
     if (item) {
       aboutNewTabService.newTabURL = item.value || item.initialValue;
     }
   }
 
@@ -64,9 +64,24 @@ this.urlOverrides = class extends Extens
       }
 
       // Set the newTabURL to the current value of the setting.
       if (item) {
         aboutNewTabService.newTabURL = item.value || item.initialValue;
       }
     }
   }
+
+  getAPI(context) {
+    return {
+      chrome_url_overrides: {
+        getNewTabPage: async () => {
+          await ExtensionSettingsStore.initialize();
+          let newTabPageSetting = ExtensionSettingsStore.getSetting(STORE_TYPE, NEW_TAB_SETTING_NAME);
+          if (newTabPageSetting) {
+            return newTabPageSetting.value;
+          }
+          return null;
+        },
+      },
+    };
+  }
 };
--- a/browser/components/extensions/schemas/chrome_settings_overrides.json
+++ b/browser/components/extensions/schemas/chrome_settings_overrides.json
@@ -103,10 +103,23 @@
                   }
                 }
               }
             }
           }
         }
       }
     ]
+  },
+  {
+    "namespace": "chrome_settings_overrides",
+    "description": "Use the <code>chrome_settings_overrides</code> API to read information about settings that have been overidden.",
+    "functions": [
+      {
+        "name": "getHomepage",
+        "type": "function",
+        "description": "Returns the current value of the home page, if overriden by an extension.",
+        "async": true,
+        "parameters": []
+      }
+    ]
   }
 ]
--- a/browser/components/extensions/schemas/url_overrides.json
+++ b/browser/components/extensions/schemas/url_overrides.json
@@ -26,10 +26,23 @@
                 "optional": true,
                 "preprocess": "localize"
               }
             }
           }
         }
       }
     ]
+  },
+  {
+    "namespace": "chrome_url_overrides",
+    "description": "Use the <code>chrome_url_overrides</code> API to read information about built-in URLs that have been overidden.",
+    "functions": [
+      {
+        "name": "getNewTabPage",
+        "type": "function",
+        "description": "Returns the current value of the new tab page, if overriden by an extension.",
+        "async": true,
+        "parameters": []
+      }
+    ]
   }
 ]
\ No newline at end of file
--- a/browser/components/extensions/test/browser/browser-common.ini
+++ b/browser/components/extensions/test/browser/browser-common.ini
@@ -43,16 +43,17 @@ skip-if = (os == 'win' && !debug) # bug 
 [browser_ext_browserAction_popup_resize.js]
 [browser_ext_browserAction_simple.js]
 [browser_ext_browserAction_telemetry.js]
 [browser_ext_browserAction_theme_icons.js]
 [browser_ext_browsingData_formData.js]
 [browser_ext_browsingData_history.js]
 [browser_ext_browsingData_pluginData.js]
 [browser_ext_browsingData_serviceWorkers.js]
+[browser_ext_chrome_settings_overrides_home.js]
 [browser_ext_commands_execute_browser_action.js]
 [browser_ext_commands_execute_page_action.js]
 [browser_ext_commands_execute_sidebar_action.js]
 [browser_ext_commands_getAll.js]
 [browser_ext_commands_onCommand.js]
 [browser_ext_contentscript_connect.js]
 [browser_ext_contextMenus.js]
 [browser_ext_contextMenus_checkboxes.js]
@@ -141,17 +142,16 @@ skip-if = debug || asan # Bug 1354681
 [browser_ext_tabs_sendMessage.js]
 [browser_ext_tabs_cookieStoreId.js]
 [browser_ext_tabs_update.js]
 [browser_ext_tabs_zoom.js]
 [browser_ext_tabs_update_url.js]
 [browser_ext_themes_icons.js]
 [browser_ext_themes_validation.js]
 [browser_ext_url_overrides_newtab.js]
-[browser_ext_url_overrides_home.js]
 [browser_ext_user_events.js]
 [browser_ext_webRequest.js]
 [browser_ext_webNavigation_frameId0.js]
 [browser_ext_webNavigation_getFrames.js]
 [browser_ext_webNavigation_onCreatedNavigationTarget.js]
 [browser_ext_webNavigation_onCreatedNavigationTarget_contextmenu.js]
 [browser_ext_webNavigation_onCreatedNavigationTarget_window_open.js]
 [browser_ext_webNavigation_urlbar_transitions.js]
rename from browser/components/extensions/test/browser/browser_ext_url_overrides_home.js
rename to browser/components/extensions/test/browser/browser_ext_chrome_settings_overrides_home.js
--- a/browser/components/extensions/test/browser/browser_ext_url_overrides_home.js
+++ b/browser/components/extensions/test/browser/browser_ext_chrome_settings_overrides_home.js
@@ -11,91 +11,127 @@ XPCOMUtils.defineLazyModuleGetter(this, 
 // Named this way so they correspond to the extensions
 const HOME_URI_2 = "http://example.com/";
 const HOME_URI_3 = "http://example.org/";
 const HOME_URI_4 = "http://example.net/";
 
 add_task(async function test_multiple_extensions_overriding_home_page() {
   let defaultHomePage = Preferences.get("browser.startup.homepage");
 
+  function background() {
+    browser.test.onMessage.addListener(async msg => {
+      let homepage = await browser.chrome_settings_overrides.getHomepage();
+      browser.test.sendMessage("homepage", homepage);
+    });
+  }
+
   let ext1 = ExtensionTestUtils.loadExtension({
     manifest: {"chrome_settings_overrides": {}},
     useAddonManager: "temporary",
+    background,
   });
 
   let ext2 = ExtensionTestUtils.loadExtension({
     manifest: {"chrome_settings_overrides": {homepage: HOME_URI_2}},
     useAddonManager: "temporary",
+    background,
   });
 
   let ext3 = ExtensionTestUtils.loadExtension({
     manifest: {"chrome_settings_overrides": {homepage: HOME_URI_3}},
     useAddonManager: "temporary",
+    background,
   });
 
   let ext4 = ExtensionTestUtils.loadExtension({
     manifest: {"chrome_settings_overrides": {homepage: HOME_URI_4}},
     useAddonManager: "temporary",
   });
 
+  let ext5 = ExtensionTestUtils.loadExtension({
+    manifest: {"chrome_settings_overrides": {}},
+    useAddonManager: "temporary",
+    background,
+  });
+
+  async function checkGetHomepage(ext, expected) {
+    ext.sendMessage("checkHomepage");
+    let homepage = await ext.awaitMessage("homepage");
+    is(homepage, expected, `getHomepage returns the expected value: ${expected}.`);
+  }
+
   await ext1.startup();
 
-  is(Preferences.get("browser.startup.homepage"), defaultHomePage,
-     "Home url should be the default");
+  await checkGetHomepage(ext1, null);
 
   // Because we are expecting the pref to change when we start or unload, we
   // need to wait on a pref change.  This is because the pref management is
   // async and can happen after the startup/unload is finished.
   let prefPromise = promisePrefChangeObserved("browser.startup.homepage");
   await ext2.startup();
   await prefPromise;
 
   ok(Preferences.get("browser.startup.homepage").endsWith(HOME_URI_2),
      "Home url should be overridden by the second extension.");
 
+  await checkGetHomepage(ext2, HOME_URI_2);
+
   // Because we are unloading an earlier extension, browser.startup.homepage won't change
   await ext1.unload();
 
+  await checkGetHomepage(ext2, HOME_URI_2);
+
   ok(Preferences.get("browser.startup.homepage").endsWith(HOME_URI_2),
      "Home url should be overridden by the second extension.");
 
   prefPromise = promisePrefChangeObserved("browser.startup.homepage");
   await ext3.startup();
   await prefPromise;
 
   ok(Preferences.get("browser.startup.homepage").endsWith(HOME_URI_3),
      "Home url should be overridden by the third extension.");
 
+  await checkGetHomepage(ext2, HOME_URI_3);
+
   // Because we are unloading an earlier extension, browser.startup.homepage won't change
   await ext2.unload();
 
   ok(Preferences.get("browser.startup.homepage").endsWith(HOME_URI_3),
      "Home url should be overridden by the third extension.");
 
+  await checkGetHomepage(ext3, HOME_URI_3);
+
   prefPromise = promisePrefChangeObserved("browser.startup.homepage");
   await ext4.startup();
   await prefPromise;
 
   ok(Preferences.get("browser.startup.homepage").endsWith(HOME_URI_4),
      "Home url should be overridden by the third extension.");
 
+  await checkGetHomepage(ext3, HOME_URI_4);
 
   prefPromise = promisePrefChangeObserved("browser.startup.homepage");
   await ext4.unload();
   await prefPromise;
 
   ok(Preferences.get("browser.startup.homepage").endsWith(HOME_URI_3),
      "Home url should be overridden by the third extension.");
 
+  await checkGetHomepage(ext3, HOME_URI_3);
+
   prefPromise = promisePrefChangeObserved("browser.startup.homepage");
   await ext3.unload();
   await prefPromise;
 
   is(Preferences.get("browser.startup.homepage"), defaultHomePage,
      "Home url should be reset to default");
+
+  await ext5.startup();
+  await checkGetHomepage(ext5, null);
+  await ext5.unload();
 });
 
 const HOME_URI_1 = "http://example.com/";
 const USER_URI = "http://example.edu/";
 
 add_task(async function test_extension_setting_home_page_back() {
   let defaultHomePage = Preferences.get("browser.startup.homepage");
 
--- a/browser/components/extensions/test/xpcshell/test_ext_url_overrides_newtab.js
+++ b/browser/components/extensions/test/xpcshell/test_ext_url_overrides_newtab.js
@@ -34,90 +34,122 @@ function awaitEvent(eventName) {
   });
 }
 
 add_task(async function test_multiple_extensions_overriding_newtab_page() {
   const NEWTAB_URI_1 = "webext-newtab-1.html";
   const NEWTAB_URI_2 = "webext-newtab-2.html";
   const EXT_2_ID = "ext2@tests.mozilla.org";
 
+  function background() {
+    browser.test.onMessage.addListener(async msg => {
+      let newTabPage = await browser.chrome_url_overrides.getNewTabPage();
+      browser.test.sendMessage("newTabPage", newTabPage);
+    });
+  }
+
+  async function checkGetNewTabPage(ext, expected) {
+    ext.sendMessage("checkNewTabPage");
+    let newTabPage = await ext.awaitMessage("newTabPage");
+
+    if (expected) {
+      ok(newTabPage.endsWith(expected),
+         `getNewTabPage returns the expected value ending with: ${expected}.`);
+      return;
+    }
+    equal(newTabPage, expected, `getNewTabPage returns the expected value: ${expected}.`);
+  }
+
   equal(aboutNewTabService.newTabURL, "about:newtab",
      "Default newtab url is about:newtab");
 
   await promiseStartupManager();
 
   let ext1 = ExtensionTestUtils.loadExtension({
     manifest: {"chrome_url_overrides": {}},
+    background,
     useAddonManager: "temporary",
   });
 
   let ext2 = ExtensionTestUtils.loadExtension({
     manifest: {
       "chrome_url_overrides": {newtab: NEWTAB_URI_1},
       applications: {
         gecko: {
           id: EXT_2_ID,
         },
       },
     },
+    background,
     useAddonManager: "temporary",
   });
 
   let ext3 = ExtensionTestUtils.loadExtension({
     manifest: {"chrome_url_overrides": {newtab: NEWTAB_URI_2}},
+    background,
     useAddonManager: "temporary",
   });
 
   await ext1.startup();
   equal(aboutNewTabService.newTabURL, "about:newtab",
        "Default newtab url is still about:newtab");
 
+  await checkGetNewTabPage(ext1, null);
+
   await ext2.startup();
   ok(aboutNewTabService.newTabURL.endsWith(NEWTAB_URI_1),
      "Newtab url is overriden by the second extension.");
+  await checkGetNewTabPage(ext1, NEWTAB_URI_1);
 
   // Disable the second extension.
   let addon = await AddonManager.getAddonByID(EXT_2_ID);
   let disabledPromise = awaitEvent("shutdown");
   addon.userDisabled = true;
   await disabledPromise;
   equal(aboutNewTabService.newTabURL, "about:newtab",
         "Newtab url is about:newtab after second extension is disabled.");
+  await checkGetNewTabPage(ext1, null);
 
   // Re-enable the second extension.
   let enabledPromise = awaitEvent("ready");
   addon.userDisabled = false;
   await enabledPromise;
   ok(aboutNewTabService.newTabURL.endsWith(NEWTAB_URI_1),
      "Newtab url is overriden by the second extension.");
+  await checkGetNewTabPage(ext1, NEWTAB_URI_1);
 
   await ext1.unload();
   ok(aboutNewTabService.newTabURL.endsWith(NEWTAB_URI_1),
      "Newtab url is still overriden by the second extension.");
+  await checkGetNewTabPage(ext2, NEWTAB_URI_1);
 
   await ext3.startup();
   ok(aboutNewTabService.newTabURL.endsWith(NEWTAB_URI_2),
    "Newtab url is overriden by the third extension.");
+  await checkGetNewTabPage(ext2, NEWTAB_URI_2);
 
   // Disable the second extension.
   disabledPromise = awaitEvent("shutdown");
   addon.userDisabled = true;
   await disabledPromise;
   ok(aboutNewTabService.newTabURL.endsWith(NEWTAB_URI_2),
    "Newtab url is still overriden by the third extension.");
+  await checkGetNewTabPage(ext3, NEWTAB_URI_2);
 
   // Re-enable the second extension.
   enabledPromise = awaitEvent("ready");
   addon.userDisabled = false;
   await enabledPromise;
   ok(aboutNewTabService.newTabURL.endsWith(NEWTAB_URI_2),
    "Newtab url is still overriden by the third extension.");
+  await checkGetNewTabPage(ext3, NEWTAB_URI_2);
 
   await ext3.unload();
   ok(aboutNewTabService.newTabURL.endsWith(NEWTAB_URI_1),
      "Newtab url reverts to being overriden by the second extension.");
+  await checkGetNewTabPage(ext2, NEWTAB_URI_1);
 
   await ext2.unload();
   equal(aboutNewTabService.newTabURL, "about:newtab",
      "Newtab url is reset to about:newtab");
 
   await promiseShutdownManager();
 });
--- a/toolkit/components/extensions/ExtensionPreferencesManager.jsm
+++ b/toolkit/components/extensions/ExtensionPreferencesManager.jsm
@@ -292,16 +292,29 @@ this.ExtensionPreferencesManager = {
     let removePromises = [];
     for (let name of settings) {
       removePromises.push(this.removeSetting(extension, name));
     }
     await Promise.all(removePromises);
   },
 
   /**
+   * Return the currently active value for a setting.
+   *
+   * @param {string} name
+   *        The unique id of the setting.
+   *
+   * @returns {Object} The current setting object.
+   */
+  async getSetting(name) {
+    await ExtensionSettingsStore.initialize();
+    return ExtensionSettingsStore.getSetting(STORE_TYPE, name);
+  },
+
+  /**
    * Return the levelOfControl for a setting / extension combo.
    * This queries the levelOfControl from the ExtensionSettingsStore and also
    * takes into account whether any of the setting's preferences are locked.
    *
    * @param {Extension} extension
    *        The extension for which levelOfControl is being requested.
    * @param {string} name
    *        The unique id of the setting.
--- a/toolkit/components/extensions/test/xpcshell/test_ext_extensionPreferencesManager.js
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_extensionPreferencesManager.js
@@ -114,16 +114,19 @@ add_task(async function test_preference_
     checkPrefs(settingObj, newValue1,
       "setSetting sets the prefs for the first extension.");
     levelOfControl = await ExtensionPreferencesManager.getLevelOfControl(extensions[1], setting);
     equal(
       levelOfControl,
       "controlled_by_this_extension",
       "getLevelOfControl returns correct levelOfControl when a pref has been set.");
 
+    let checkSetting = await ExtensionPreferencesManager.getSetting(setting);
+    equal(checkSetting.value, newValue1, "getSetting returns the expected value.");
+
     let newValue2 = "newValue2";
     prefsChanged = await ExtensionPreferencesManager.setSetting(extensions[0], setting, newValue2);
     ok(!prefsChanged, "setSetting returns false when the pref(s) have not been set.");
     checkPrefs(settingObj, newValue1,
       "setSetting does not set the pref(s) for an earlier extension.");
 
     prefsChanged = await ExtensionPreferencesManager.disableSetting(extensions[0], setting);
     ok(!prefsChanged, "disableSetting returns false when the pref(s) have not been set.");
@@ -161,16 +164,19 @@ add_task(async function test_preference_
       "removeSetting sets the pref(s) to the next value when removing the top extension.");
 
     prefsChanged = await ExtensionPreferencesManager.removeSetting(extensions[0], setting);
     ok(prefsChanged, "removeSetting returns true when the pref(s) have been set.");
     for (let i = 0; i < settingObj.prefNames.length; i++) {
       equal(Preferences.get(settingObj.prefNames[i]), settingObj.initalValues[i],
         "removeSetting sets the pref(s) to the initial value(s) when removing the last extension.");
     }
+
+    checkSetting = await ExtensionPreferencesManager.getSetting(setting);
+    equal(checkSetting, null, "getSetting returns null when nothing has been set.");
   }
 
   // Tests for unsetAll.
   let newValue3 = "newValue3";
   for (let setting in SETTINGS) {
     let settingObj = SETTINGS[setting];
     await ExtensionPreferencesManager.setSetting(extensions[0], setting, newValue3);
     checkPrefs(settingObj, newValue3, "setSetting set the pref.");