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 { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
13 const { BrowserElementPromptService } = ChromeUtils.import(
14 "resource://gre/modules/BrowserElementPromptService.jsm"
18 // dump("BrowserElementParent - " + msg + "\n");
21 function handleWindowEvent(e) {
22 if (this._browserElementParents) {
23 let beps = ChromeUtils.nondeterministicGetWeakMapKeys(
24 this._browserElementParents
26 beps.forEach(bep => bep._handleOwnerEvent(e));
30 function BrowserElementParent() {
31 debug("Creating new BrowserElementParent object");
34 BrowserElementParent.prototype = {
35 classDescription: "BrowserElementAPI implementation",
36 classID: Components.ID("{9f171ac4-0939-4ef8-b360-3408aedc3060}"),
37 contractID: "@mozilla.org/dom/browser-element-api;1",
38 QueryInterface: ChromeUtils.generateQI([
39 "nsIBrowserElementAPI",
40 "nsISupportsWeakReference",
43 setFrameLoader(frameLoader) {
44 debug("Setting frameLoader");
45 this._frameLoader = frameLoader;
46 this._frameElement = frameLoader.ownerElement;
47 if (!this._frameElement) {
48 debug("No frame element?");
51 // Listen to visibilitychange on the iframe's owner window, and forward
52 // changes down to the child. We want to do this while registering as few
53 // visibilitychange listeners on _window as possible, because such a listener
54 // may live longer than this BrowserElementParent object.
56 // To accomplish this, we register just one listener on the window, and have
57 // it reference a WeakMap whose keys are all the BrowserElementParent objects
58 // on the window. Then when the listener fires, we iterate over the
59 // WeakMap's keys (which we can do, because we're chrome) to notify the
60 // BrowserElementParents.
61 if (!this._window._browserElementParents) {
62 this._window._browserElementParents = new WeakMap();
63 let handler = handleWindowEvent.bind(this._window);
64 let windowEvents = ["visibilitychange"];
65 for (let event of windowEvents) {
66 Services.els.addSystemEventListener(
70 /* useCapture = */ true
75 this._window._browserElementParents.set(this, null);
77 // Insert ourself into the prompt service.
78 BrowserElementPromptService.mapFrameToBrowserElementParent(
82 this._setupMessageListener();
85 destroyFrameScripts() {
86 debug("Destroying frame scripts");
87 this._mm.sendAsyncMessage("browser-element-api:destroy");
90 _setupMessageListener() {
91 this._mm = this._frameLoader.messageManager;
92 this._mm.addMessageListener("browser-element-api:call", this);
95 receiveMessage(aMsg) {
96 if (!this._isAlive()) {
100 // Messages we receive are handed to functions which take a (data) argument,
101 // where |data| is the message manager's data object.
102 // We use a single message and dispatch to various function based
105 hello: this._recvHello,
108 let mmSecuritySensitiveCalls = {
109 showmodalprompt: this._handleShowModalPrompt,
112 if (aMsg.data.msg_name in mmCalls) {
113 return mmCalls[aMsg.data.msg_name].apply(this, arguments);
114 } else if (aMsg.data.msg_name in mmSecuritySensitiveCalls) {
115 return mmSecuritySensitiveCalls[aMsg.data.msg_name].apply(
123 _removeMessageListener() {
124 this._mm.removeMessageListener("browser-element-api:call", this);
128 * You shouldn't touch this._frameElement or this._window if _isAlive is
129 * false. (You'll likely get an exception if you do.)
133 !Cu.isDeadWrapper(this._frameElement) &&
134 !Cu.isDeadWrapper(this._frameElement.ownerDocument) &&
135 !Cu.isDeadWrapper(this._frameElement.ownerGlobal)
140 return this._frameElement.ownerGlobal;
143 _sendAsyncMsg(msg, data) {
150 this._mm.sendAsyncMessage("browser-element-api:call", data);
160 // Inform our child if our owner element's document is invisible. Note
161 // that we must do so here, rather than in the BrowserElementParent
162 // constructor, because the BrowserElementChild may not be initialized when
163 // we run our constructor.
164 if (this._window.document.hidden) {
165 this._ownerVisibilityChange();
170 * Fire either a vanilla or a custom event, depending on the contents of
173 _fireEventFromMsg(data) {
174 let detail = data.json;
175 let name = detail.msg_name;
177 // For events that send a "_payload_" property, we just want to transmit
178 // this in the event.
179 if ("_payload_" in detail) {
180 detail = detail._payload_;
183 debug("fireEventFromMsg: " + name + ", " + JSON.stringify(detail));
184 let evt = this._createEvent(name, detail, /* cancelable = */ false);
185 this._frameElement.dispatchEvent(evt);
188 _handleShowModalPrompt(data) {
189 // Fire a showmodalprmopt event on the iframe. When this method is called,
190 // the child is spinning in a nested event loop waiting for an
191 // unblock-modal-prompt message.
193 // If the embedder calls preventDefault() on the showmodalprompt event,
194 // we'll block the child until event.detail.unblock() is called.
196 // Otherwise, if preventDefault() is not called, we'll send the
197 // unblock-modal-prompt message to the child as soon as the event is done
200 let detail = data.json;
201 debug("handleShowPrompt " + JSON.stringify(detail));
203 // Strip off the windowID property from the object we send along in the
205 let windowID = detail.windowID;
206 delete detail.windowID;
207 debug("Event will have detail: " + JSON.stringify(detail));
208 let evt = this._createEvent(
211 /* cancelable = */ true
215 let unblockMsgSent = false;
216 function sendUnblockMsg() {
217 if (unblockMsgSent) {
220 unblockMsgSent = true;
222 // We don't need to sanitize evt.detail.returnValue (e.g. converting the
223 // return value of confirm() to a boolean); Gecko does that for us.
225 let data = { windowID, returnValue: evt.detail.returnValue };
226 self._sendAsyncMsg("unblock-modal-prompt", data);
229 Cu.exportFunction(sendUnblockMsg, evt.detail, { defineAs: "unblock" });
231 this._frameElement.dispatchEvent(evt);
233 if (!evt.defaultPrevented) {
234 // Unblock the inner frame immediately. Otherwise we'll unblock upon
235 // evt.detail.unblock().
240 _createEvent(evtName, detail, cancelable) {
241 // This will have to change if we ever want to send a CustomEvent with null
242 // detail. For now, it's OK.
243 if (detail !== undefined && detail !== null) {
244 detail = Cu.cloneInto(detail, this._window);
245 return new this._window.CustomEvent("mozbrowser" + evtName, {
252 return new this._window.Event("mozbrowser" + evtName, {
258 _handleOwnerEvent(evt) {
260 case "visibilitychange":
261 this._ownerVisibilityChange();
267 * Called when the visibility of the window which owns this iframe changes.
269 _ownerVisibilityChange() {
270 let bc = this._frameLoader?.browsingContext;
272 bc.isActive = !this._window.document.hidden;
277 var EXPORTED_SYMBOLS = ["BrowserElementParent"];