Bug 1252855 - allow setting a specific list of prefs from the content process, r?mrbkap,margaret
MozReview-Commit-ID: DihkBXqlLQl
--- 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();
+ });
+}