diff options
Diffstat (limited to 'toolkit/components/addoncompat/multiprocessShims.js')
-rw-r--r-- | toolkit/components/addoncompat/multiprocessShims.js | 182 |
1 files changed, 182 insertions, 0 deletions
diff --git a/toolkit/components/addoncompat/multiprocessShims.js b/toolkit/components/addoncompat/multiprocessShims.js new file mode 100644 index 000000000..8b252a0c4 --- /dev/null +++ b/toolkit/components/addoncompat/multiprocessShims.js @@ -0,0 +1,182 @@ +/* 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, "Prefetcher", + "resource://gre/modules/Prefetcher.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() +{ + Prefetcher.init(); + RemoteAddonsParent.init(); + + // These maps keep track of the interpositions for all different + // kinds of objects. + this._interfaceInterpositions = RemoteAddonsParent.getInterfaceInterpositions(); + this._taggedInterpositions = RemoteAddonsParent.getTaggedInterpositions(); + + let wl = []; + for (let v in this._interfaceInterpositions) { + let interp = this._interfaceInterpositions[v]; + wl.push(...Object.getOwnPropertyNames(interp.methods)); + wl.push(...Object.getOwnPropertyNames(interp.getters)); + wl.push(...Object.getOwnPropertyNames(interp.setters)); + } + + for (let v in this._taggedInterpositions) { + let interp = this._taggedInterpositions[v]; + wl.push(...Object.getOwnPropertyNames(interp.methods)); + wl.push(...Object.getOwnPropertyNames(interp.getters)); + wl.push(...Object.getOwnPropertyNames(interp.setters)); + } + + let nameSet = new Set(); + wl = wl.filter(function(item) { + if (nameSet.has(item)) + return true; + + nameSet.add(item); + return true; + }); + + this._whitelist = wl; +} + +AddonInterpositionService.prototype = { + classID: Components.ID("{1363d5f0-d95e-11e3-9c1a-0800200c9a66}"), + QueryInterface: XPCOMUtils.generateQI([Ci.nsIAddonInterposition, Ci.nsISupportsWeakReference]), + + getWhitelist: function() { + return this._whitelist; + }, + + // 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)) { + return Cu.getCrossProcessWrapperTag(target); + } + + if (target instanceof Ci.nsIDOMXULElement) { + if (target.localName == "browser" && target.isRemoteBrowser) { + return "RemoteBrowserElement"; + } + + if (target.localName == "tabbrowser") { + return "TabBrowserElement"; + } + } + + if (target instanceof Ci.nsIDOMChromeWindow && target.gMultiProcessBrowser) { + return "ChromeWindow"; + } + + if (target instanceof Ci.nsIDOMEventTarget) { + return "EventTarget"; + } + + return "generic"; + }, + + interposeProperty: function(addon, target, iid, prop) { + let interp; + if (iid) { + interp = this._interfaceInterpositions[iid]; + } else { + try { + interp = this._taggedInterpositions[this.getObjectTag(target)]; + } + catch (e) { + Cu.reportError(new Components.Exception("Failed to interpose object", e.result, Components.stack.caller)); + } + } + + if (!interp) { + return Prefetcher.lookupInCache(addon, target, prop); + } + + let desc = { configurable: false, enumerable: true }; + + if ("methods" in interp && prop in interp.methods) { + desc.writable = false; + desc.value = function(...args) { + return interp.methods[prop](addon, target, ...args); + } + + return desc; + } else if ("getters" in interp && prop in interp.getters) { + desc.get = function() { return interp.getters[prop](addon, target); }; + + if ("setters" in interp && prop in interp.setters) { + desc.set = function(v) { return interp.setters[prop](addon, target, v); }; + } + + return desc; + } + + return Prefetcher.lookupInCache(addon, target, prop); + }, + + interposeCall: function(addonId, originalFunc, originalThis, args) { + args.splice(0, 0, addonId); + return originalFunc.apply(originalThis, args); + }, +}; + +this.NSGetFactory = XPCOMUtils.generateNSGetFactory([AddonInterpositionService]); |