diff --git a/toolkit/components/contentprefs/ContentPrefService2.jsm b/toolkit/components/contentprefs/ContentPrefService2.jsm index 9a1c7e0ce7d..6b4085e03dc 100644 --- a/toolkit/components/contentprefs/ContentPrefService2.jsm +++ b/toolkit/components/contentprefs/ContentPrefService2.jsm @@ -195,7 +195,7 @@ ContentPrefService2.prototype = { if (context && context.usePrivateBrowsing) { this._pbStore.set(group, name, value); this._schedule(function () { - this._cps._notifyPrefSet(group, name, value); + this._cps._broadcastPrefSet(group, name, value); cbHandleCompletion(callback, Ci.nsIContentPrefCallback2.COMPLETE_OK); }); return; @@ -260,7 +260,7 @@ ContentPrefService2.prototype = { onDone: function onDone(reason, ok) { if (ok) { this._cache.setWithCast(group, name, value); - this._cps._notifyPrefSet(group, name, value); + this._cps._broadcastPrefSet(group, name, value); } cbHandleCompletion(callback, reason); }, @@ -343,7 +343,7 @@ ContentPrefService2.prototype = { } } for (let [sgroup, , ] in prefs) { - this._cps._notifyPrefRemoved(sgroup, name); + this._cps._broadcastPrefRemoved(sgroup, name); } } cbHandleCompletion(callback, reason); @@ -429,7 +429,7 @@ ContentPrefService2.prototype = { } } for (let [sgroup, sname, ] in prefs) { - this._cps._notifyPrefRemoved(sgroup, sname); + this._cps._broadcastPrefRemoved(sgroup, sname); } } cbHandleCompletion(callback, reason); @@ -481,7 +481,7 @@ ContentPrefService2.prototype = { this._pbStore.removeGrouped(); } for (let [sgroup, sname, ] in prefs) { - this._cps._notifyPrefRemoved(sgroup, sname); + this._cps._broadcastPrefRemoved(sgroup, sname); } } cbHandleCompletion(callback, reason); @@ -554,7 +554,7 @@ ContentPrefService2.prototype = { } } for (let [sgroup, , ] in prefs) { - this._cps._notifyPrefRemoved(sgroup, name); + this._cps._broadcastPrefRemoved(sgroup, name); } } cbHandleCompletion(callback, reason); diff --git a/toolkit/components/contentprefs/nsContentPrefService.js b/toolkit/components/contentprefs/nsContentPrefService.js index 06dafc6c5a0..ef635328a34 100644 --- a/toolkit/components/contentprefs/nsContentPrefService.js +++ b/toolkit/components/contentprefs/nsContentPrefService.js @@ -9,6 +9,25 @@ const Cu = Components.utils; const CACHE_MAX_GROUP_ENTRIES = 100; + +// We have a whitelist for getting/setting. This is because +// there are potential privacy issues with a compromised +// content process checking the user's content preferences +// and using that to discover all the websites visited, etc. +// Also there are both potential race conditions (if two processes +// set more than one value in succession, and the values +// only make sense together), as well as security issues, if +// a compromised content process can send arbitrary setPref +// messages. The whitelist contains only those settings that +// are not at risk for either. +// We currently whitelist saving/reading the last directory of file +// uploads, and the last current spellchecker dictionary which are so far +// the only need we have identified. +const REMOTE_WHITELIST = [ + "browser.upload.lastDir", + "spellcheck.lang", +]; + Cu.import("resource://gre/modules/XPCOMUtils.jsm"); /** @@ -26,25 +45,15 @@ function electrolify(service) { Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT) { // Parent process + service.messageManager = Cc["@mozilla.org/parentprocessmessagemanager;1"]. + getService(Ci.nsIMessageBroadcaster); + // Setup listener for child messages. We don't need to call // addMessageListener as the wakeup service will do that for us. service.receiveMessage = function(aMessage) { var json = aMessage.json; - // We have a whitelist for getting/setting. This is because - // there are potential privacy issues with a compromised - // content process checking the user's content preferences - // and using that to discover all the websites visited, etc. - // Also there are both potential race conditions (if two processes - // set more than one value in succession, and the values - // only make sense together), as well as security issues, if - // a compromised content process can send arbitrary setPref - // messages. The whitelist contains only those settings that - // are not at risk for either. - // We currently whitelist saving/reading the last directory of file - // uploads, and the last current spellchecker dictionary which are so far - // the only need we have identified. - const NAME_WHITELIST = ["browser.upload.lastDir", "spellcheck.lang"]; - if (NAME_WHITELIST.indexOf(json.name) == -1) + + if (REMOTE_WHITELIST.indexOf(json.name) == -1) return { succeeded: false }; switch (aMessage.name) { @@ -88,6 +97,19 @@ function electrolify(service) { return ret.value; }; }); + + // Listen to preference change notifications from the parent and notify + // observers in the child process according to the change + service.messageManager.addMessageListener("ContentPref:notifyPrefSet", + function(aMessage) { + var json = aMessage.json; + service._notifyPrefSet(json.group, json.name, json.value); + }); + service.messageManager.addMessageListener("ContentPref:notifyPrefRemoved", + function(aMessage) { + var json = aMessage.json; + service._notifyPrefRemoved(json.group, json.name); + }); } } @@ -328,7 +350,7 @@ ContentPrefService.prototype = { if (aContext && aContext.usePrivateBrowsing) { this._privModeStorage.setWithCast(group, aName, aValue); - this._notifyPrefSet(group, aName, aValue); + this._broadcastPrefSet(group, aName, aValue); return; } @@ -350,8 +372,7 @@ ContentPrefService.prototype = { this._insertPref(groupID, settingID, aValue); this._cache.setWithCast(group, aName, aValue); - - this._notifyPrefSet(group, aName, aValue); + this._broadcastPrefSet(group, aName, aValue); }, hasPref: function ContentPrefService_hasPref(aGroup, aName, aContext) { @@ -379,7 +400,7 @@ ContentPrefService.prototype = { if (aContext && aContext.usePrivateBrowsing) { this._privModeStorage.remove(group, aName); - this._notifyPrefRemoved(group, aName); + this._broadcastPrefRemoved(group, aName); return; } @@ -402,7 +423,7 @@ ContentPrefService.prototype = { this._deleteGroupIfUnused(groupID); this._cache.remove(group, aName); - this._notifyPrefRemoved(group, aName); + this._broadcastPrefRemoved(group, aName); }, removeGroupedPrefs: function ContentPrefService_removeGroupedPrefs(aContext) { @@ -437,7 +458,7 @@ ContentPrefService.prototype = { for (let [group, name, ] in this._privModeStorage) { if (name === aName) { this._privModeStorage.remove(group, aName); - this._notifyPrefRemoved(group, aName); + this._broadcastPrefRemoved(group, aName); } } } @@ -479,7 +500,7 @@ ContentPrefService.prototype = { if (groupNames[i]) // ie. not null, which will be last (and i == groupIDs.length) this._deleteGroupIfUnused(groupIDs[i]); if (!aContext || !aContext.usePrivateBrowsing) { - this._notifyPrefRemoved(groupNames[i], aName); + this._broadcastPrefRemoved(groupNames[i], aName); } } }, @@ -569,7 +590,10 @@ ContentPrefService.prototype = { return observers; }, - + + /** + * Notify all observers about the removal of a preference. + */ _notifyPrefRemoved: function ContentPrefService__notifyPrefRemoved(aGroup, aName) { for each (var observer in this._getObservers(aName)) { try { @@ -581,6 +605,9 @@ ContentPrefService.prototype = { } }, + /** + * Notify all observers about a preference change. + */ _notifyPrefSet: function ContentPrefService__notifyPrefSet(aGroup, aName, aValue) { for each (var observer in this._getObservers(aName)) { try { @@ -592,6 +619,38 @@ ContentPrefService.prototype = { } }, + /** + * Notify all observers in the current process about the removal of a + * preference and send a message to all other processes so that they can in + * turn notify their observers about the change. This is meant to be called + * only in the parent process. Only whitelisted preferences are broadcast to + * the child processes. + */ + _broadcastPrefRemoved: function ContentPrefService__broadcastPrefRemoved(aGroup, aName) { + this._notifyPrefRemoved(aGroup, aName); + + if (REMOTE_WHITELIST.indexOf(aName) != -1) { + this.messageManager.broadcastAsyncMessage('ContentPref:notifyPrefRemoved', + { "group": aGroup, "name": aName } ); + } + }, + + /** + * Notify all observers in the current process about a preference change and + * send a message to all other processes so that they can in turn notify + * their observers about the change. This is meant to be called only in the + * parent process. Only whitelisted preferences are broadcast to the child + * processes. + */ + _broadcastPrefSet: function ContentPrefService__broadcastPrefSet(aGroup, aName, aValue) { + this._notifyPrefSet(aGroup, aName, aValue); + + if (REMOTE_WHITELIST.indexOf(aName) != -1) { + this.messageManager.broadcastAsyncMessage('ContentPref:notifyPrefSet', + { "group": aGroup, "name": aName, "value": aValue } ); + } + }, + _grouper: null, get grouper() { if (!this._grouper)