Bug 1031609 - Basic e10s add-on shim architecture and content-policy shims (r=mconley,mrbkap)

This commit is contained in:
Bill McCloskey 2014-07-14 22:10:06 -07:00
Родитель 842257593a
Коммит 26513758b2
8 изменённых файлов: 513 добавлений и 0 удалений

Просмотреть файл

@ -411,6 +411,8 @@
#endif
@BINPATH@/components/nsUpdateTimerManager.manifest
@BINPATH@/components/nsUpdateTimerManager.js
@BINPATH@/components/addoncompat.manifest
@BINPATH@/components/multiprocessShims.js
@BINPATH@/components/pluginGlue.manifest
@BINPATH@/browser/components/nsSessionStore.manifest
@BINPATH@/browser/components/nsSessionStartup.js

Просмотреть файл

@ -0,0 +1,156 @@
// 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/.
this.EXPORTED_SYMBOLS = ["RemoteAddonsChild"];
const Ci = Components.interfaces;
const Cc = Components.classes;
const Cu = Components.utils;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
// Similar to Python. Returns dict[key] if it exists. Otherwise,
// sets dict[key] to default_ and returns default_.
function setDefault(dict, key, default_)
{
if (key in dict) {
return dict[key];
}
dict[key] = default_;
return default_;
}
// This code keeps track of a set of paths of the form [component_1,
// ..., component_n]. The components can be strings or booleans. The
// child is notified whenever a path is added or removed, and new
// children can request the current set of paths. The purpose is to
// keep track of all the observers and events that the child should
// monitor for the parent.
//
// In the child, clients can watch for changes to all paths that start
// with a given component.
let NotificationTracker = {
init: function() {
let cpmm = Cc["@mozilla.org/childprocessmessagemanager;1"]
.getService(Ci.nsISyncMessageSender);
cpmm.addMessageListener("Addons:AddNotification", this);
cpmm.addMessageListener("Addons:RemoveNotification", this);
let [paths] = cpmm.sendSyncMessage("Addons:GetNotifications");
this._paths = paths;
this._watchers = {};
},
receiveMessage: function(msg) {
let path = msg.data;
let tracked = this._paths;
for (let component of path) {
tracked = setDefault(tracked, component, {});
}
let count = tracked._count || 0;
switch (msg.name) {
case "Addons:AddNotification":
count++;
break;
case "Addons:RemoveNotification":
count--;
break;
}
tracked._count = count;
for (let cb of this._watchers[path[0]]) {
cb(path, count);
}
},
watch: function(component1, callback) {
setDefault(this._watchers, component1, []).push(callback);
function enumerate(tracked, curPath) {
for (let component in tracked) {
if (component == "_count") {
callback(curPath, tracked._count);
} else {
let path = curPath.slice();
if (component === "true") {
component = true;
} else if (component === "false") {
component = false;
}
path.push(component);
enumerate(tracked[component], path);
}
}
}
enumerate(this._paths[component1] || {}, [component1]);
}
};
NotificationTracker.init();
// This code registers an nsIContentPolicy in the child process. When
// it runs, it notifies the parent that it needs to run its own
// nsIContentPolicy list. If any policy in the parent rejects a
// resource load, that answer is returned to the child.
let ContentPolicyChild = {
_classDescription: "Addon shim content policy",
_classID: Components.ID("6e869130-635c-11e2-bcfd-0800200c9a66"),
_contractID: "@mozilla.org/addon-child/policy;1",
init: function() {
let registrar = Components.manager.QueryInterface(Ci.nsIComponentRegistrar);
registrar.registerFactory(this._classID, this._classDescription, this._contractID, this);
NotificationTracker.watch("content-policy", (path, count) => this.track(path, count));
},
QueryInterface: XPCOMUtils.generateQI([Ci.nsIContentPolicy, Ci.nsIObserver,
Ci.nsIChannelEventSink, Ci.nsIFactory,
Ci.nsISupportsWeakReference]),
track: function(path, count) {
let catMan = Cc["@mozilla.org/categorymanager;1"].getService(Ci.nsICategoryManager);
if (count) {
catMan.addCategoryEntry("content-policy", this._contractID, this._contractID, false, true);
} else {
catMan.deleteCategoryEntry("content-policy", this._contractID, false);
}
},
shouldLoad: function(contentType, contentLocation, requestOrigin, node, mimeTypeGuess, extra) {
let cpmm = Cc["@mozilla.org/childprocessmessagemanager;1"]
.getService(Ci.nsISyncMessageSender);
var rval = cpmm.sendRpcMessage("Addons:ContentPolicy:Run", {}, {
contentType: contentType,
mimeTypeGuess: mimeTypeGuess,
contentLocation: contentLocation,
requestOrigin: requestOrigin,
node: node
});
if (rval.length != 1) {
return Ci.nsIContentPolicy.ACCEPT;
}
return rval[0];
},
shouldProcess: function(contentType, contentLocation, requestOrigin, insecNode, mimeType, extra) {
return Ci.nsIContentPolicy.ACCEPT;
},
createInstance: function(outer, iid) {
if (outer) {
throw Cr.NS_ERROR_NO_AGGREGATION;
}
return this.QueryInterface(iid);
},
};
ContentPolicyChild.init();
let RemoteAddonsChild = {
init: function(global) {
},
};

Просмотреть файл

@ -0,0 +1,194 @@
// 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/.
this.EXPORTED_SYMBOLS = ["RemoteAddonsParent"];
const Ci = Components.interfaces;
const Cc = Components.classes;
const Cu = Components.utils;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import('resource://gre/modules/Services.jsm');
// Similar to Python. Returns dict[key] if it exists. Otherwise,
// sets dict[key] to default_ and returns default_.
function setDefault(dict, key, default_)
{
if (key in dict) {
return dict[key];
}
dict[key] = default_;
return default_;
}
// This code keeps track of a set of paths of the form [component_1,
// ..., component_n]. The components can be strings or booleans. The
// child is notified whenever a path is added or removed, and new
// children can request the current set of paths. The purpose is to
// keep track of all the observers and events that the child should
// monitor for the parent.
let NotificationTracker = {
// _paths is a multi-level dictionary. Let's add paths [A, B] and
// [A, C]. Then _paths will look like this:
// { 'A': { 'B': { '_count': 1 }, 'C': { '_count': 1 } } }
// Each component in a path will be a key in some dictionary. At the
// end, the _count property keeps track of how many instances of the
// given path are present in _paths.
_paths: {},
init: function() {
let ppmm = Cc["@mozilla.org/parentprocessmessagemanager;1"]
.getService(Ci.nsIMessageBroadcaster);
ppmm.addMessageListener("Addons:GetNotifications", this);
},
add: function(path) {
let tracked = this._paths;
for (let component of path) {
tracked = setDefault(tracked, component, {});
}
let count = tracked._count || 0;
count++;
tracked._count = count;
let ppmm = Cc["@mozilla.org/parentprocessmessagemanager;1"]
.getService(Ci.nsIMessageBroadcaster);
ppmm.broadcastAsyncMessage("Addons:AddNotification", path);
},
remove: function(path) {
let tracked = this._paths;
for (let component of path) {
tracked = setDefault(tracked, component, {});
}
tracked._count--;
let ppmm = Cc["@mozilla.org/parentprocessmessagemanager;1"]
.getService(Ci.nsIMessageBroadcaster);
ppmm.broadcastAsyncMessage("Addons:RemoveNotification", path);
},
receiveMessage: function(msg) {
if (msg.name == "Addons:GetNotifications") {
return this._paths;
}
}
};
NotificationTracker.init();
// An interposition is an object with three properties: methods,
// getters, and setters. See multiprocessShims.js for an explanation
// of how these are used. The constructor here just allows one
// interposition to inherit members from another.
function Interposition(base)
{
if (base) {
this.methods = Object.create(base.methods);
this.getters = Object.create(base.getters);
this.setters = Object.create(base.setters);
} else {
this.methods = {};
this.getters = {};
this.setters = {};
}
}
// This object is responsible for notifying the child when a new
// content policy is added or removed. It also runs all the registered
// add-on content policies when the child asks it to do so.
let ContentPolicyParent = {
init: function() {
let ppmm = Cc["@mozilla.org/parentprocessmessagemanager;1"]
.getService(Ci.nsIMessageBroadcaster);
ppmm.addMessageListener("Addons:ContentPolicy:Run", this);
this._policies = [];
},
addContentPolicy: function(cid) {
this._policies.push(cid);
NotificationTracker.add(["content-policy"]);
},
removeContentPolicy: function(cid) {
let index = this._policies.lastIndexOf(cid);
if (index > -1) {
this._policies.splice(index, 1);
}
NotificationTracker.remove(["content-policy"]);
},
receiveMessage: function (aMessage) {
switch (aMessage.name) {
case "Addons:ContentPolicy:Run":
return this.shouldLoad(aMessage.data, aMessage.objects);
break;
}
},
shouldLoad: function(aData, aObjects) {
for (let policyCID of this._policies) {
let policy = Cc[policyCID].getService(Ci.nsIContentPolicy);
try {
let result = policy.shouldLoad(aObjects.contentType,
aObjects.contentLocation,
aObjects.requestOrigin,
aObjects.node,
aObjects.mimeTypeGuess,
null);
if (result != Ci.nsIContentPolicy.ACCEPT && result != 0)
return result;
} catch (e) {
Cu.reportError(e);
}
}
return Ci.nsIContentPolicy.ACCEPT;
},
};
ContentPolicyParent.init();
// This interposition intercepts calls to add or remove new content
// policies and forwards these requests to ContentPolicyParent.
let CategoryManagerInterposition = new Interposition();
CategoryManagerInterposition.methods.addCategoryEntry =
function(addon, target, category, entry, value, persist, replace) {
if (category == "content-policy") {
ContentPolicyParent.addContentPolicy(entry);
}
target.addCategoryEntry(category, entry, value, persist, replace);
};
CategoryManagerInterposition.methods.deleteCategoryEntry =
function(addon, target, category, entry, persist) {
if (category == "content-policy") {
ContentPolicyParent.remoteContentPolicy(entry);
}
target.deleteCategoryEntry(category, entry, persist);
};
let RemoteAddonsParent = {
init: function() {
},
getInterfaceInterpositions: function() {
let result = {};
function register(intf, interp) {
result[intf.number] = interp;
}
register(Ci.nsICategoryManager, CategoryManagerInterposition);
return result;
},
getTaggedInterpositions: function() {
return {};
},
};

Просмотреть файл

@ -0,0 +1,2 @@
component {1363d5f0-d95e-11e3-9c1a-0800200c9a66} multiprocessShims.js
contract @mozilla.org/addons/multiprocess-shims;1 {1363d5f0-d95e-11e3-9c1a-0800200c9a66}

Просмотреть файл

@ -0,0 +1,15 @@
# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
# vim: set filetype=python:
# 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/.
EXTRA_COMPONENTS += [
'addoncompat.manifest',
'multiprocessShims.js',
]
EXTRA_JS_MODULES += [
'RemoteAddonsChild.jsm',
'RemoteAddonsParent.jsm',
]

Просмотреть файл

@ -0,0 +1,139 @@
/* 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 Cu = Components.utils;
const Ci = Components.interfaces;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "RemoteAddonsParent",
"resource://gre/modules/RemoteAddonsParent.jsm");
/**
* This service overlays the API that the browser exposes to
* add-ons. The overlay tries to make a multiprocess browser appear as
* much as possible like a single process browser. An overlay can
* replace methods, getters, and setters of arbitrary browser objects.
*
* Most of the actual replacement code is implemented in
* RemoteAddonsParent. The code in this service simply decides how to
* replace code. For a given type of object (say, an
* nsIObserverService) the code in RemoteAddonsParent can register a
* set of replacement methods. This set is called an
* "interposition". The service keeps track of all the different
* interpositions. Whenever a method is called on some part of the
* browser API, this service gets a chance to replace it. To do so, it
* consults its map based on the type of object. If an interposition
* is found, the given method is looked up on it and called
* instead. If no method (or no interposition) is found, then the
* original target method is called as normal.
*
* For each method call, we need to determine the type of the target
* object. If the object is an old-style XPConnect wrapped native,
* then the type is simply the interface that the method was called on
* (Ci.nsIObserverService, say). For all other objects (WebIDL
* objects, CPOWs, and normal JS objects), the type is determined by
* calling getObjectTag.
*
* The interpositions defined in RemoteAddonsParent have three
* properties: methods, getters, and setters. When accessing a
* property, we first consult methods. If nothing is found, then we
* consult getters or setters, depending on whether the access is a
* get or a set.
*
* The methods in |methods| are functions that will be called whenever
* the given method is called on the target object. They are passed
* the same parameters as the original function except for two
* additional ones at the beginning: the add-on ID and the original
* target object that the method was called on. Additionally, the
* value of |this| is set to the original target object.
*
* The values in |getters| and |setters| should also be
* functions. They are called immediately when the given property is
* accessed. The functions in |getters| take two parameters: the
* add-on ID and the original target object. The functions in
* |setters| take those arguments plus the value that the property is
* being set to.
*/
function AddonInterpositionService()
{
RemoteAddonsParent.init();
// These maps keep track of the interpositions for all different
// kinds of objects.
this._interfaceInterpositions = RemoteAddonsParent.getInterfaceInterpositions();
this._taggedInterpositions = RemoteAddonsParent.getTaggedInterpositions();
}
AddonInterpositionService.prototype = {
classID: Components.ID("{1363d5f0-d95e-11e3-9c1a-0800200c9a66}"),
QueryInterface: XPCOMUtils.generateQI([Ci.nsIAddonInterposition, Ci.nsISupportsWeakReference]),
// When the interface is not known for a method call, this code
// determines the type of the target object.
getObjectTag: function(target) {
if (Cu.isCrossProcessWrapper(target)) {
if (target instanceof Ci.nsIDocShellTreeItem) {
return "ContentDocShellTreeItem";
}
if (target instanceof Ci.nsIDOMDocument) {
return "ContentDocument";
}
}
const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
if ((target instanceof Ci.nsIDOMXULElement) &&
target.localName == "browser" &&
target.namespaceURI == XUL_NS) {
return "BrowserElement";
}
if (target instanceof Ci.nsIDOMEventTarget) {
return "EventTarget";
}
return "generic";
},
interpose: function(addon, target, iid, prop) {
let interp;
if (iid) {
interp = this._interfaceInterpositions[iid];
} else {
interp = this._taggedInterpositions[this.getObjectTag(target)];
}
if (!interp) {
return null;
}
let desc = { configurable: false, enumerable: true };
if ("methods" in interp && interp.methods.hasOwnProperty(prop)) {
desc.writable = false;
desc.value = function(...args) {
return interp.methods[prop](addon, target, ...args);
}
return desc;
} else if ("getters" in interp &&
interp.getters.hasOwnProperty(prop)) {
desc.get = function() { return interp.getters[prop](addon, target); };
if ("setters" in interp && interp.setters.hasOwnProperty(prop)) {
desc.set = function(v) { return interp.setters[prop](addon, target, v); };
}
return desc;
}
return null;
},
};
this.NSGetFactory = XPCOMUtils.generateNSGetFactory([AddonInterpositionService]);

Просмотреть файл

@ -11,6 +11,7 @@ if CONFIG['MOZ_ENABLE_XREMOTE']:
PARALLEL_DIRS += [
'aboutcache',
'aboutmemory',
'addoncompat',
'alerts',
'apppicker',
'commandlines',

Просмотреть файл

@ -8,6 +8,7 @@ let Cu = Components.utils;
Cu.import("resource://gre/modules/Services.jsm");
Cu.import('resource://gre/modules/XPCOMUtils.jsm');
Cu.import("resource://gre/modules/RemoteAddonsChild.jsm");
Cu.import("resource://gre/modules/Timer.jsm");
#ifdef MOZ_CRASHREPORTER
@ -352,6 +353,9 @@ addEventListener("TextZoomChange", function (aEvent) {
}
}, false);
// This needs to be rooted so that it stays alive as long as the tab.
let AddonsChild = RemoteAddonsChild.init(this);
addMessageListener("NetworkPrioritizer:AdjustPriority", (msg) => {
let webNav = docShell.QueryInterface(Ci.nsIWebNavigation);
let loadGroup = webNav.QueryInterface(Ci.nsIDocumentLoader)