Bug 1027898 - Implement most of nsIContentPrefService2 for e10s. r=adw

This commit is contained in:
Blake Kaplan 2014-08-11 11:13:36 -07:00
parent 8c277a965f
commit 23294fd2f8
9 changed files with 737 additions and 40 deletions

View File

@ -81,8 +81,11 @@ XPCOMUtils.defineLazyModuleGetter(this, "PlacesBackups",
XPCOMUtils.defineLazyModuleGetter(this, "OS",
"resource://gre/modules/osfile.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "RemotePrompt",
"resource:///modules/RemotePrompt.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "RemotePrompt",
"resource:///modules/RemotePrompt.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "ContentPrefServiceParent",
"resource://gre/modules/ContentPrefServiceParent.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "SessionStore",
"resource:///modules/sessionstore/SessionStore.jsm");
@ -507,10 +510,9 @@ BrowserGlue.prototype = {
BrowserUITelemetry.init();
ContentSearch.init();
if (Services.appinfo.browserTabsRemote) {
ContentClick.init();
RemotePrompt.init();
}
ContentClick.init();
RemotePrompt.init();
ContentPrefServiceParent.init();
LoginManagerParent.init();

View File

@ -29,7 +29,7 @@ let EXPORTED_SYMBOLS = [
const { interfaces: Ci, classes: Cc, results: Cr, utils: Cu } = Components;
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/ContentPrefUtils.jsm");
Cu.import("resource://gre/modules/ContentPrefStore.jsm");
function ContentPrefService2(cps) {
@ -823,39 +823,6 @@ ContentPrefService2.prototype = {
},
};
function ContentPref(domain, name, value) {
this.domain = domain;
this.name = name;
this.value = value;
}
ContentPref.prototype = {
QueryInterface: XPCOMUtils.generateQI([Ci.nsIContentPref]),
};
function cbHandleResult(callback, pref) {
safeCallback(callback, "handleResult", [pref]);
}
function cbHandleCompletion(callback, reason) {
safeCallback(callback, "handleCompletion", [reason]);
}
function cbHandleError(callback, nsresult) {
safeCallback(callback, "handleError", [nsresult]);
}
function safeCallback(callbackObj, methodName, args) {
if (!callbackObj || typeof(callbackObj[methodName]) != "function")
return;
try {
callbackObj[methodName].apply(callbackObj, args);
}
catch (err) {
Cu.reportError(err);
}
}
function checkGroupArg(group) {
if (!group || typeof(group) != "string")
throw invalidArg("domain must be nonempty string.");

View File

@ -0,0 +1,236 @@
/* vim: set ts=2 sw=2 sts=2 et tw=80: */
/* 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 = [ "ContentPrefServiceChild" ];
const Ci = Components.interfaces;
const Cc = Components.classes;
const Cu = Components.utils;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/ContentPrefUtils.jsm");
Cu.import("resource://gre/modules/ContentPrefStore.jsm");
// We only need one bit of information out of the context.
function contextArg(context) {
return (context && context.usePrivateBrowsing) ?
{ usePrivateBrowsing: true } :
null;
}
function NYI() {
throw new Error("Do not add any new users of these functions");
}
function CallbackCaller(callback) {
this._callback = callback;
}
CallbackCaller.prototype = {
handleResult: function(contentPref) {
cbHandleResult(this._callback,
new ContentPref(contentPref.domain,
contentPref.name,
contentPref.value));
},
handleError: function(result) {
cbHandleError(this._callback, result);
},
handleCompletion: function(reason) {
cbHandleCompletion(this._callback, reason);
},
};
let ContentPrefServiceChild = {
QueryInterface: XPCOMUtils.generateQI([ Ci.nsIContentPrefService2 ]),
// Map from pref name -> set of observers
_observers: new Map(),
_mm: Cc["@mozilla.org/childprocessmessagemanager;1"]
.getService(Ci.nsIMessageSender),
_getRandomId: function() {
return Cc["@mozilla.org/uuid-generator;1"]
.getService(Ci.nsIUUIDGenerator).generateUUID().toString();
},
// Map from random ID string -> CallbackCaller, per request
_requests: new Map(),
init: function() {
this._mm.addMessageListener("ContentPrefs:HandleResult", this);
this._mm.addMessageListener("ContentPrefs:HandleError", this);
this._mm.addMessageListener("ContentPrefs:HandleCompletion", this);
},
receiveMessage: function(msg) {
let data = msg.data;
let callback;
switch (msg.name) {
case "ContentPrefs:HandleResult":
callback = this._requests.get(data.requestId);
callback.handleResult(data.contentPref);
break;
case "ContentPrefs:HandleError":
callback = this._requests.get(data.requestId);
callback.handleError(data.error);
break;
case "ContentPrefs:HandleCompletion":
callback = this._requests.get(data.requestId);
this._requests.delete(data.requestId);
callback.handleCompletion(data.reason);
break;
case "ContentPrefs:NotifyObservers": {
let observerList = this._observers.get(data.name);
if (!observerList)
break;
for (let observer of observerList) {
safeCallback(observer, data.callback, data.args);
}
break;
}
}
},
_callFunction: function(call, args, callback) {
let requestId = this._getRandomId();
let data = { call: call, args: args, requestId: requestId };
this._mm.sendAsyncMessage("ContentPrefs:FunctionCall", data);
this._requests.set(requestId, new CallbackCaller(callback));
},
getByName: function(name, context, callback) {
return this._callFunction("getByName",
[ name, contextArg(context) ],
callback);
},
getByDomainAndName: function(domain, name, context, callback) {
return this._callFunction("getByDomainAndName",
[ domain, name, contextArg(context) ],
callback);
},
getBySubdomainAndName: function(domain, name, context, callback) {
return this._callFunction("getBySubdomainAndName",
[ domain, name, contextArg(context) ],
callback);
},
getGlobal: function(name, context, callback) {
return this._callFunction("getGlobal",
[ name, contextArg(context) ],
callback);
},
getCachedByDomainAndName: NYI,
getCachedBySubdomainAndName: NYI,
getCachedGlobal: NYI,
set: function(domain, name, value, context, callback) {
this._callFunction("set",
[ domain, name, value, contextArg(context) ],
callback);
},
setGlobal: function(name, value, context, callback) {
this._callFunction("setGlobal",
[ name, value, contextArg(context) ],
callback);
},
removeByDomainAndName: function(domain, name, context, callback) {
this._callFunction("removeByDomainAndName",
[ domain, name, contextArg(context) ],
callback);
},
removeBySubdomainAndName: function(domain, name, context, callback) {
this._callFunction("removeBySubdomainAndName",
[ domain, name, contextArg(context) ],
callback);
},
removeGlobal: function(name, context, callback) {
this._callFunction("removeGlobal", [ name, contextArg(context) ], callback);
},
removeByDomain: function(domain, context, callback) {
this._callFunction("removeByDomain", [ domain, contextArg(context) ],
callback);
},
removeBySubdomain: function(domain, context, callback) {
this._callFunction("removeBySubdomain", [ domain, contextArg(context) ],
callback);
},
removeByName: function(name, context, callback) {
this._callFunction("removeByName", [ name, value, contextArg(context) ],
callback);
},
removeAllDomains: function(context, callback) {
this._callFunction("removeAllDomains", [ contextArg(context) ], callback);
},
removeAllGlobals: function(context, callback) {
this._callFunction("removeAllGlobals", [ contextArg(context) ],
callback);
},
addObserverForName: function(name, observer) {
let set = this._observers.get(name);
if (!set) {
set = new Set();
if (this._observers.size === 0) {
// This is the first observer of any kind. Start listening for changes.
this._mm.addMessageListener("ContentPrefs:NotifyObservers", this);
}
// This is the first observer for this name. Start listening for changes
// to it.
this._mm.sendAsyncMessage("ContentPrefs:AddObserverForName", { name: name });
this._observers.set(name, set);
}
set.add(observer);
},
removeObserverForName: function(name, observer) {
let set = this._observers.get(name);
if (!set)
return;
set.delete(observer);
if (set.size === 0) {
// This was the last observer for this name. Stop listening for changes.
this._mm.sendAsyncMessage("ContentPrefs:RemoveObserverForName", { name: name });
this._observers.delete(name);
if (this._observers.size === 0) {
// This was the last observer for this process. Stop listing for all
// changes.
this._mm.removeMessageListener("ContentPrefs:NotifyObservers", this);
}
}
},
extractDomain: NYI
};
ContentPrefServiceChild.init();

View File

@ -0,0 +1,127 @@
/* vim: set ts=2 sw=2 sts=2 et tw=80: */
/* 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 = [ "ContentPrefServiceParent" ];
const Cc = Components.classes;
const Ci = Components.interfaces;
const Cu = Components.utils;
let ContentPrefServiceParent = {
_cps2: null,
init: function() {
let globalMM = Cc["@mozilla.org/parentprocessmessagemanager;1"]
.getService(Ci.nsIMessageListenerManager);
this._cps2 = Cc["@mozilla.org/content-pref/service;1"]
.getService(Ci.nsIContentPrefService2);
globalMM.addMessageListener("ContentPrefs:FunctionCall", this);
let observerChangeHandler = this.handleObserverChange.bind(this);
globalMM.addMessageListener("ContentPrefs:AddObserverForName", observerChangeHandler);
globalMM.addMessageListener("ContentPrefs:RemoveObserverForName", observerChangeHandler);
globalMM.addMessageListener("child-process-shutdown", observerChangeHandler);
},
// Map from message manager -> content pref observer.
_observers: new Map(),
handleObserverChange: function(msg) {
let observer = this._observers.get(msg.target);
if (msg.name === "child-process-shutdown") {
// If we didn't have any observers for this child process, don't do
// anything.
if (!observer)
return;
for (let i of observer._names) {
this._cps2.removeObserverForName(i, observer);
}
this._observers.delete(msg.target);
return;
}
let prefName = msg.data.name;
if (msg.name === "ContentPrefs:AddObserverForName") {
// The child process is responsible for not adding multiple parent
// observers for the same name.
if (!observer) {
observer = {
onContentPrefSet: function(group, name, value) {
msg.target.sendAsyncMessage("ContentPrefs:NotifyObservers",
{ name: name, callback: "onContentPrefSet",
args: [ group, name, value ] });
},
onContentPrefRemoved: function(group, name) {
msg.target.sendAsyncMessage("ContentPrefs:NotifyObservers",
{ name: name, callback: "onContentPrefRemoved",
args: [ group, name ] });
},
// The names we're using this observer object for, used to keep track
// of the number of names we care about as well as for removing this
// observer if its associated process goes away.
_names: new Set()
};
this._observers.set(msg.target, observer);
}
observer._names.add(prefName);
this._cps2.addObserverForName(prefName, observer);
} else {
// RemoveObserverForName
// We must have an observer.
this._cps2.removeObserverForName(prefName, observer);
observer._names.delete(prefName);
if (observer._names.size === 0) {
// This was the last use for this observer.
this._observers.delete(msg.target);
}
}
},
receiveMessage: function(msg) {
let data = msg.data;
let args = data.args;
let requestId = data.requestId;
let listener = {
handleResult: function(pref) {
msg.target.sendAsyncMessage("ContentPrefs:HandleResult",
{ requestId: requestId,
contentPref: pref });
},
handleError: function(error) {
msg.target.sendAsyncMessage("ContentPrefs:HandleError",
{ requestId: requestId,
error: error });
},
handleCompletion: function(reason) {
msg.target.sendAsyncMessage("ContentPrefs:HandleCompletion",
{ requestId: requestId,
reason: reason });
}
};
// Push our special listener.
args.push(listener);
// And call the function.
this._cps2[data.call](...args);
}
};

View File

@ -0,0 +1,52 @@
/* vim: set ts=2 sw=2 sts=2 et tw=80: */
/* 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 = [
"ContentPref",
"cbHandleResult",
"cbHandleError",
"cbHandleCompletion",
"safeCallback",
];
const { interfaces: Ci, classes: Cc, results: Cr, utils: Cu } = Components;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
function ContentPref(domain, name, value) {
this.domain = domain;
this.name = name;
this.value = value;
}
ContentPref.prototype = {
QueryInterface: XPCOMUtils.generateQI([Ci.nsIContentPref]),
};
function cbHandleResult(callback, pref) {
safeCallback(callback, "handleResult", [pref]);
}
function cbHandleCompletion(callback, reason) {
safeCallback(callback, "handleCompletion", [reason]);
}
function cbHandleError(callback, nsresult) {
safeCallback(callback, "handleError", [nsresult]);
}
function safeCallback(callbackObj, methodName, args) {
if (!callbackObj || typeof(callbackObj[methodName]) != "function")
return;
try {
callbackObj[methodName].apply(callbackObj, args);
}
catch (err) {
Cu.reportError(err);
}
}

View File

@ -9,6 +9,10 @@ XPCSHELL_TESTS_MANIFESTS += [
'tests/unit_cps2/xpcshell.ini',
]
MOCHITEST_MANIFESTS += [
'tests/mochitest/mochitest.ini'
]
EXTRA_COMPONENTS += [
'nsContentPrefService.js',
'nsContentPrefService.manifest',
@ -17,6 +21,9 @@ EXTRA_COMPONENTS += [
EXTRA_JS_MODULES += [
'ContentPrefInstance.jsm',
'ContentPrefService2.jsm',
'ContentPrefServiceChild.jsm',
'ContentPrefServiceParent.jsm',
'ContentPrefStore.jsm',
'ContentPrefUtils.jsm',
]

View File

@ -10,8 +10,14 @@ const Cu = Components.utils;
const CACHE_MAX_GROUP_ENTRIES = 100;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
function ContentPrefService() {
if (Services.appinfo.processType === Services.appinfo.PROCESS_TYPE_CONTENT) {
return Cu.import("resource://gre/modules/ContentPrefServiceChild.jsm")
.ContentPrefServiceChild;
}
// If this throws an exception, it causes the getService call to fail,
// but the next time a consumer tries to retrieve the service, we'll try
// to initialize the database again, which might work if the failure

View File

@ -0,0 +1,4 @@
[DEFAULT]
[test_remoteContentPrefs.html]
skip-if = buildapp == 'mulet' || buildapp == 'b2g' || toolkit == 'android' || e10s #bug 783513 # b2g(nested ipc not working) b2g-debug(nested ipc not working) b2g-desktop(nested ipc not working)

View File

@ -0,0 +1,296 @@
<!DOCTYPE HTML>
<html>
<head>
<title>Test for nsIContentPrefService2 in child processes</title>
<script type="application/javascript"
src="/tests/SimpleTest/SimpleTest.js">
</script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
</head>
<body>
<script type="application/javascript;version=1.8">
"use strict";
SimpleTest.waitForExplicitFinish();
const childFrameURL =
"data:text/html,<!DOCTYPE HTML><html><body></body></html>";
function childFrameScript() {
"use strict";
function Tester(resultArray) {
this.results = [];
}
Tester.prototype.is =
function(a, b, note) {
this.results.push([a === b, note + " (" + a + ", " + b + ")"]);
};
Tester.prototype.ok =
function(b, note) {
this.results.push([b != false, note]);
};
var cps = Components.classes["@mozilla.org/content-pref/service;1"]
.getService(Components.interfaces.nsIContentPrefService2);
let test = null;
function* test1(message) {
let tester = new Tester();
tester.ok(cps !== null, "got the content pref service");
cps.setGlobal("testing", 42, null, {
handleCompletion: function(reason) {
tester.is(reason, 0, "set a pref?");
test.next();
}
});
yield;
let numResults = 0;
cps.getGlobal("testing", null, {
handleResult: function(pref) {
numResults++;
tester.is(pref.name, "testing", "pref has the right name");
tester.is(pref.value, 42, "pref has the right value");
},
handleCompletion: function(reason) {
tester.is(reason, 0, "get a pref?");
tester.is(numResults, 1, "got the right number of prefs");
tester.is(test.next().done, true, "done with test1");
message.target.sendAsyncMessage("testRemoteContentPrefs:test1Finished",
{ results: tester.results });
}
});
yield;
}
function* test2(message) {
let tester = new Tester();
let observer;
let removed = false;
cps.addObserverForName("testName", observer = {
onContentPrefSet: function(group, name, value) {
if (removed) {
message.target.sendAsyncMessage("testRemoteContentPrefs:fail",
{ reason: "unexpected notification" });
}
tester.is(group, null, "group should be null");
tester.is(name, "testName", "should only see testName");
tester.is(value, 42, "value should be correct");
message.target.sendAsyncMessage("testRemoteContentPrefs:test2poke2", {})
},
onContentPrefRemoved: function(group, name) {
tester.is(group, null, "group should be null");
tester.is(name, "testName");
tester.is(test.next().done, true, "should be done with test2");
cps.removeObserverForName("testName", observer);
removed = true;
message.target.sendAsyncMessage("testRemoteContentPrefs:test2Finished",
{ results: tester.results });
}
});
message.target.sendAsyncMessage("testRemoteContentPrefs:test2poke", {});
yield;
}
function* test3(message) {
let tester = new Tester();
cps.setGlobal("testName", 42, null, {
handleCompletion: function(reason) {
tester.is(reason, 0, "set a pref");
cps.set("http://mochi.test", "testpref", "str", null, {
handleCompletion: function(reason) {
tester.is(reason, 0, "set a pref");
test.next();
}
});
}
});
yield;
cps.removeByDomain("http://mochi.test", null, {
handleCompletion: function(reason) {
tester.is(reason, 0, "remove succeeded");
cps.getByDomainAndName("http://mochi.test", "testpref", null, {
handleResult: function() {
message.target.sendAsyncMessage("testRemoteContentPrefs:fail",
{ reason: "got removed pref in test3" });
},
handleCompletion: function() {
test.next();
}
});
}
});
yield;
message.target.sendAsyncMessage("testRemoteContentPrefs:test3Finished",
{ results: tester.results });
}
function* test4(message) {
let tester = new Tester();
let prefObserver = {
onContentPrefSet: function(group, name, value) {
test.next({ group: group, name: name, value: value });
},
onContentPrefRemoved: function(group, name) {
test.next({ group: group, name: name });
}
};
addMessageListener("testRemoteContentPrefs:prefResults", (msg) => {
test.next(msg.data.results);
});
cps.addObserverForName("test", prefObserver);
cps.set("http://mochi.test", "test", 42, { usePrivateBrowsing: true });
let event = yield;
tester.is(event.name, "test");
message.target.sendAsyncMessage("testRemoteContentPrefs:getPref",
{ group: "http://mochi.test", name: "test" });
let results = yield;
tester.is(results.length, 0, "should not have seen the pb pref");
message.target.sendAsyncMessage("testRemoteContentPrefs:test4Finished",
{ results: tester.results });
}
addMessageListener("testRemoteContentPrefs:test1", function(message) {
test = test1(message);
test.next();
});
addMessageListener("testRemoteContentPrefs:test2", function(message) {
test = test2(message);
test.next();
});
addMessageListener("testRemoteContentPrefs:test3", function(message) {
test = test3(message);
test.next();
});
addMessageListener("testRemoteContentPrefs:test4", function(message) {
test = test4(message);
test.next();
});
}
function processResults(results) {
for (let i of results) {
ok(...i);
}
}
let test;
function* testStructure(mm) {
let lastResult;
function testDone(msg) {
test.next(msg.data);
}
mm.addMessageListener("testRemoteContentPrefs:test1Finished", testDone);
mm.addMessageListener("testRemoteContentPrefs:test2Finished", testDone);
mm.addMessageListener("testRemoteContentPrefs:test3Finished", testDone);
mm.addMessageListener("testRemoteContentPrefs:test4Finished", testDone);
mm.addMessageListener("testRemoteContentPrefs:fail", function(msg) {
ok(false, msg.data.reason);
});
mm.sendAsyncMessage("testRemoteContentPrefs:test1", {});
lastResult = yield;
processResults(lastResult.results);
var cps = SpecialPowers.Cc["@mozilla.org/content-pref/service;1"]
.getService(SpecialPowers.Ci.nsIContentPrefService2);
mm.sendAsyncMessage("testRemoteContentPrefs:test2", {});
mm.addMessageListener("testRemoteContentPrefs:test2poke", function() {
cps.setGlobal("testName", 42, null);
});
mm.addMessageListener("testRemoteContentPrefs:test2poke2", function() {
cps.removeGlobal("testName", null);
});
lastResult = yield;
processResults(lastResult.results);
mm.sendAsyncMessage("testRemoteContentPrefs:test3", {});
lastResult = yield;
processResults(lastResult.results);
mm.addMessageListener("testRemoteContentPrefs:getPref", function(msg) {
let results = [];
cps.getByDomainAndName(msg.data.group, msg.data.name, null, {
handleResult: function(pref) {
results.push(pref);
},
handleCompletion: function(reason) {
mm.sendAsyncMessage("testRemoteContentPrefs:prefResults",
{ results: results });
}
});
});
mm.sendAsyncMessage("testRemoteContentPrefs:test4", {});
lastResult = yield;
processResults(lastResult.results);
SimpleTest.finish();
}
function runTests() {
info("Browser prefs set.");
let iframe = document.createElement("iframe");
SpecialPowers.wrap(iframe).mozbrowser = true;
iframe.id = "iframe";
iframe.src = childFrameURL;
iframe.addEventListener("mozbrowserloadend", function() {
info("Got iframe load event.");
let mm = SpecialPowers.getBrowserFrameMessageManager(iframe);
mm.loadFrameScript("data:,(" + childFrameScript.toString() + ")();",
false);
test = testStructure(mm);
test.next();
});
document.body.appendChild(iframe);
}
addEventListener("load", function() {
info("Got load event.");
SpecialPowers.addPermission("browser", true, document);
SpecialPowers.pushPrefEnv({
"set": [
["dom.ipc.browser_frames.oop_by_default", true],
["dom.mozBrowserFramesEnabled", true],
["browser.pagethumbnails.capturing_disabled", true]
]
}, runTests);
});
</script>
</body>
</html>