summaryrefslogtreecommitdiff
path: root/toolkit/components/addoncompat/multiprocessShims.js
blob: 8b252a0c484af0db4c9e069867f54e22a2f10ef3 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
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]);