Bug 1871127 - Add tsconfig, basic types, and fix or ignore remaining type errors...
[gecko.git] / toolkit / components / extensions / ConduitsChild.sys.mjs
blobc5774ab39c67b923d74b88629970e3a6cd077c19
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/. */
5 /**
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.
9  *
10  * @typedef {object} MessageData
11  * @property {ConduitID} [target]
12  * @property {ConduitID} [sender]
13  * @property {boolean} query
14  * @property {object} arg
15  */
17 /**
18  * Base class for both child (Point) and parent (Broadcast) side of conduits,
19  * handles setting up send/receive method stubs.
20  */
21 export class BaseConduit {
22   /**
23    * @param {object} subject
24    * @param {ConduitAddress} address
25    */
26   constructor(subject, address) {
27     this.subject = subject;
28     this.address = address;
29     this.id = address.id;
31     for (let name of address.send || []) {
32       this[`send${name}`] = this._send.bind(this, name, false);
33     }
34     for (let name of address.query || []) {
35       this[`query${name}`] = this._send.bind(this, name, true);
36     }
38     this.recv = new Map();
39     for (let name of address.recv || []) {
40       let method = this.subject[`recv${name}`];
41       if (!method) {
42         throw new Error(`recv${name} not found for conduit ${this.id}`);
43       }
44       this.recv.set(name, method.bind(this.subject));
45     }
46   }
48   /**
49    * Internal, partially @abstract, uses the actor to send the message/query.
50    *
51    * @param {string} method
52    * @param {boolean} query Flag indicating a response is expected.
53    * @param {JSWindowActor} actor
54    * @param {MessageData} data
55    * @returns {Promise?}
56    */
57   _send(method, query, actor, data) {
58     if (query) {
59       return actor.sendQuery(method, data);
60     }
61     actor.sendAsyncMessage(method, data);
62   }
64   /**
65    * Internal, calls the specific recvX method based on the message.
66    *
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.
70    */
71   async _recv(name, arg, meta) {
72     let method = this.recv.get(name);
73     if (!method) {
74       throw new Error(`recv${name} not found for conduit ${this.id}`);
75     }
76     try {
77       return await method(arg, meta);
78     } catch (e) {
79       if (meta.query) {
80         return Promise.reject(e);
81       }
82       Cu.reportError(e);
83     }
84   }
87 /**
88  * Child side conduit, can only send/receive point-to-point messages via the
89  * one specific ConduitsChild actor.
90  */
91 export class PointConduit extends BaseConduit {
92   constructor(subject, address, actor) {
93     super(subject, address);
94     this.actor = actor;
95     this.actor.sendAsyncMessage("ConduitOpened", { arg: address });
96   }
98   /**
99    * Internal, sends messages via the actor, used by sendX stubs.
100    *
101    * @param {string} method
102    * @param {boolean} query
103    * @param {object?} arg
104    * @returns {Promise?}
105    */
106   _send(method, query, arg = {}) {
107     if (!this.actor) {
108       throw new Error(`send${method} on closed conduit ${this.id}`);
109     }
110     let sender = this.id;
111     return super._send(method, query, this.actor, { arg, query, sender });
112   }
114   /**
115    * Closes the conduit from further IPC, notifies the parent side by default.
116    *
117    * @param {boolean} silent
118    */
119   close(silent = false) {
120     let { actor } = this;
121     if (actor) {
122       this.actor = null;
123       actor.conduits.delete(this.id);
124       if (!silent) {
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.
128         try {
129           actor.sendAsyncMessage("ConduitClosed", { sender: this.id });
130         } catch (ex) {}
131       }
132     }
133     this.closeCallback?.();
134     this.closeCallback = null;
135   }
137   /**
138    * Set the callback to be called when the conduit is closed.
139    *
140    * @param {Function} callback
141    */
142   setCloseCallback(callback) {
143     this.closeCallback = callback;
144   }
148  * Implements the child side of the Conduits actor, manages conduit lifetimes.
149  */
150 export class ConduitsChild extends JSWindowActorChild {
151   constructor() {
152     super();
153     this.conduits = new Map();
154   }
156   /**
157    * Public entry point a child-side subject uses to open a conduit.
158    *
159    * @param {object} subject
160    * @param {ConduitAddress} address
161    * @returns {PointConduit}
162    */
163   openConduit(subject, address) {
164     let conduit = new PointConduit(subject, address, this);
165     this.conduits.set(conduit.id, conduit);
166     return conduit;
167   }
169   /**
170    * JSWindowActor method, routes the message to the target subject.
171    *
172    * @param {object} options
173    * @param {string} options.name
174    * @param {MessageData | MessageData[]} options.data
175    * @returns {Promise?}
176    */
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)));
182     }
184     let { target, arg, query, sender } = data;
185     let conduit = this.conduits.get(target);
186     if (!conduit) {
187       throw new Error(`${name} for closed conduit ${target}: ${uneval(arg)}`);
188     }
189     return conduit._recv(name, arg, { sender, query, actor: this });
190   }
192   /**
193    * JSWindowActor method, ensure cleanup.
194    */
195   didDestroy() {
196     for (let conduit of this.conduits.values()) {
197       conduit.close(true);
198     }
199     this.conduits.clear();
200   }
204  * Child side of the Conduits process actor.  Same code as JSWindowActor.
205  */
206 export class ProcessConduitsChild extends JSProcessActorChild {
207   constructor() {
208     super();
209     this.conduits = new Map();
210   }
212   openConduit = ConduitsChild.prototype.openConduit;
213   receiveMessage = ConduitsChild.prototype.receiveMessage;
214   willDestroy = ConduitsChild.prototype.willDestroy;
215   didDestroy = ConduitsChild.prototype.didDestroy;