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
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 * This @file implements the child side of Conduits, an abstraction over
7 * Fission IPC for extension API subject. See {@link ConduitsParent.jsm}
8 * for more details about the overall design.
10 * @typedef {object} MessageData
11 * @property {ConduitID} [target]
12 * @property {ConduitID} [sender]
13 * @property {boolean} query
14 * @property {object} arg
18 * Base class for both child (Point) and parent (Broadcast) side of conduits,
19 * handles setting up send/receive method stubs.
21 export class BaseConduit {
23 * @param {object} subject
24 * @param {ConduitAddress} address
26 constructor(subject, address) {
27 this.subject = subject;
28 this.address = address;
31 for (let name of address.send || []) {
32 this[`send${name}`] = this._send.bind(this, name, false);
34 for (let name of address.query || []) {
35 this[`query${name}`] = this._send.bind(this, name, true);
38 this.recv = new Map();
39 for (let name of address.recv || []) {
40 let method = this.subject[`recv${name}`];
42 throw new Error(`recv${name} not found for conduit ${this.id}`);
44 this.recv.set(name, method.bind(this.subject));
49 * Internal, partially @abstract, uses the actor to send the message/query.
51 * @param {string} method
52 * @param {boolean} query Flag indicating a response is expected.
53 * @param {JSWindowActor} actor
54 * @param {MessageData} data
57 _send(method, query, actor, data) {
59 return actor.sendQuery(method, data);
61 actor.sendAsyncMessage(method, data);
65 * Internal, calls the specific recvX method based on the message.
67 * @param {string} name Message/method name.
68 * @param {object} arg Message data, the one and only method argument.
69 * @param {object} meta Metadata about the method call.
71 async _recv(name, arg, meta) {
72 let method = this.recv.get(name);
74 throw new Error(`recv${name} not found for conduit ${this.id}`);
77 return await method(arg, meta);
80 return Promise.reject(e);
88 * Child side conduit, can only send/receive point-to-point messages via the
89 * one specific ConduitsChild actor.
91 export class PointConduit extends BaseConduit {
92 constructor(subject, address, actor) {
93 super(subject, address);
95 this.actor.sendAsyncMessage("ConduitOpened", { arg: address });
99 * Internal, sends messages via the actor, used by sendX stubs.
101 * @param {string} method
102 * @param {boolean} query
103 * @param {object?} arg
104 * @returns {Promise?}
106 _send(method, query, arg = {}) {
108 throw new Error(`send${method} on closed conduit ${this.id}`);
110 let sender = this.id;
111 return super._send(method, query, this.actor, { arg, query, sender });
115 * Closes the conduit from further IPC, notifies the parent side by default.
117 * @param {boolean} silent
119 close(silent = false) {
120 let { actor } = this;
123 actor.conduits.delete(this.id);
125 // Catch any exceptions that can occur if the conduit is closed while
126 // the actor is being destroyed due to the containing browser being closed.
127 // This should be treated as if the silent flag was passed.
129 actor.sendAsyncMessage("ConduitClosed", { sender: this.id });
133 this.closeCallback?.();
134 this.closeCallback = null;
138 * Set the callback to be called when the conduit is closed.
140 * @param {Function} callback
142 setCloseCallback(callback) {
143 this.closeCallback = callback;
148 * Implements the child side of the Conduits actor, manages conduit lifetimes.
150 export class ConduitsChild extends JSWindowActorChild {
153 this.conduits = new Map();
157 * Public entry point a child-side subject uses to open a conduit.
159 * @param {object} subject
160 * @param {ConduitAddress} address
161 * @returns {PointConduit}
163 openConduit(subject, address) {
164 let conduit = new PointConduit(subject, address, this);
165 this.conduits.set(conduit.id, conduit);
170 * JSWindowActor method, routes the message to the target subject.
172 * @param {object} options
173 * @param {string} options.name
174 * @param {MessageData | MessageData[]} options.data
175 * @returns {Promise?}
177 receiveMessage({ name, data }) {
178 // Batch of webRequest events, run each and return results, ignoring errors.
179 if (Array.isArray(data)) {
180 let run = data => this.receiveMessage({ name, data });
181 return Promise.all(data.map(data => run(data).catch(Cu.reportError)));
184 let { target, arg, query, sender } = data;
185 let conduit = this.conduits.get(target);
187 throw new Error(`${name} for closed conduit ${target}: ${uneval(arg)}`);
189 return conduit._recv(name, arg, { sender, query, actor: this });
193 * JSWindowActor method, ensure cleanup.
196 for (let conduit of this.conduits.values()) {
199 this.conduits.clear();
204 * Child side of the Conduits process actor. Same code as JSWindowActor.
206 export class ProcessConduitsChild extends JSProcessActorChild {
209 this.conduits = new Map();
212 openConduit = ConduitsChild.prototype.openConduit;
213 receiveMessage = ConduitsChild.prototype.receiveMessage;
214 willDestroy = ConduitsChild.prototype.willDestroy;
215 didDestroy = ConduitsChild.prototype.didDestroy;