Bug 1341458 - Move homepage to chrome_settings_overrides. r?aswan, kmag draft
authorMichael Kaply <mozilla@kaply.com>
Fri, 03 Mar 2017 12:02:10 -0600
changeset 493447 704682559f5db0ab2e1bf8d00b2db09aaa5938b7
parent 492009 d29f84406483c721a13cf9a52936ecced0c5c98a
child 547866 f62777dc24481e6eb56e6b50c3a0ce22cc324960
push id47767
push usermixedpuppy@gmail.com
push dateSat, 04 Mar 2017 01:49:37 +0000
reviewersaswan, kmag
bugs1341458
milestone54.0a1
Bug 1341458 - Move homepage to chrome_settings_overrides. r?aswan, kmag MozReview-Commit-ID: 2P7H39uAQef
browser/components/extensions/ext-chrome-settings-overrides.js
browser/components/extensions/ext-url-overrides.js
browser/components/extensions/extensions-browser.manifest
browser/components/extensions/jar.mn
browser/components/extensions/schemas/chrome_settings_overrides.json
browser/components/extensions/schemas/jar.mn
browser/components/extensions/schemas/url_overrides.json
browser/components/extensions/test/browser/browser-common.ini
browser/components/extensions/test/browser/browser_ext_url_overrides_all.js
browser/components/extensions/test/browser/browser_ext_url_overrides_home.js
browser/components/extensions/test/browser/browser_ext_url_overrides_newtab.js
browser/components/extensions/test/browser/head.js
new file mode 100644
--- /dev/null
+++ b/browser/components/extensions/ext-chrome-settings-overrides.js
@@ -0,0 +1,35 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "ExtensionPreferencesManager",
+                                  "resource://gre/modules/ExtensionPreferencesManager.jsm");
+
+/* eslint-disable mozilla/balanced-listeners */
+extensions.on("manifest_chrome_settings_overrides", (type, directive, extension, manifest) => {
+  if ("homepage" in manifest.chrome_settings_overrides &&
+      manifest.chrome_settings_overrides.homepage) {
+    ExtensionPreferencesManager.addSetting("homepage_override", {
+      prefNames: [
+        "browser.startup.homepage",
+      ],
+      setCallback(value) {
+        return {
+          "browser.startup.homepage": value,
+        };
+      },
+    });
+    ExtensionPreferencesManager.setSetting(extension, "homepage_override",
+                                           manifest.chrome_settings_overrides.homepage);
+  }
+});
+
+extensions.on("shutdown", (type, extension) => {
+  ExtensionPreferencesManager.unsetSetting(extension, "homepage_override");
+});
+/* eslint-enable mozilla/balanced-listeners */
--- a/browser/components/extensions/ext-url-overrides.js
+++ b/browser/components/extensions/ext-url-overrides.js
@@ -3,102 +3,47 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
 XPCOMUtils.defineLazyServiceGetter(this, "aboutNewTabService",
                                    "@mozilla.org/browser/aboutnewtab-service;1",
                                    "nsIAboutNewTabService");
-XPCOMUtils.defineLazyModuleGetter(this, "Preferences",
-                                  "resource://gre/modules/Preferences.jsm");
 
 // Bug 1320736 tracks creating a generic precedence manager for handling
 // multiple addons modifying the same properties, and bug 1330494 has been filed
 // to track utilizing this manager for chrome_url_overrides. Until those land,
 // the edge cases surrounding multiple addons using chrome_url_overrides will
 // be ignored and precedence will be first come, first serve.
 let overrides = {
   // A queue of extensions in line to override the newtab page (sorted oldest to newest).
   newtab: [],
-  // A queue of extensions in line to override the home page (sorted oldest to newest).
-  home: [],
 };
 
-/**
- * Resets the specified page to its default value.
- *
- * @param {string} page The page to override. Accepted values are "newtab" and "home".
- */
-function resetPage(page) {
-  switch (page) {
-    case "newtab":
-      aboutNewTabService.resetNewTabURL();
-      break;
-    case "home":
-      Preferences.reset("browser.startup.homepage");
-      break;
-    default:
-      throw new Error("Unrecognized override type");
-  }
-}
-
-/**
- * Overrides the specified page to the specified URL.
- *
- * @param {string} page The page to override. Accepted values are "newtab" and "home".
- * @param {string} url The resolved URL to use for the page override.
- */
-function overridePage(page, url) {
-  switch (page) {
-    case "newtab":
-      aboutNewTabService.newTabURL = url;
-      break;
-    case "home":
-      Preferences.set("browser.startup.homepage", url);
-      break;
-    default:
-      throw new Error("Unrecognized override type");
-  }
-}
-
-/**
- * Updates the page to the URL specified by the extension next in line. If no extensions
- * are in line, the page is reset to its default value.
- *
- * @param {string} page The page to override.
- */
-function updatePage(page) {
-  if (overrides[page].length) {
-    overridePage(page, overrides[page][0].url);
-  } else {
-    resetPage(page);
-  }
-}
-
 /* eslint-disable mozilla/balanced-listeners */
 extensions.on("manifest_chrome_url_overrides", (type, directive, extension, manifest) => {
-  if (Object.keys(overrides).length > 1) {
-    extension.manifestError("Extensions can override only one page.");
-  }
+  if (manifest.chrome_url_overrides.newtab) {
+    let newtab = manifest.chrome_url_overrides.newtab;
+    let url = extension.baseURI.resolve(newtab);
 
-  for (let page of Object.keys(overrides)) {
-    if (manifest.chrome_url_overrides[page]) {
-      let relativeURL = manifest.chrome_url_overrides[page];
-      let url = extension.baseURI.resolve(relativeURL);
-      // Store the extension ID instead of a hard reference to the extension.
-      overrides[page].push({id: extension.id, url});
-      updatePage(page);
-      break;
+    // Only set the newtab URL if no other extension is overriding it.
+    if (!overrides.newtab.length) {
+      aboutNewTabService.newTabURL = url;
     }
+
+    overrides.newtab.push({id: extension.id, url});
   }
 });
 
 extensions.on("shutdown", (type, extension) => {
-  for (let page of Object.keys(overrides)) {
-    let i = overrides[page].findIndex(o => o.id === extension.id);
-    if (i !== -1) {
-      overrides[page].splice(i, 1);
-      updatePage(page);
+  let i = overrides.newtab.findIndex(o => o.id === extension.id);
+  if (i !== -1) {
+    overrides.newtab.splice(i, 1);
+
+    if (overrides.newtab.length) {
+      aboutNewTabService.newTabURL = overrides.newtab[0].url;
+    } else {
+      aboutNewTabService.resetNewTabURL();
     }
   }
 });
 /* eslint-enable mozilla/balanced-listeners */
--- a/browser/components/extensions/extensions-browser.manifest
+++ b/browser/components/extensions/extensions-browser.manifest
@@ -1,12 +1,13 @@
 # scripts
 category webextension-scripts bookmarks chrome://browser/content/ext-bookmarks.js
 category webextension-scripts browserAction chrome://browser/content/ext-browserAction.js
 category webextension-scripts browsingData chrome://browser/content/ext-browsingData.js
+category webextension-scripts chrome-settings-overrides chrome://browser/content/ext-chrome-settings-overrides.js
 category webextension-scripts commands chrome://browser/content/ext-commands.js
 category webextension-scripts contextMenus chrome://browser/content/ext-contextMenus.js
 category webextension-scripts desktop-runtime chrome://browser/content/ext-desktop-runtime.js
 category webextension-scripts devtools chrome://browser/content/ext-devtools.js
 category webextension-scripts devtools-inspectedWindow chrome://browser/content/ext-devtools-inspectedWindow.js
 category webextension-scripts devtools-network chrome://browser/content/ext-devtools-network.js
 category webextension-scripts devtools-panels chrome://browser/content/ext-devtools-panels.js
 category webextension-scripts history chrome://browser/content/ext-history.js
@@ -28,16 +29,17 @@ category webextension-scripts-devtools d
 category webextension-scripts-addon contextMenus chrome://browser/content/ext-c-contextMenus.js
 category webextension-scripts-addon omnibox chrome://browser/content/ext-c-omnibox.js
 category webextension-scripts-addon tabs chrome://browser/content/ext-c-tabs.js
 
 # schemas
 category webextension-schemas bookmarks chrome://browser/content/schemas/bookmarks.json
 category webextension-schemas browser_action chrome://browser/content/schemas/browser_action.json
 category webextension-schemas browsing_data chrome://browser/content/schemas/browsing_data.json
+category webextension-schemas chrome_settings_overrides chrome://browser/content/schemas/chrome_settings_overrides.json
 category webextension-schemas commands chrome://browser/content/schemas/commands.json
 category webextension-schemas context_menus chrome://browser/content/schemas/context_menus.json
 category webextension-schemas context_menus_internal chrome://browser/content/schemas/context_menus_internal.json
 category webextension-schemas devtools chrome://browser/content/schemas/devtools.json
 category webextension-schemas devtools_inspected_window chrome://browser/content/schemas/devtools_inspected_window.json
 category webextension-schemas devtools_network chrome://browser/content/schemas/devtools_network.json
 category webextension-schemas devtools_panels chrome://browser/content/schemas/devtools_panels.json
 category webextension-schemas history chrome://browser/content/schemas/history.json
--- a/browser/components/extensions/jar.mn
+++ b/browser/components/extensions/jar.mn
@@ -10,16 +10,17 @@ browser.jar:
 #endif
 #ifdef XP_WIN
     content/browser/extension-win-panel.css
 #endif
     content/browser/extension.svg
     content/browser/ext-bookmarks.js
     content/browser/ext-browserAction.js
     content/browser/ext-browsingData.js
+    content/browser/ext-chrome-settings-overrides.js
     content/browser/ext-commands.js
     content/browser/ext-contextMenus.js
     content/browser/ext-desktop-runtime.js
     content/browser/ext-devtools.js
     content/browser/ext-devtools-inspectedWindow.js
     content/browser/ext-devtools-network.js
     content/browser/ext-devtools-panels.js
     content/browser/ext-history.js
new file mode 100644
--- /dev/null
+++ b/browser/components/extensions/schemas/chrome_settings_overrides.json
@@ -0,0 +1,24 @@
+[
+  {
+    "namespace": "manifest",
+    "types": [
+      {
+        "$extend": "WebExtensionManifest",
+        "properties": {
+          "chrome_settings_overrides": {
+            "type": "object",
+            "optional": true,
+            "properties": {
+              "homepage": {
+                "type": "string",
+                "format": "url",
+                "optional": true,
+                "preprocess": "localize"
+              }
+            }
+          }
+        }
+      }
+    ]
+  }
+]
\ No newline at end of file
--- a/browser/components/extensions/schemas/jar.mn
+++ b/browser/components/extensions/schemas/jar.mn
@@ -1,16 +1,17 @@
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 browser.jar:
     content/browser/schemas/bookmarks.json
     content/browser/schemas/browser_action.json
     content/browser/schemas/browsing_data.json
+    content/browser/schemas/chrome_settings_overrides.json
     content/browser/schemas/commands.json
     content/browser/schemas/context_menus.json
     content/browser/schemas/context_menus_internal.json
     content/browser/schemas/devtools.json
     content/browser/schemas/devtools_inspected_window.json
     content/browser/schemas/devtools_network.json
     content/browser/schemas/devtools_panels.json
     content/browser/schemas/history.json
--- a/browser/components/extensions/schemas/url_overrides.json
+++ b/browser/components/extensions/schemas/url_overrides.json
@@ -9,21 +9,16 @@
             "type": "object",
             "optional": true,
             "properties": {
               "newtab": {
                 "$ref": "ExtensionURL",
                 "optional": true,
                 "preprocess": "localize"
               },
-              "home": {
-                "$ref": "ExtensionURL",
-                "optional": true,
-                "preprocess": "localize"
-              },
               "bookmarks": {
                 "unsupported": true,
                 "$ref": "ExtensionURL",
                 "optional": true,
                 "preprocess": "localize"
               },
               "history": {
                 "unsupported": true,
--- a/browser/components/extensions/test/browser/browser-common.ini
+++ b/browser/components/extensions/test/browser/browser-common.ini
@@ -112,17 +112,16 @@ support-files =
 [browser_ext_tabs_cookieStoreId.js]
 [browser_ext_tabs_update.js]
 [browser_ext_tabs_zoom.js]
 [browser_ext_tabs_update_url.js]
 [browser_ext_themes_chromeparity.js]
 [browser_ext_themes_dynamic_updates.js]
 [browser_ext_themes_lwtsupport.js]
 [browser_ext_topwindowid.js]
-[browser_ext_url_overrides_all.js]
 [browser_ext_url_overrides_home.js]
 [browser_ext_url_overrides_newtab.js]
 [browser_ext_webRequest.js]
 [browser_ext_webNavigation_frameId0.js]
 [browser_ext_webNavigation_getFrames.js]
 [browser_ext_webNavigation_urlbar_transitions.js]
 [browser_ext_windows.js]
 [browser_ext_windows_create.js]
deleted file mode 100644
--- a/browser/components/extensions/test/browser/browser_ext_url_overrides_all.js
+++ /dev/null
@@ -1,97 +0,0 @@
-/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
-/* vim: set sts=2 sw=2 et tw=80: */
-
-"use strict";
-
-XPCOMUtils.defineLazyServiceGetter(this, "aboutNewTabService",
-                                   "@mozilla.org/browser/aboutnewtab-service;1",
-                                   "nsIAboutNewTabService");
-
-const NEWTAB_URI = "webext-newtab.html";
-const HOME_URI = "webext-home.html";
-
-add_task(function* test_extensions_overriding_different_pages() {
-  let defaultHomePage = Preferences.get("browser.startup.homepage");
-  let defaultNewtabPage = aboutNewTabService.newTabURL;
-
-  is(Preferences.get("browser.startup.homepage"), defaultHomePage,
-    `Default home url should be ${defaultHomePage}`);
-  is(aboutNewTabService.newTabURL, defaultNewtabPage,
-    `Default newtab url should be ${defaultNewtabPage}`);
-
-  let ext1 = ExtensionTestUtils.loadExtension({
-    manifest: {"chrome_url_overrides": {}},
-  });
-
-  let ext2 = ExtensionTestUtils.loadExtension({
-    manifest: {"chrome_url_overrides": {newtab: NEWTAB_URI}},
-  });
-
-  let ext3 = ExtensionTestUtils.loadExtension({
-    manifest: {"chrome_url_overrides": {home: HOME_URI}},
-  });
-
-  yield ext1.startup();
-
-  is(aboutNewTabService.newTabURL, defaultNewtabPage,
-    `Default newtab url should still be ${defaultNewtabPage}`);
-  is(Preferences.get("browser.startup.homepage"), defaultHomePage,
-    `Default home url should be ${defaultHomePage}`);
-
-  yield ext2.startup();
-
-  ok(aboutNewTabService.newTabURL.endsWith(NEWTAB_URI),
-    "Newtab url should be overriden by the second extension.");
-  is(Preferences.get("browser.startup.homepage"), defaultHomePage,
-    `Default home url should be ${defaultHomePage}`);
-
-  yield ext1.unload();
-
-  ok(aboutNewTabService.newTabURL.endsWith(NEWTAB_URI),
-    "Newtab url should still be overriden by the second extension.");
-  is(Preferences.get("browser.startup.homepage"), defaultHomePage,
-    `Default home url should be ${defaultHomePage}`);
-
-  yield ext3.startup();
-
-  ok(aboutNewTabService.newTabURL.endsWith(NEWTAB_URI),
-    "Newtab url should still be overriden by the second extension.");
-  ok(Preferences.get("browser.startup.homepage").endsWith(HOME_URI),
-    "Home url should be overriden by the third extension.");
-
-  yield ext2.unload();
-
-  is(aboutNewTabService.newTabURL, defaultNewtabPage,
-    `Newtab url should be reset to ${defaultNewtabPage}`);
-  ok(Preferences.get("browser.startup.homepage").endsWith(HOME_URI),
-    "Home url should still be overriden by the third extension.");
-
-  yield ext3.unload();
-
-  is(aboutNewTabService.newTabURL, defaultNewtabPage,
-    `Newtab url should be reset to ${defaultNewtabPage}`);
-  is(Preferences.get("browser.startup.homepage"), defaultHomePage,
-    `Home url should be reset to ${defaultHomePage}`);
-});
-
-add_task(function* test_extensions_with_multiple_overrides() {
-  let ext = ExtensionTestUtils.loadExtension({
-    manifest: {"chrome_url_overrides": {
-      newtab: NEWTAB_URI,
-      home: HOME_URI,
-    }},
-  });
-
-  SimpleTest.waitForExplicitFinish();
-  let waitForConsole = new Promise(resolve => {
-    SimpleTest.monitorConsole(resolve, [{
-      message: /Extensions can override only one page./,
-    }]);
-  });
-
-  yield ext.startup();
-  yield ext.unload();
-
-  SimpleTest.endMonitorConsole();
-  yield waitForConsole;
-});
--- a/browser/components/extensions/test/browser/browser_ext_url_overrides_home.js
+++ b/browser/components/extensions/test/browser/browser_ext_url_overrides_home.js
@@ -1,74 +1,95 @@
 /* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set sts=2 sw=2 et tw=80: */
 
 "use strict";
 
 XPCOMUtils.defineLazyModuleGetter(this, "Preferences",
                                   "resource://gre/modules/Preferences.jsm");
 
-const HOME_URI_1 = "webext-home-1.html";
-const HOME_URI_2 = "webext-home-2.html";
-const HOME_URI_3 = "webext-home-3.html";
+// 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(function* test_multiple_extensions_overriding_newtab_page() {
   let defaultHomePage = Preferences.get("browser.startup.homepage");
 
   is(Preferences.get("browser.startup.homepage"), defaultHomePage,
      `Default home url should be ${defaultHomePage}`);
 
   let ext1 = ExtensionTestUtils.loadExtension({
-    manifest: {"chrome_url_overrides": {}},
+    manifest: {"chrome_settings_overrides": {}},
+    useAddonManager: "temporary",
   });
 
   let ext2 = ExtensionTestUtils.loadExtension({
-    manifest: {"chrome_url_overrides": {home: HOME_URI_1}},
+    manifest: {"chrome_settings_overrides": {homepage: HOME_URI_2}},
+    useAddonManager: "temporary",
   });
 
   let ext3 = ExtensionTestUtils.loadExtension({
-    manifest: {"chrome_url_overrides": {home: HOME_URI_2}},
+    manifest: {"chrome_settings_overrides": {homepage: HOME_URI_3}},
+    useAddonManager: "temporary",
   });
 
   let ext4 = ExtensionTestUtils.loadExtension({
-    manifest: {"chrome_url_overrides": {home: HOME_URI_3}},
+    manifest: {"chrome_settings_overrides": {homepage: HOME_URI_4}},
+    useAddonManager: "temporary",
   });
 
   yield ext1.startup();
 
   is(Preferences.get("browser.startup.homepage"), defaultHomePage,
        `Default home url should still be ${defaultHomePage}`);
 
+  // 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");
   yield ext2.startup();
-
-  ok(Preferences.get("browser.startup.homepage").endsWith(HOME_URI_1),
-     "Home url should be overriden by the second extension.");
-
-  yield ext1.unload();
+  yield prefPromise;
 
-  ok(Preferences.get("browser.startup.homepage").endsWith(HOME_URI_1),
-     "Home url should still be overriden by the second extension.");
-
-  yield ext3.startup();
+  ok(Preferences.get("browser.startup.homepage").endsWith(HOME_URI_2),
+     "Home url should be overridden by the second extension.");
 
-  ok(Preferences.get("browser.startup.homepage").endsWith(HOME_URI_1),
-     "Home url should still be overriden by the second extension.");
-
-  yield ext2.unload();
+  // Because we are unloading an earlier extension, browser.startup.homepage won't change
+  yield ext1.unload();
 
   ok(Preferences.get("browser.startup.homepage").endsWith(HOME_URI_2),
-     "Home url should be overriden by the third extension.");
+     "Home url should be overridden by the second extension.");
+
+  prefPromise = promisePrefChangeObserved("browser.startup.homepage");
+  yield ext3.startup();
+  yield prefPromise;
 
-  yield ext4.startup();
+  ok(Preferences.get("browser.startup.homepage").endsWith(HOME_URI_3),
+     "Home url should be overridden by the third extension.");
 
-  ok(Preferences.get("browser.startup.homepage").endsWith(HOME_URI_2),
-     "Home url should be overriden by the third extension.");
+  // Because we are unloading an earlier extension, browser.startup.homepage won't change
+  yield ext2.unload();
+
+  ok(Preferences.get("browser.startup.homepage").endsWith(HOME_URI_3),
+     "Home url should be overridden by the third extension.");
 
-  yield ext4.unload();
+  prefPromise = promisePrefChangeObserved("browser.startup.homepage");
+  yield ext4.startup();
+  yield prefPromise;
+
+  ok(Preferences.get("browser.startup.homepage").endsWith(HOME_URI_4),
+     "Home url should be overridden by the third extension.");
+
 
-  ok(Preferences.get("browser.startup.homepage").endsWith(HOME_URI_2),
-     "Home url should be overriden by the third extension.");
+  prefPromise = promisePrefChangeObserved("browser.startup.homepage");
+  yield ext4.unload();
+  yield prefPromise;
 
+  ok(Preferences.get("browser.startup.homepage").endsWith(HOME_URI_3),
+     "Home url should be overridden by the third extension.");
+
+  prefPromise = promisePrefChangeObserved("browser.startup.homepage");
   yield ext3.unload();
+  yield prefPromise;
 
   is(Preferences.get("browser.startup.homepage"), defaultHomePage,
-     `Home url should be reset to ${defaultHomePage}`);
+     "Home url should be reset to default");
 });
--- a/browser/components/extensions/test/browser/browser_ext_url_overrides_newtab.js
+++ b/browser/components/extensions/test/browser/browser_ext_url_overrides_newtab.js
@@ -71,21 +71,21 @@ add_task(function* test_multiple_extensi
   is(aboutNewTabService.newTabURL, "about:newtab",
      "Newtab url should be reset to about:newtab");
 });
 
 add_task(function* test_sending_message_from_newtab_page() {
   let extension = ExtensionTestUtils.loadExtension({
     manifest: {
       "chrome_url_overrides": {
-        newtab: NEWTAB_URI_1,
+        newtab: NEWTAB_URI_2,
       },
     },
     files: {
-      [NEWTAB_URI_1]: `
+      [NEWTAB_URI_2]: `
         <!DOCTYPE html>
         <head>
           <meta charset="utf-8"/></head>
         <html>
           <body>
             <script src="newtab.js"></script>
           </body>
         </html>
--- a/browser/components/extensions/test/browser/head.js
+++ b/browser/components/extensions/test/browser/head.js
@@ -10,16 +10,17 @@
  *          promisePopupShown promisePopupHidden
  *          openContextMenu closeContextMenu
  *          openExtensionContextMenu closeExtensionContextMenu
  *          openActionContextMenu openSubmenu closeActionContextMenu
  *          openTabContextMenu closeTabContextMenu
  *          imageBuffer getListStyleImage getPanelForNode
  *          awaitExtensionPanel awaitPopupResize
  *          promiseContentDimensions alterContent
+ *          promisePrefChangeObserved
  */
 
 const {AppConstants} = Cu.import("resource://gre/modules/AppConstants.jsm", {});
 const {CustomizableUI} = Cu.import("resource:///modules/CustomizableUI.jsm", {});
 
 // We run tests under two different configurations, from browser.ini and
 // browser-remote.ini. When running from browser-remote.ini, the tests are
 // copied to the sub-directory "test-oop-extensions", which we detect here, and
@@ -337,8 +338,16 @@ function closePageAction(extension, win 
   if (node) {
     return promisePopupShown(node).then(() => {
       node.hidePopup();
     });
   }
 
   return Promise.resolve();
 }
+
+function promisePrefChangeObserved(pref) {
+  return new Promise((resolve, reject) =>
+    Preferences.observe(pref, function prefObserver() {
+      Preferences.ignore(pref, prefObserver);
+      resolve();
+    }));
+}