Bug 1586801 - Use the contextual WalkerFront in _duplicateNode(). r=pbro
[gecko.git] / toolkit / modules / ActorManagerChild.jsm
blobebe9d09c8aef00113c723c56754d22aedf7c5a6f
1 /* vim: set ts=2 sw=2 sts=2 et tw=80: */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
4  * You can obtain one at http://mozilla.org/MPL/2.0/. */
5 "use strict";
7 /**
8  * This module implements logic for creating JavaScript IPC actors, as defined
9  * in ActorManagerParent, for frame message manager contexts. See
10  * ActorManagerParent.jsm for more information.
11  */
13 var EXPORTED_SYMBOLS = ["ActorManagerChild"];
15 const { ExtensionUtils } = ChromeUtils.import(
16   "resource://gre/modules/ExtensionUtils.jsm"
18 const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
19 const { XPCOMUtils } = ChromeUtils.import(
20   "resource://gre/modules/XPCOMUtils.jsm"
23 ChromeUtils.defineModuleGetter(
24   this,
25   "WebNavigationFrames",
26   "resource://gre/modules/WebNavigationFrames.jsm"
29 const { DefaultMap } = ExtensionUtils;
31 const { sharedData } = Services.cpmm;
33 XPCOMUtils.defineLazyPreferenceGetter(
34   this,
35   "simulateEvents",
36   "fission.frontend.simulate-events",
37   false
39 XPCOMUtils.defineLazyPreferenceGetter(
40   this,
41   "simulateMessages",
42   "fission.frontend.simulate-messages",
43   false
46 function getMessageManager(window) {
47   return window.docShell.messageManager;
50 class Dispatcher {
51   constructor(mm, data) {
52     this.mm = mm;
54     this.actors = data.actors;
55     this.events = data.events;
56     this.messages = data.messages;
57     this.observers = data.observers;
59     this.instances = new Map();
60   }
62   init() {
63     for (let msg of this.messages.keys()) {
64       // This is directly called on the message manager
65       // because this.addMessageListener is meant to handle
66       // additions after initialization.
67       this.mm.addMessageListener(msg, this);
68     }
69     for (let topic of this.observers.keys()) {
70       Services.obs.addObserver(this, topic, true);
71     }
72     for (let { event, options, actor } of this.events) {
73       this.addEventListener(event, actor, options);
74     }
76     this.mm.addEventListener("unload", this);
77   }
79   cleanup() {
80     for (let topic of this.observers.keys()) {
81       Services.obs.removeObserver(this, topic);
82     }
84     this.mm.removeEventListener("unload", this);
85   }
87   get window() {
88     return this.mm.content;
89   }
91   get frameId() {
92     // 0 for top-level windows, outerWindowId otherwise
93     return WebNavigationFrames.getFrameId(this.window);
94   }
96   get browsingContextId() {
97     return this.window.docShell.browsingContext.id;
98   }
100   addEventListener(event, actor, options) {
101     let listener = this.handleActorEvent.bind(this, actor);
102     this.mm.addEventListener(event, listener, options);
103   }
105   addMessageListener(msg, actor) {
106     let actors = this.messages.get(msg);
108     if (!actors) {
109       actors = [];
110       this.messages.set(msg, actors);
111     }
113     if (!actors.length) {
114       this.mm.addMessageListener(msg, this);
115     }
117     if (!actors.includes(actor)) {
118       actors.push(actor);
119     }
120   }
122   getActor(actorName) {
123     let inst = this.instances.get(actorName);
124     if (!inst) {
125       let actor = this.actors.get(actorName);
127       let obj = {};
128       ChromeUtils.import(actor.module, obj);
130       inst = new obj[actorName](this);
131       this.instances.set(actorName, inst);
132     }
133     return inst;
134   }
136   handleEvent(event) {
137     if (event.type == "unload") {
138       this.cleanup();
139     }
140   }
142   handleActorEvent(actor, event) {
143     let targetWindow = null;
145     if (simulateEvents) {
146       targetWindow = event.target.ownerGlobal;
147       if (targetWindow != this.window) {
148         // events can't propagate across frame boundaries because the
149         // frames will be hosted on separated processes.
150         return;
151       }
152     }
153     this.getActor(actor).handleEvent(event);
154   }
156   receiveMessage(message) {
157     let actors = this.messages.get(message.name);
159     if (simulateMessages) {
160       let match = false;
161       let data = message.data || {};
162       if (data.hasOwnProperty("frameId")) {
163         match = data.frameId == this.frameId;
164       } else if (data.hasOwnProperty("browsingContextId")) {
165         match = data.browsingContextId == this.browsingContextId;
166       } else {
167         // if no specific target was given, just dispatch it to
168         // top-level actors.
169         match = this.frameId == 0;
170       }
172       if (!match) {
173         return;
174       }
175     }
177     for (let actor of actors) {
178       try {
179         this.getActor(actor).receiveMessage(message);
180       } catch (e) {
181         Cu.reportError(e);
182       }
183     }
184   }
186   observe(subject, topic, data) {
187     let actors = this.observers.get(topic);
188     for (let actor of actors) {
189       try {
190         this.getActor(actor).observe(subject, topic, data);
191       } catch (e) {
192         Cu.reportError(e);
193       }
194     }
195   }
198 Dispatcher.prototype.QueryInterface = ChromeUtils.generateQI([
199   "nsIObserver",
200   "nsISupportsWeakReference",
203 class SingletonDispatcher extends Dispatcher {
204   constructor(window, data) {
205     super(getMessageManager(window), data);
207     window.addEventListener("pageshow", this, { mozSystemGroup: true });
208     window.addEventListener("pagehide", this, { mozSystemGroup: true });
210     this._window = Cu.getWeakReference(window);
211     this.listeners = [];
212   }
214   init() {
215     super.init();
217     for (let actor of this.instances.values()) {
218       if (typeof actor.init === "function") {
219         try {
220           actor.init();
221         } catch (e) {
222           Cu.reportError(e);
223         }
224       }
225     }
226   }
228   cleanup() {
229     super.cleanup();
231     for (let msg of this.messages.keys()) {
232       this.mm.removeMessageListener(msg, this);
233     }
234     for (let [event, listener, options] of this.listeners) {
235       this.mm.removeEventListener(event, listener, options);
236     }
238     for (let actor of this.instances.values()) {
239       if (typeof actor.cleanup == "function") {
240         try {
241           actor.cleanup();
242         } catch (e) {
243           Cu.reportError(e);
244         }
245       }
246     }
248     this.listeners = [];
249   }
251   get window() {
252     return this._window.get();
253   }
255   handleEvent(event) {
256     if (event.type == "pageshow") {
257       if (this.hidden) {
258         this.init();
259       }
260       this.hidden = false;
261     } else if (event.type == "pagehide") {
262       this.hidden = true;
263       this.cleanup();
264     }
265   }
267   handleActorEvent(actor, event) {
268     if (event.target.ownerGlobal == this.window) {
269       const inst = this.getActor(actor);
270       if (typeof inst.handleEvent != "function") {
271         throw new Error(
272           `Unhandled event for ${actor}: ${event.type}: missing handleEvent`
273         );
274       }
275       inst.handleEvent(event);
276     }
277   }
279   addEventListener(event, actor, options) {
280     let listener = this.handleActorEvent.bind(this, actor);
281     this.listeners.push([event, listener, options]);
282     this.mm.addEventListener(event, listener, options);
283   }
286 /* globals MatchPatternSet, MozDocumentMatcher, MozDocumentObserver */
288 var ActorManagerChild = {
289   groups: new DefaultMap(group => {
290     return sharedData.get(`ChildActors:${group || ""}`);
291   }),
293   singletons: new Map(),
295   init() {
296     let singletons = sharedData.get("ChildSingletonActors");
297     for (let [filter, data] of singletons.entries()) {
298       let options = {
299         matches: new MatchPatternSet(filter.matches, {
300           restrictSchemes: false,
301         }),
302         allFrames: filter.allFrames,
303         matchAboutBlank: filter.matchAboutBlank,
304       };
306       this.singletons.set(new MozDocumentMatcher(options), data);
307     }
309     this.observer = new MozDocumentObserver(this);
310     this.observer.observe(this.singletons.keys());
312     this.init = null;
313   },
315   /**
316    * MozDocumentObserver callbacks. These handle instantiating singleton actors
317    * for documents which match their MozDocumentMatcher filters.
318    */
319   onNewDocument(matcher, window) {
320     new SingletonDispatcher(window, this.singletons.get(matcher)).init();
321   },
322   onPreloadDocument(matcher, loadInfo) {},
324   /**
325    * Attaches the appropriate set of actors to the given frame message manager.
326    *
327    * @param {ContentFrameMessageManager} mm
328    *        The message manager to which to attach the actors.
329    * @param {string} [group]
330    *        The messagemanagergroup of the <browser> to which the caller frame
331    *        script belongs. This restricts the attached set of actors based on
332    *        the "group" that their actor definitions specify.
333    */
334   attach(mm, group = null) {
335     new Dispatcher(mm, this.groups.get(group)).init();
336   },
338   getActor(mm, actorName) {
339     for (let dispatcher of this.dispatchers.get(mm)) {
340       let actor = dispatcher.getActor(actorName);
341       if (actor) {
342         return actor;
343       }
344     }
345     return null;
346   },
349 ActorManagerChild.init();