Last active
June 26, 2020 16:00
-
-
Save hotsoft-desenv2/b381cba3880e2025f29886ec189406fd to your computer and use it in GitHub Desktop.
resource://gre/modules/remotepagemanager/MessagePort.jsm
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* 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) | |
); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment