зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1031609 - Basic e10s add-on shim architecture and content-policy shims (r=mconley,mrbkap)
This commit is contained in:
Родитель
842257593a
Коммит
26513758b2
|
@ -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)
|
||||
|
|
Загрузка…
Ссылка в новой задаче