Bug 1252855 - allow setting a specific list of prefs from the content process, r?mrbkap,margaret draft
authorGijs Kruitbosch <gijskruitbosch@gmail.com>
Sun, 13 Mar 2016 13:10:16 +0000
changeset 341161 a9deaa7ba18a75bc2a1281cf651edf5be0643971
parent 341096 bf91d8c739453edd22bba18be331246c905d6be8
child 341162 726330b675b4a20984bbfd4bba57ad933e122807
push id13155
push usergijskruitbosch@gmail.com
push dateWed, 16 Mar 2016 17:15:48 +0000
reviewersmrbkap, margaret
bugs1252855
milestone48.0a1
Bug 1252855 - allow setting a specific list of prefs from the content process, r?mrbkap,margaret MozReview-Commit-ID: DihkBXqlLQl
browser/components/nsBrowserGlue.js
mobile/android/chrome/content/browser.js
toolkit/content/process-content.js
toolkit/modules/AsyncPrefs.jsm
toolkit/modules/moz.build
toolkit/modules/tests/browser/browser.ini
toolkit/modules/tests/browser/browser_AsyncPrefs.js
--- a/browser/components/nsBrowserGlue.js
+++ b/browser/components/nsBrowserGlue.js
@@ -7,16 +7,18 @@ const Cc = Components.classes;
 const Cr = Components.results;
 const Cu = Components.utils;
 
 const XULNS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/AppConstants.jsm");
+// Set us up to use async prefs in the parent process.
+Cu.import("resource://gre/modules/AsyncPrefs.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "AboutHome",
                                   "resource:///modules/AboutHome.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "AboutNewTab",
                                   "resource:///modules/AboutNewTab.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "DirectoryLinksProvider",
--- a/mobile/android/chrome/content/browser.js
+++ b/mobile/android/chrome/content/browser.js
@@ -8,16 +8,17 @@ var Cc = Components.classes;
 var Ci = Components.interfaces;
 var Cu = Components.utils;
 var Cr = Components.results;
 
 Cu.import("resource://gre/modules/AppConstants.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/AddonManager.jsm");
+Cu.import("reosurce://gre/modules/AsyncPrefs.jsm");
 Cu.import("resource://gre/modules/DelayedInit.jsm");
 
 if (AppConstants.ACCESSIBILITY) {
   XPCOMUtils.defineLazyModuleGetter(this, "AccessFu",
                                     "resource://gre/modules/accessibility/AccessFu.jsm");
 }
 
 XPCOMUtils.defineLazyModuleGetter(this, "SpatialNavigation",
--- a/toolkit/content/process-content.js
+++ b/toolkit/content/process-content.js
@@ -5,8 +5,12 @@
 "use strict";
 
 var { classes: Cc, interfaces: Ci, utils: Cu } = Components;
 
 // Creates a new PageListener for this process. This will listen for page loads
 // and for those that match URLs provided by the parent process will set up
 // a dedicated message port and notify the parent process.
 Cu.import("resource://gre/modules/RemotePageManager.jsm");
+
+// Enables us to set prefs from the content process - assuming the parent process
+// loads the same module, too!
+Cu.import("resource://gre/modules/AsyncPrefs.jsm");
new file mode 100644
--- /dev/null
+++ b/toolkit/modules/AsyncPrefs.jsm
@@ -0,0 +1,172 @@
+/* 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";
+
+this.EXPORTED_SYMBOLS = ["AsyncPrefs"];
+
+const {interfaces: Ci, utils: Cu, classes: Cc} = Components;
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/Task.jsm");
+
+const kInChildProcess = Services.appinfo.processType == Services.appinfo.PROCESS_TYPE_CONTENT;
+
+const kAllowedPrefs = new Set([
+  "testing.allowed-prefs.some-bool-pref",
+  "testing.allowed-prefs.some-char-pref",
+  "testing.allowed-prefs.some-int-pref",
+]);
+
+const kPrefTypeMap = new Map([
+  ["boolean", Services.prefs.PREF_BOOL],
+  ["number", Services.prefs.PREF_INT],
+  ["string", Services.prefs.PREF_STRING],
+]);
+
+let gUniqueId = 0;
+let gMsgMap = new Map();
+
+function maybeReturnErrorForReset(pref) {
+  if (!kAllowedPrefs.has(pref)) {
+    return `Resetting pref ${pref} from content is not allowed.`;
+  }
+  return false;
+}
+
+function maybeReturnErrorForSet(pref, value) {
+  if (!kAllowedPrefs.has(pref)) {
+    return `Setting pref ${pref} from content is not allowed.`;
+  }
+
+  let valueType = typeof value;
+  if (!kPrefTypeMap.has(valueType)) {
+    return `Can't set pref ${pref} to value of type ${valueType}.`;
+  }
+  let prefType = Services.prefs.getPrefType(pref);
+  if (prefType != Services.prefs.PREF_INVALID &&
+      prefType != kPrefTypeMap.get(valueType)) {
+    return `Can't set pref ${pref} to a value with type ${valueType} that doesn't match the pref's type ${prefType}.`;
+  }
+  return false;
+}
+
+var AsyncPrefs;
+if (kInChildProcess) {
+  AsyncPrefs = {
+    set: Task.async(function(pref, value) {
+      let error = maybeReturnErrorForSet(pref, value);
+      if (error) {
+        return Promise.reject(error);
+      }
+
+      let msgId = ++this._uniqueId;
+      return new Promise((resolve, reject) => {
+        gMsgMap.set(msgId, {resolve, reject});
+        Services.cpmm.sendAsyncMessage("AsyncPrefs:SetPref", {pref, value, msgId});
+      });
+    }),
+
+    reset: Task.async(function(pref) {
+      let error = maybeReturnErrorForReset(pref);
+      if (error) {
+        return Promise.reject(error);
+      }
+
+      let msgId = ++this._uniqueId;
+      return new Promise((resolve, reject) => {
+        gMsgMap.set(msgId, {resolve, reject});
+        Services.cpmm.sendAsyncMessage("AsyncPrefs:ResetPref", {pref, msgId});
+      });
+    }),
+
+    receiveMessage(msg) {
+      let promiseRef = gMsgMap.get(msg.data.msgId);
+      if (promiseRef) {
+        gMsgMap.delete(msg.data.msgId);
+        if (msg.data.success) {
+          promiseRef.resolve();
+        } else {
+          promiseRef.reject(msg.data.message);
+        }
+      }
+    },
+
+    init() {
+      Services.cpmm.addMessageListener("AsyncPrefs:PrefSetFinished", this);
+      Services.cpmm.addMessageListener("AsyncPrefs:PrefResetFinished", this);
+    },
+  };
+} else {
+  AsyncPrefs = {
+    methodForType: {
+      number: "setIntPref",
+      boolean: "setBoolPref",
+      string: "setCharPref",
+    },
+
+    set: Task.async(function(pref, value) {
+      let error = maybeReturnErrorForSet(pref, value);
+      if (error) {
+        return Promise.reject(error);
+      }
+      let methodToUse = this.methodForType[typeof value];
+      try {
+        Services.prefs[methodToUse](pref, value);
+        return Promise.resolve(value);
+      } catch (ex) {
+        Cu.reportError(ex);
+        return Promise.reject(ex.message);
+      }
+    }),
+
+    reset: Task.async(function(pref) {
+      let error = maybeReturnErrorForReset(pref);
+      if (error) {
+        return Promise.reject(error);
+      }
+
+      try {
+        Services.prefs.clearUserPref(pref);
+        return Promise.resolve();
+      } catch (ex) {
+        Cu.reportError(ex);
+        return Promise.reject(ex.message);
+      }
+    }),
+
+    receiveMessage(msg) {
+      if (msg.name == "AsyncPrefs:SetPref") {
+        this.onPrefSet(msg);
+      } else {
+        this.onPrefReset(msg);
+      }
+    },
+
+    onPrefReset(msg) {
+      let {pref, msgId} = msg.data;
+      this.reset(pref).then(function() {
+        msg.target.sendAsyncMessage("AsyncPrefs:PrefResetFinished", {msgId, success: true});
+      }, function(msg) {
+        msg.target.sendAsyncMessage("AsyncPrefs:PrefResetFinished", {msgId, success: false, message: msg});
+      });
+    },
+
+    onPrefSet(msg) {
+      let {pref, value, msgId} = msg.data;
+      this.set(pref, value).then(function() {
+        msg.target.sendAsyncMessage("AsyncPrefs:PrefSetFinished", {msgId, success: true});
+      }, function(msg) {
+        msg.target.sendAsyncMessage("AsyncPrefs:PrefSetFinished", {msgId, success: false, message: msg});
+      });
+    },
+
+    init() {
+      Services.ppmm.addMessageListener("AsyncPrefs:SetPref", this);
+      Services.ppmm.addMessageListener("AsyncPrefs:ResetPref", this);
+    }
+  };
+}
+
+AsyncPrefs.init();
+
--- a/toolkit/modules/moz.build
+++ b/toolkit/modules/moz.build
@@ -18,16 +18,17 @@ SPHINX_TREES['toolkit_modules'] = 'docs'
 EXTRA_JS_MODULES += [
     'addons/MatchPattern.jsm',
     'addons/WebNavigation.jsm',
     'addons/WebNavigationContent.js',
     'addons/WebNavigationFrames.jsm',
     'addons/WebRequest.jsm',
     'addons/WebRequestCommon.jsm',
     'addons/WebRequestContent.js',
+    'AsyncPrefs.jsm',
     'Battery.jsm',
     'BinarySearch.jsm',
     'BrowserUtils.jsm',
     'CertUtils.jsm',
     'CharsetMenu.jsm',
     'ClientID.jsm',
     'Console.jsm',
     'debug.js',
--- a/toolkit/modules/tests/browser/browser.ini
+++ b/toolkit/modules/tests/browser/browser.ini
@@ -16,16 +16,17 @@ support-files =
   file_style_redirect.css
   file_script_good.js
   file_script_bad.js
   file_script_redirect.js
   file_script_xhr.js
   WebRequest_dynamic.sjs
   WebRequest_redirection.sjs
 
+[browser_AsyncPrefs.js]
 [browser_Battery.js]
 [browser_Deprecated.js]
 [browser_Finder.js]
 [browser_Geometry.js]
 [browser_InlineSpellChecker.js]
 [browser_WebNavigation.js]
 [browser_WebRequest.js]
 [browser_WebRequest_cookies.js]
new file mode 100644
--- /dev/null
+++ b/toolkit/modules/tests/browser/browser_AsyncPrefs.js
@@ -0,0 +1,97 @@
+"use strict";
+
+const kWhiteListedBool = "testing.allowed-prefs.some-bool-pref";
+const kWhiteListedChar = "testing.allowed-prefs.some-char-pref";
+const kWhiteListedInt = "testing.allowed-prefs.some-int-pref";
+
+function resetPrefs() {
+  for (let pref of [kWhiteListedBool, kWhiteListedChar, kWhiteListedBool]) {
+    Services.prefs.clearUserPref(pref);
+  }
+}
+
+registerCleanupFunction(resetPrefs);
+
+Services.prefs.getDefaultBranch("testing.allowed-prefs.").setBoolPref("some-bool-pref", false);
+Services.prefs.getDefaultBranch("testing.allowed-prefs.").setCharPref("some-char-pref", "");
+Services.prefs.getDefaultBranch("testing.allowed-prefs.").setIntPref("some-int-pref", 0);
+
+function* runTest() {
+  let {AsyncPrefs} = Cu.import("resource://gre/modules/AsyncPrefs.jsm", {});
+  const kInChildProcess = Services.appinfo.processType == Services.appinfo.PROCESS_TYPE_CONTENT;
+
+  // Need to define these again because when run in a content task we have no scope access.
+  const kNotWhiteListed = "some.pref.thats.not.whitelisted";
+  const kWhiteListedBool = "testing.allowed-prefs.some-bool-pref";
+  const kWhiteListedChar = "testing.allowed-prefs.some-char-pref";
+  const kWhiteListedInt = "testing.allowed-prefs.some-int-pref";
+
+  const procDesc = kInChildProcess ? "child process" : "parent process";
+
+  const valueResultMap = [
+    [true, "Bool"],
+    [false, "Bool"],
+    [10, "Int"],
+    [-1, "Int"],
+    ["", "Char"],
+    ["stuff", "Char"],
+    [[], false],
+    [{}, false],
+    [BrowserUtils.makeURI("http://mozilla.org/"), false],
+  ];
+
+  const prefMap = [
+    ["Bool", kWhiteListedBool],
+    ["Char", kWhiteListedChar],
+    ["Int", kWhiteListedInt],
+  ];
+
+  function doesFail(pref, value) {
+    let msg = `Should not succeed setting ${pref} to ${value} in ${procDesc}`;
+    return AsyncPrefs.set(pref, value).then(() => ok(false, msg), error => ok(true, msg + "; " + error));
+  }
+
+  function doesWork(pref, value) {
+    let msg = `Should be able to set ${pref} to ${value} in ${procDesc}`;
+    return AsyncPrefs.set(pref, value).then(() => ok(true, msg), error => ok(false, msg + "; " + error));
+  }
+
+  function doReset(pref) {
+    let msg = `Should be able to reset ${pref} in ${procDesc}`;
+    return AsyncPrefs.reset(pref).then(() => ok(true, msg), () => ok(false, msg));
+  }
+
+  for (let [val, ] of valueResultMap) {
+    yield doesFail(kNotWhiteListed, val);
+    is(Services.prefs.prefHasUserValue(kNotWhiteListed), false, "Pref shouldn't get changed");
+  }
+
+  let resetMsg = `Should not succeed resetting ${kNotWhiteListed} in ${procDesc}`;
+  AsyncPrefs.reset(kNotWhiteListed).then(() => ok(false, resetMsg), error => ok(true, resetMsg + "; " + error));
+
+  for (let [type, pref] of prefMap) {
+    for (let [val, result] of valueResultMap) {
+      if (result == type) {
+        yield doesWork(pref, val);
+        is(Services.prefs["get" + type + "Pref"](pref), val, "Pref should have been updated");
+        yield doReset(pref);
+      } else {
+        yield doesFail(pref, val);
+        is(Services.prefs.prefHasUserValue(pref), false, `Pref ${pref} shouldn't get changed`);
+      }
+    }
+  }
+}
+
+add_task(function* runInParent() {
+  yield runTest();
+  resetPrefs();
+});
+
+if (gMultiProcessBrowser) {
+  add_task(function* runInChild() {
+    ok(gBrowser.selectedBrowser.isRemoteBrowser, "Should actually run this in child process");
+    yield ContentTask.spawn(gBrowser.selectedBrowser, null, runTest);
+    resetPrefs();
+  });
+}