1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
3 * You can obtain one at http://mozilla.org/MPL/2.0/. */
7 /* BrowserElementParent injects script to listen for certain events in the
8 * child. We then listen to messages from the child script and take
9 * appropriate action here in the parent.
12 const { BrowserElementPromptService } = ChromeUtils.import(
13 "resource://gre/modules/BrowserElementPromptService.jsm"
17 // dump("BrowserElementParent - " + msg + "\n");
20 function handleWindowEvent(e) {
21 if (this._browserElementParents) {
22 let beps = ChromeUtils.nondeterministicGetWeakMapKeys(
23 this._browserElementParents
25 beps.forEach(bep => bep._handleOwnerEvent(e));
29 function BrowserElementParent() {
30 debug("Creating new BrowserElementParent object");
33 BrowserElementParent.prototype = {
34 classDescription: "BrowserElementAPI implementation",
35 classID: Components.ID("{9f171ac4-0939-4ef8-b360-3408aedc3060}"),
36 contractID: "@mozilla.org/dom/browser-element-api;1",
37 QueryInterface: ChromeUtils.generateQI([
38 "nsIBrowserElementAPI",
39 "nsISupportsWeakReference",
42 setFrameLoader(frameLoader) {
43 debug("Setting frameLoader");
44 this._frameLoader = frameLoader;
45 this._frameElement = frameLoader.ownerElement;
46 if (!this._frameElement) {
47 debug("No frame element?");
50 // Listen to visibilitychange on the iframe's owner window, and forward
51 // changes down to the child. We want to do this while registering as few
52 // visibilitychange listeners on _window as possible, because such a listener
53 // may live longer than this BrowserElementParent object.
55 // To accomplish this, we register just one listener on the window, and have
56 // it reference a WeakMap whose keys are all the BrowserElementParent objects
57 // on the window. Then when the listener fires, we iterate over the
58 // WeakMap's keys (which we can do, because we're chrome) to notify the
59 // BrowserElementParents.
60 if (!this._window._browserElementParents) {
61 this._window._browserElementParents = new WeakMap();
62 let handler = handleWindowEvent.bind(this._window);
63 let windowEvents = ["visibilitychange"];
64 for (let event of windowEvents) {
65 Services.els.addSystemEventListener(
69 /* useCapture = */ true
74 this._window._browserElementParents.set(this, null);
76 // Insert ourself into the prompt service.
77 BrowserElementPromptService.mapFrameToBrowserElementParent(
81 this._setupMessageListener();
84 destroyFrameScripts() {
85 debug("Destroying frame scripts");
86 this._mm.sendAsyncMessage("browser-element-api:destroy");
89 _setupMessageListener() {
90 this._mm = this._frameLoader.messageManager;
91 this._mm.addMessageListener("browser-element-api:call", this);
94 receiveMessage(aMsg) {
95 if (!this._isAlive()) {
99 // Messages we receive are handed to functions which take a (data) argument,
100 // where |data| is the message manager's data object.
101 // We use a single message and dispatch to various function based
104 hello: this._recvHello,
107 let mmSecuritySensitiveCalls = {
108 showmodalprompt: this._handleShowModalPrompt,
111 if (aMsg.data.msg_name in mmCalls) {
112 return mmCalls[aMsg.data.msg_name].apply(this, arguments);
113 } else if (aMsg.data.msg_name in mmSecuritySensitiveCalls) {
114 return mmSecuritySensitiveCalls[aMsg.data.msg_name].apply(
122 _removeMessageListener() {
123 this._mm.removeMessageListener("browser-element-api:call", this);
127 * You shouldn't touch this._frameElement or this._window if _isAlive is
128 * false. (You'll likely get an exception if you do.)
132 !Cu.isDeadWrapper(this._frameElement) &&
133 !Cu.isDeadWrapper(this._frameElement.ownerDocument) &&
134 !Cu.isDeadWrapper(this._frameElement.ownerGlobal)
139 return this._frameElement.ownerGlobal;
142 _sendAsyncMsg(msg, data) {
149 this._mm.sendAsyncMessage("browser-element-api:call", data);
159 // Inform our child if our owner element's document is invisible. Note
160 // that we must do so here, rather than in the BrowserElementParent
161 // constructor, because the BrowserElementChild may not be initialized when
162 // we run our constructor.
163 if (this._window.document.hidden) {
164 this._ownerVisibilityChange();
169 * Fire either a vanilla or a custom event, depending on the contents of
172 _fireEventFromMsg(data) {
173 let detail = data.json;
174 let name = detail.msg_name;
176 // For events that send a "_payload_" property, we just want to transmit
177 // this in the event.
178 if ("_payload_" in detail) {
179 detail = detail._payload_;
182 debug("fireEventFromMsg: " + name + ", " + JSON.stringify(detail));
183 let evt = this._createEvent(name, detail, /* cancelable = */ false);
184 this._frameElement.dispatchEvent(evt);
187 _handleShowModalPrompt(data) {
188 // Fire a showmodalprmopt event on the iframe. When this method is called,
189 // the child is spinning in a nested event loop waiting for an
190 // unblock-modal-prompt message.
192 // If the embedder calls preventDefault() on the showmodalprompt event,
193 // we'll block the child until event.detail.unblock() is called.
195 // Otherwise, if preventDefault() is not called, we'll send the
196 // unblock-modal-prompt message to the child as soon as the event is done
199 let detail = data.json;
200 debug("handleShowPrompt " + JSON.stringify(detail));
202 // Strip off the windowID property from the object we send along in the
204 let windowID = detail.windowID;
205 delete detail.windowID;
206 debug("Event will have detail: " + JSON.stringify(detail));
207 let evt = this._createEvent(
210 /* cancelable = */ true
214 let unblockMsgSent = false;
215 function sendUnblockMsg() {
216 if (unblockMsgSent) {
219 unblockMsgSent = true;
221 // We don't need to sanitize evt.detail.returnValue (e.g. converting the
222 // return value of confirm() to a boolean); Gecko does that for us.
224 let data = { windowID, returnValue: evt.detail.returnValue };
225 self._sendAsyncMsg("unblock-modal-prompt", data);
228 Cu.exportFunction(sendUnblockMsg, evt.detail, { defineAs: "unblock" });
230 this._frameElement.dispatchEvent(evt);
232 if (!evt.defaultPrevented) {
233 // Unblock the inner frame immediately. Otherwise we'll unblock upon
234 // evt.detail.unblock().
239 _createEvent(evtName, detail, cancelable) {
240 // This will have to change if we ever want to send a CustomEvent with null
241 // detail. For now, it's OK.
242 if (detail !== undefined && detail !== null) {
243 detail = Cu.cloneInto(detail, this._window);
244 return new this._window.CustomEvent("mozbrowser" + evtName, {
251 return new this._window.Event("mozbrowser" + evtName, {
257 _handleOwnerEvent(evt) {
259 case "visibilitychange":
260 this._ownerVisibilityChange();
266 * Called when the visibility of the window which owns this iframe changes.
268 _ownerVisibilityChange() {
269 let bc = this._frameLoader?.browsingContext;
271 bc.isActive = !this._window.document.hidden;
276 var EXPORTED_SYMBOLS = ["BrowserElementParent"];