зеркало из https://github.com/mozilla/gecko-dev.git
281 строка
7.1 KiB
JavaScript
281 строка
7.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";
|
|
|
|
var EXPORTED_SYMBOLS = ["MessagePort", "MessageListener"];
|
|
|
|
ChromeUtils.defineModuleGetter(
|
|
this,
|
|
"PromiseUtils",
|
|
"resource://gre/modules/PromiseUtils.jsm"
|
|
);
|
|
|
|
class MessageListener {
|
|
constructor() {
|
|
this.listeners = new Map();
|
|
}
|
|
|
|
keys() {
|
|
return this.listeners.keys();
|
|
}
|
|
|
|
has(name) {
|
|
return this.listeners.has(name);
|
|
}
|
|
|
|
callListeners(message) {
|
|
let listeners = this.listeners.get(message.name);
|
|
if (!listeners) {
|
|
return;
|
|
}
|
|
|
|
for (let listener of listeners.values()) {
|
|
try {
|
|
listener(message);
|
|
} catch (e) {
|
|
Cu.reportError(e);
|
|
}
|
|
}
|
|
}
|
|
|
|
addMessageListener(name, callback) {
|
|
if (!this.listeners.has(name)) {
|
|
this.listeners.set(name, new Set([callback]));
|
|
} else {
|
|
this.listeners.get(name).add(callback);
|
|
}
|
|
}
|
|
|
|
removeMessageListener(name, callback) {
|
|
if (!this.listeners.has(name)) {
|
|
return;
|
|
}
|
|
|
|
this.listeners.get(name).delete(callback);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* A message port sits on each side of the process boundary for every remote
|
|
* page. Each has a port ID that is unique to the message manager it talks
|
|
* through.
|
|
*
|
|
* We roughly implement the same contract as nsIMessageSender and
|
|
* nsIMessageListenerManager
|
|
*/
|
|
class MessagePort {
|
|
constructor(messageManagerOrActor, portID) {
|
|
this.messageManager = messageManagerOrActor;
|
|
this.portID = portID;
|
|
this.destroyed = false;
|
|
this.listener = new MessageListener();
|
|
|
|
// This is a sparse array of pending requests. The id of each request is
|
|
// simply its index in the array. The next id is the current length of the
|
|
// array (which includes the count of missing indexes).
|
|
this.requests = [];
|
|
|
|
this.message = this.message.bind(this);
|
|
this.receiveRequest = this.receiveRequest.bind(this);
|
|
this.receiveResponse = this.receiveResponse.bind(this);
|
|
this.addMessageListeners();
|
|
}
|
|
|
|
addMessageListeners() {
|
|
if (!(this.messageManager instanceof Ci.nsIMessageSender)) {
|
|
return;
|
|
}
|
|
|
|
this.messageManager.addMessageListener("RemotePage:Message", this.message);
|
|
this.messageManager.addMessageListener(
|
|
"RemotePage:Request",
|
|
this.receiveRequest
|
|
);
|
|
this.messageManager.addMessageListener(
|
|
"RemotePage:Response",
|
|
this.receiveResponse
|
|
);
|
|
}
|
|
|
|
removeMessageListeners() {
|
|
if (!(this.messageManager instanceof Ci.nsIMessageSender)) {
|
|
return;
|
|
}
|
|
|
|
this.messageManager.removeMessageListener(
|
|
"RemotePage:Message",
|
|
this.message
|
|
);
|
|
this.messageManager.removeMessageListener(
|
|
"RemotePage:Request",
|
|
this.receiveRequest
|
|
);
|
|
this.messageManager.removeMessageListener(
|
|
"RemotePage:Response",
|
|
this.receiveResponse
|
|
);
|
|
}
|
|
|
|
// Called when the message manager used to connect to the other process has
|
|
// changed, i.e. when a tab is detached.
|
|
swapMessageManager(messageManager) {
|
|
this.removeMessageListeners();
|
|
this.messageManager = messageManager;
|
|
this.addMessageListeners();
|
|
}
|
|
|
|
// Sends a request to the other process and returns a promise that completes
|
|
// once the other process has responded to the request or some error occurs.
|
|
sendRequest(name, data = null) {
|
|
if (this.destroyed) {
|
|
return this.window.Promise.reject(
|
|
new Error("Message port has been destroyed")
|
|
);
|
|
}
|
|
|
|
let deferred = PromiseUtils.defer();
|
|
this.requests.push(deferred);
|
|
|
|
this.messageManager.sendAsyncMessage("RemotePage:Request", {
|
|
portID: this.portID,
|
|
requestID: this.requests.length - 1,
|
|
name,
|
|
data,
|
|
});
|
|
|
|
return this.wrapPromise(deferred.promise);
|
|
}
|
|
|
|
// Handles an IPC message to perform a request of some kind.
|
|
async receiveRequest({ data: messagedata }) {
|
|
if (this.destroyed || messagedata.portID != this.portID) {
|
|
return;
|
|
}
|
|
|
|
let data = {
|
|
portID: this.portID,
|
|
requestID: messagedata.requestID,
|
|
};
|
|
|
|
try {
|
|
data.resolve = await this.handleRequest(
|
|
messagedata.name,
|
|
messagedata.data
|
|
);
|
|
} catch (e) {
|
|
data.reject = e;
|
|
}
|
|
|
|
this.messageManager.sendAsyncMessage("RemotePage:Response", data);
|
|
}
|
|
|
|
// Handles an IPC message with the response of a request.
|
|
receiveResponse({ data: messagedata }) {
|
|
if (this.destroyed || messagedata.portID != this.portID) {
|
|
return;
|
|
}
|
|
|
|
let deferred = this.requests[messagedata.requestID];
|
|
if (!deferred) {
|
|
Cu.reportError("Received a response to an unknown request.");
|
|
return;
|
|
}
|
|
|
|
delete this.requests[messagedata.requestID];
|
|
|
|
if ("resolve" in messagedata) {
|
|
deferred.resolve(messagedata.resolve);
|
|
} else if ("reject" in messagedata) {
|
|
deferred.reject(messagedata.reject);
|
|
} else {
|
|
deferred.reject(new Error("Internal RPM error."));
|
|
}
|
|
}
|
|
|
|
// Handles an IPC message containing any message.
|
|
message({ data: messagedata }) {
|
|
if (this.destroyed || messagedata.portID != this.portID) {
|
|
return;
|
|
}
|
|
|
|
this.handleMessage(messagedata);
|
|
}
|
|
|
|
/* Adds a listener for messages. Many callbacks can be registered for the
|
|
* same message if necessary. An attempt to register the same callback for the
|
|
* same message twice will be ignored. When called the callback is passed an
|
|
* object with these properties:
|
|
* target: This message port
|
|
* name: The message name
|
|
* data: Any data sent with the message
|
|
*/
|
|
addMessageListener(name, callback) {
|
|
if (this.destroyed) {
|
|
throw new Error("Message port has been destroyed");
|
|
}
|
|
|
|
this.listener.addMessageListener(name, callback);
|
|
}
|
|
|
|
/*
|
|
* Removes a listener for messages.
|
|
*/
|
|
removeMessageListener(name, callback) {
|
|
if (this.destroyed) {
|
|
throw new Error("Message port has been destroyed");
|
|
}
|
|
|
|
this.listener.removeMessageListener(name, callback);
|
|
}
|
|
|
|
// Sends a message asynchronously to the other process
|
|
sendAsyncMessage(name, data = null) {
|
|
if (this.destroyed) {
|
|
throw new Error("Message port has been destroyed");
|
|
}
|
|
|
|
let id;
|
|
if (this.window) {
|
|
id = this.window.docShell.browsingContext.id;
|
|
}
|
|
if (this.messageManager instanceof Ci.nsIMessageSender) {
|
|
this.messageManager.sendAsyncMessage("RemotePage:Message", {
|
|
portID: this.portID,
|
|
browsingContextID: id,
|
|
name,
|
|
data,
|
|
});
|
|
} else {
|
|
this.messageManager.sendAsyncMessage(name, data);
|
|
}
|
|
}
|
|
|
|
// Called to destroy this port
|
|
destroy() {
|
|
try {
|
|
// This can fail in the child process if the tab has already been closed
|
|
this.removeMessageListeners();
|
|
} catch (e) {}
|
|
|
|
for (let deferred of this.requests) {
|
|
if (deferred) {
|
|
deferred.reject(new Error("Message port has been destroyed"));
|
|
}
|
|
}
|
|
|
|
this.messageManager = null;
|
|
this.destroyed = true;
|
|
this.portID = null;
|
|
this.listener = null;
|
|
this.requests = [];
|
|
}
|
|
|
|
wrapPromise(promise) {
|
|
return new this.window.Promise((resolve, reject) =>
|
|
promise.then(resolve, reject)
|
|
);
|
|
}
|
|
}
|