2019-11-17 13:18:34 +03:00
|
|
|
/* 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";
|
|
|
|
|
|
|
|
/**
|
|
|
|
* This @file implements the child side of Conduits, an abstraction over
|
|
|
|
* Fission IPC for extension API subject. See {@link ConduitsParent.jsm}
|
|
|
|
* for more details about the overall design.
|
2019-11-20 22:20:26 +03:00
|
|
|
*
|
|
|
|
* @typedef {object} MessageData
|
|
|
|
* @prop {ConduitID} [target]
|
|
|
|
* @prop {ConduitID} [sender]
|
|
|
|
* @prop {boolean} query
|
|
|
|
* @prop {object} arg
|
2019-11-17 13:18:34 +03:00
|
|
|
*/
|
|
|
|
|
2021-10-06 15:28:16 +03:00
|
|
|
const EXPORTED_SYMBOLS = [
|
|
|
|
"BaseConduit",
|
|
|
|
"ConduitsChild",
|
|
|
|
"ProcessConduitsChild",
|
|
|
|
];
|
2019-11-17 13:18:34 +03:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Base class for both child (Point) and parent (Broadcast) side of conduits,
|
|
|
|
* handles setting up send/receive method stubs.
|
|
|
|
*/
|
|
|
|
class BaseConduit {
|
|
|
|
/**
|
|
|
|
* @param {object} subject
|
|
|
|
* @param {ConduitAddress} address
|
|
|
|
*/
|
|
|
|
constructor(subject, address) {
|
|
|
|
this.subject = subject;
|
|
|
|
this.address = address;
|
|
|
|
this.id = address.id;
|
|
|
|
|
|
|
|
for (let name of address.send || []) {
|
|
|
|
this[`send${name}`] = this._send.bind(this, name, false);
|
|
|
|
}
|
|
|
|
for (let name of address.query || []) {
|
|
|
|
this[`query${name}`] = this._send.bind(this, name, true);
|
|
|
|
}
|
|
|
|
|
|
|
|
this.recv = new Map();
|
|
|
|
for (let name of address.recv || []) {
|
|
|
|
let method = this.subject[`recv${name}`];
|
|
|
|
if (!method) {
|
|
|
|
throw new Error(`recv${name} not found for conduit ${this.id}`);
|
|
|
|
}
|
|
|
|
this.recv.set(name, method.bind(this.subject));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Internal, partially @abstract, uses the actor to send the message/query.
|
|
|
|
* @param {string} method
|
|
|
|
* @param {boolean} query Flag indicating a response is expected.
|
|
|
|
* @param {JSWindowActor} actor
|
2019-11-20 22:20:26 +03:00
|
|
|
* @param {MessageData} data
|
2019-11-17 13:18:34 +03:00
|
|
|
* @returns {Promise?}
|
|
|
|
*/
|
|
|
|
_send(method, query, actor, data) {
|
|
|
|
if (query) {
|
|
|
|
return actor.sendQuery(method, data);
|
|
|
|
}
|
|
|
|
actor.sendAsyncMessage(method, data);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Internal, calls the specific recvX method based on the message.
|
|
|
|
* @param {string} name Message/method name.
|
|
|
|
* @param {object} arg Message data, the one and only method argument.
|
|
|
|
* @param {object} meta Metadata about the method call.
|
|
|
|
*/
|
|
|
|
async _recv(name, arg, meta) {
|
|
|
|
let method = this.recv.get(name);
|
|
|
|
if (!method) {
|
|
|
|
throw new Error(`recv${name} not found for conduit ${this.id}`);
|
|
|
|
}
|
|
|
|
try {
|
|
|
|
return await method(arg, meta);
|
|
|
|
} catch (e) {
|
|
|
|
if (meta.query) {
|
|
|
|
return Promise.reject(e);
|
|
|
|
}
|
|
|
|
Cu.reportError(e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Child side conduit, can only send/receive point-to-point messages via the
|
|
|
|
* one specific ConduitsChild actor.
|
|
|
|
*/
|
|
|
|
class PointConduit extends BaseConduit {
|
|
|
|
constructor(subject, address, actor) {
|
|
|
|
super(subject, address);
|
|
|
|
this.actor = actor;
|
|
|
|
this.actor.sendAsyncMessage("ConduitOpened", { arg: address });
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Internal, sends messages via the actor, used by sendX stubs.
|
|
|
|
* @param {string} method
|
|
|
|
* @param {boolean} query
|
2019-11-18 16:01:59 +03:00
|
|
|
* @param {object?} arg
|
2019-11-17 13:18:34 +03:00
|
|
|
* @returns {Promise?}
|
|
|
|
*/
|
2019-11-18 16:01:59 +03:00
|
|
|
_send(method, query, arg = {}) {
|
2019-11-17 13:18:34 +03:00
|
|
|
if (!this.actor) {
|
|
|
|
throw new Error(`send${method} on closed conduit ${this.id}`);
|
|
|
|
}
|
|
|
|
let sender = this.id;
|
|
|
|
return super._send(method, query, this.actor, { arg, query, sender });
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2019-11-18 16:01:59 +03:00
|
|
|
* Closes the conduit from further IPC, notifies the parent side by default.
|
|
|
|
* @param {boolean} silent
|
2019-11-17 13:18:34 +03:00
|
|
|
*/
|
2019-11-18 16:01:59 +03:00
|
|
|
close(silent = false) {
|
|
|
|
let { actor } = this;
|
|
|
|
if (actor) {
|
2019-11-17 13:18:34 +03:00
|
|
|
this.actor = null;
|
2019-11-18 16:01:59 +03:00
|
|
|
actor.conduits.delete(this.id);
|
|
|
|
if (!silent) {
|
2020-10-21 02:27:39 +03:00
|
|
|
// Catch any exceptions that can occur if the conduit is closed while
|
|
|
|
// the actor is being destroyed due to the containing browser being closed.
|
|
|
|
// This should be treated as if the silent flag was passed.
|
|
|
|
try {
|
|
|
|
actor.sendAsyncMessage("ConduitClosed", { sender: this.id });
|
|
|
|
} catch (ex) {}
|
2019-11-18 16:01:59 +03:00
|
|
|
}
|
2019-11-17 13:18:34 +03:00
|
|
|
}
|
2020-11-16 04:24:14 +03:00
|
|
|
this.closeCallback?.();
|
|
|
|
this.closeCallback = null;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Set the callback to be called when the conduit is closed.
|
|
|
|
* @param {function} callback
|
|
|
|
*/
|
|
|
|
setCloseCallback(callback) {
|
|
|
|
this.closeCallback = callback;
|
2019-11-17 13:18:34 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Implements the child side of the Conduits actor, manages conduit lifetimes.
|
|
|
|
*/
|
|
|
|
class ConduitsChild extends JSWindowActorChild {
|
|
|
|
constructor() {
|
|
|
|
super();
|
|
|
|
this.conduits = new Map();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Public entry point a child-side subject uses to open a conduit.
|
|
|
|
* @param {object} subject
|
|
|
|
* @param {ConduitAddress} address
|
|
|
|
* @returns {PointConduit}
|
|
|
|
*/
|
|
|
|
openConduit(subject, address) {
|
|
|
|
let conduit = new PointConduit(subject, address, this);
|
|
|
|
this.conduits.set(conduit.id, conduit);
|
|
|
|
return conduit;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2019-11-18 16:01:59 +03:00
|
|
|
* JSWindowActor method, routes the message to the target subject.
|
2019-11-20 22:20:26 +03:00
|
|
|
* @param {string} name
|
|
|
|
* @param {MessageData|MessageData[]} data
|
2019-11-17 13:18:34 +03:00
|
|
|
* @returns {Promise?}
|
|
|
|
*/
|
2019-11-20 22:20:26 +03:00
|
|
|
receiveMessage({ name, data }) {
|
|
|
|
// Batch of webRequest events, run each and return results, ignoring errors.
|
|
|
|
if (Array.isArray(data)) {
|
|
|
|
let run = data => this.receiveMessage({ name, data });
|
|
|
|
return Promise.all(data.map(data => run(data).catch(Cu.reportError)));
|
|
|
|
}
|
|
|
|
|
|
|
|
let { target, arg, query, sender } = data;
|
2019-11-17 13:18:34 +03:00
|
|
|
let conduit = this.conduits.get(target);
|
|
|
|
if (!conduit) {
|
2019-11-18 16:01:59 +03:00
|
|
|
throw new Error(`${name} for closed conduit ${target}: ${uneval(arg)}`);
|
2019-11-17 13:18:34 +03:00
|
|
|
}
|
|
|
|
return conduit._recv(name, arg, { sender, query, actor: this });
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2020-10-21 02:27:39 +03:00
|
|
|
* JSWindowActor method, ensure cleanup.
|
2019-11-17 13:18:34 +03:00
|
|
|
*/
|
2020-10-21 02:27:39 +03:00
|
|
|
didDestroy() {
|
2019-11-17 13:18:34 +03:00
|
|
|
for (let conduit of this.conduits.values()) {
|
2019-11-18 16:01:59 +03:00
|
|
|
conduit.close(true);
|
2019-11-17 13:18:34 +03:00
|
|
|
}
|
|
|
|
this.conduits.clear();
|
|
|
|
}
|
|
|
|
}
|
2021-10-06 15:28:16 +03:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Child side of the Conduits process actor. Same code as JSWindowActor.
|
|
|
|
*/
|
|
|
|
class ProcessConduitsChild extends JSProcessActorChild {
|
|
|
|
constructor() {
|
|
|
|
super();
|
|
|
|
this.conduits = new Map();
|
|
|
|
}
|
|
|
|
|
|
|
|
openConduit = ConduitsChild.prototype.openConduit;
|
|
|
|
receiveMessage = ConduitsChild.prototype.receiveMessage;
|
|
|
|
willDestroy = ConduitsChild.prototype.willDestroy;
|
|
|
|
didDestroy = ConduitsChild.prototype.didDestroy;
|
|
|
|
}
|