зеркало из https://github.com/mozilla/gecko-dev.git
278 строки
8.1 KiB
JavaScript
278 строки
8.1 KiB
JavaScript
/* 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";
|
|
|
|
/* BrowserElementParent injects script to listen for certain events in the
|
|
* child. We then listen to messages from the child script and take
|
|
* appropriate action here in the parent.
|
|
*/
|
|
|
|
const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
|
|
const { BrowserElementPromptService } = ChromeUtils.import(
|
|
"resource://gre/modules/BrowserElementPromptService.jsm"
|
|
);
|
|
|
|
function debug(msg) {
|
|
// dump("BrowserElementParent - " + msg + "\n");
|
|
}
|
|
|
|
function handleWindowEvent(e) {
|
|
if (this._browserElementParents) {
|
|
let beps = ChromeUtils.nondeterministicGetWeakMapKeys(
|
|
this._browserElementParents
|
|
);
|
|
beps.forEach(bep => bep._handleOwnerEvent(e));
|
|
}
|
|
}
|
|
|
|
function BrowserElementParent() {
|
|
debug("Creating new BrowserElementParent object");
|
|
}
|
|
|
|
BrowserElementParent.prototype = {
|
|
classDescription: "BrowserElementAPI implementation",
|
|
classID: Components.ID("{9f171ac4-0939-4ef8-b360-3408aedc3060}"),
|
|
contractID: "@mozilla.org/dom/browser-element-api;1",
|
|
QueryInterface: ChromeUtils.generateQI([
|
|
"nsIBrowserElementAPI",
|
|
"nsISupportsWeakReference",
|
|
]),
|
|
|
|
setFrameLoader(frameLoader) {
|
|
debug("Setting frameLoader");
|
|
this._frameLoader = frameLoader;
|
|
this._frameElement = frameLoader.ownerElement;
|
|
if (!this._frameElement) {
|
|
debug("No frame element?");
|
|
return;
|
|
}
|
|
// Listen to visibilitychange on the iframe's owner window, and forward
|
|
// changes down to the child. We want to do this while registering as few
|
|
// visibilitychange listeners on _window as possible, because such a listener
|
|
// may live longer than this BrowserElementParent object.
|
|
//
|
|
// To accomplish this, we register just one listener on the window, and have
|
|
// it reference a WeakMap whose keys are all the BrowserElementParent objects
|
|
// on the window. Then when the listener fires, we iterate over the
|
|
// WeakMap's keys (which we can do, because we're chrome) to notify the
|
|
// BrowserElementParents.
|
|
if (!this._window._browserElementParents) {
|
|
this._window._browserElementParents = new WeakMap();
|
|
let handler = handleWindowEvent.bind(this._window);
|
|
let windowEvents = ["visibilitychange"];
|
|
for (let event of windowEvents) {
|
|
Services.els.addSystemEventListener(
|
|
this._window,
|
|
event,
|
|
handler,
|
|
/* useCapture = */ true
|
|
);
|
|
}
|
|
}
|
|
|
|
this._window._browserElementParents.set(this, null);
|
|
|
|
// Insert ourself into the prompt service.
|
|
BrowserElementPromptService.mapFrameToBrowserElementParent(
|
|
this._frameElement,
|
|
this
|
|
);
|
|
this._setupMessageListener();
|
|
},
|
|
|
|
destroyFrameScripts() {
|
|
debug("Destroying frame scripts");
|
|
this._mm.sendAsyncMessage("browser-element-api:destroy");
|
|
},
|
|
|
|
_setupMessageListener() {
|
|
this._mm = this._frameLoader.messageManager;
|
|
this._mm.addMessageListener("browser-element-api:call", this);
|
|
},
|
|
|
|
receiveMessage(aMsg) {
|
|
if (!this._isAlive()) {
|
|
return undefined;
|
|
}
|
|
|
|
// Messages we receive are handed to functions which take a (data) argument,
|
|
// where |data| is the message manager's data object.
|
|
// We use a single message and dispatch to various function based
|
|
// on data.msg_name
|
|
let mmCalls = {
|
|
hello: this._recvHello,
|
|
};
|
|
|
|
let mmSecuritySensitiveCalls = {
|
|
showmodalprompt: this._handleShowModalPrompt,
|
|
};
|
|
|
|
if (aMsg.data.msg_name in mmCalls) {
|
|
return mmCalls[aMsg.data.msg_name].apply(this, arguments);
|
|
} else if (aMsg.data.msg_name in mmSecuritySensitiveCalls) {
|
|
return mmSecuritySensitiveCalls[aMsg.data.msg_name].apply(
|
|
this,
|
|
arguments
|
|
);
|
|
}
|
|
return undefined;
|
|
},
|
|
|
|
_removeMessageListener() {
|
|
this._mm.removeMessageListener("browser-element-api:call", this);
|
|
},
|
|
|
|
/**
|
|
* You shouldn't touch this._frameElement or this._window if _isAlive is
|
|
* false. (You'll likely get an exception if you do.)
|
|
*/
|
|
_isAlive() {
|
|
return (
|
|
!Cu.isDeadWrapper(this._frameElement) &&
|
|
!Cu.isDeadWrapper(this._frameElement.ownerDocument) &&
|
|
!Cu.isDeadWrapper(this._frameElement.ownerGlobal)
|
|
);
|
|
},
|
|
|
|
get _window() {
|
|
return this._frameElement.ownerGlobal;
|
|
},
|
|
|
|
_sendAsyncMsg(msg, data) {
|
|
try {
|
|
if (!data) {
|
|
data = {};
|
|
}
|
|
|
|
data.msg_name = msg;
|
|
this._mm.sendAsyncMessage("browser-element-api:call", data);
|
|
} catch (e) {
|
|
return false;
|
|
}
|
|
return true;
|
|
},
|
|
|
|
_recvHello() {
|
|
debug("recvHello");
|
|
|
|
// Inform our child if our owner element's document is invisible. Note
|
|
// that we must do so here, rather than in the BrowserElementParent
|
|
// constructor, because the BrowserElementChild may not be initialized when
|
|
// we run our constructor.
|
|
if (this._window.document.hidden) {
|
|
this._ownerVisibilityChange();
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Fire either a vanilla or a custom event, depending on the contents of
|
|
* |data|.
|
|
*/
|
|
_fireEventFromMsg(data) {
|
|
let detail = data.json;
|
|
let name = detail.msg_name;
|
|
|
|
// For events that send a "_payload_" property, we just want to transmit
|
|
// this in the event.
|
|
if ("_payload_" in detail) {
|
|
detail = detail._payload_;
|
|
}
|
|
|
|
debug("fireEventFromMsg: " + name + ", " + JSON.stringify(detail));
|
|
let evt = this._createEvent(name, detail, /* cancelable = */ false);
|
|
this._frameElement.dispatchEvent(evt);
|
|
},
|
|
|
|
_handleShowModalPrompt(data) {
|
|
// Fire a showmodalprmopt event on the iframe. When this method is called,
|
|
// the child is spinning in a nested event loop waiting for an
|
|
// unblock-modal-prompt message.
|
|
//
|
|
// If the embedder calls preventDefault() on the showmodalprompt event,
|
|
// we'll block the child until event.detail.unblock() is called.
|
|
//
|
|
// Otherwise, if preventDefault() is not called, we'll send the
|
|
// unblock-modal-prompt message to the child as soon as the event is done
|
|
// dispatching.
|
|
|
|
let detail = data.json;
|
|
debug("handleShowPrompt " + JSON.stringify(detail));
|
|
|
|
// Strip off the windowID property from the object we send along in the
|
|
// event.
|
|
let windowID = detail.windowID;
|
|
delete detail.windowID;
|
|
debug("Event will have detail: " + JSON.stringify(detail));
|
|
let evt = this._createEvent(
|
|
"showmodalprompt",
|
|
detail,
|
|
/* cancelable = */ true
|
|
);
|
|
|
|
let self = this;
|
|
let unblockMsgSent = false;
|
|
function sendUnblockMsg() {
|
|
if (unblockMsgSent) {
|
|
return;
|
|
}
|
|
unblockMsgSent = true;
|
|
|
|
// We don't need to sanitize evt.detail.returnValue (e.g. converting the
|
|
// return value of confirm() to a boolean); Gecko does that for us.
|
|
|
|
let data = { windowID, returnValue: evt.detail.returnValue };
|
|
self._sendAsyncMsg("unblock-modal-prompt", data);
|
|
}
|
|
|
|
Cu.exportFunction(sendUnblockMsg, evt.detail, { defineAs: "unblock" });
|
|
|
|
this._frameElement.dispatchEvent(evt);
|
|
|
|
if (!evt.defaultPrevented) {
|
|
// Unblock the inner frame immediately. Otherwise we'll unblock upon
|
|
// evt.detail.unblock().
|
|
sendUnblockMsg();
|
|
}
|
|
},
|
|
|
|
_createEvent(evtName, detail, cancelable) {
|
|
// This will have to change if we ever want to send a CustomEvent with null
|
|
// detail. For now, it's OK.
|
|
if (detail !== undefined && detail !== null) {
|
|
detail = Cu.cloneInto(detail, this._window);
|
|
return new this._window.CustomEvent("mozbrowser" + evtName, {
|
|
bubbles: true,
|
|
cancelable,
|
|
detail,
|
|
});
|
|
}
|
|
|
|
return new this._window.Event("mozbrowser" + evtName, {
|
|
bubbles: true,
|
|
cancelable,
|
|
});
|
|
},
|
|
|
|
_handleOwnerEvent(evt) {
|
|
switch (evt.type) {
|
|
case "visibilitychange":
|
|
this._ownerVisibilityChange();
|
|
break;
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Called when the visibility of the window which owns this iframe changes.
|
|
*/
|
|
_ownerVisibilityChange() {
|
|
let bc = this._frameLoader?.browsingContext;
|
|
if (bc) {
|
|
bc.isActive = !this._window.document.hidden;
|
|
}
|
|
},
|
|
};
|
|
|
|
var EXPORTED_SYMBOLS = ["BrowserElementParent"];
|