forked from mirrors/gecko-dev
		
	
		
			
				
	
	
		
			227 lines
		
	
	
	
		
			6.2 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			227 lines
		
	
	
	
		
			6.2 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/. */
 | |
| 
 | |
| /**
 | |
|  * This @file implements the child side of Conduits, an abstraction over
 | |
|  * Fission IPC for extension API subject.  See {@link ConduitsParent.sys.mjs}
 | |
|  * for more details about the overall design.
 | |
|  *
 | |
|  * @typedef {object} MessageData
 | |
|  * @property {ConduitID} [target]
 | |
|  * @property {ConduitID} [sender]
 | |
|  * @property {boolean} query
 | |
|  * @property {object} arg
 | |
|  *
 | |
|  * @typedef {import("ConduitsParent.sys.mjs").ConduitAddress} ConduitAddress
 | |
|  * @typedef {import("ConduitsParent.sys.mjs").ConduitID} ConduitID
 | |
|  */
 | |
| 
 | |
| /**
 | |
|  * Base class for both child (Point) and parent (Broadcast) side of conduits,
 | |
|  * handles setting up send/receive method stubs.
 | |
|  */
 | |
| export 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, uses the actor to send the message/query.
 | |
|    *
 | |
|    * @param {string} method
 | |
|    * @param {boolean} query Flag indicating a response is expected.
 | |
|    * @param {JSWindowActorChild|JSWindowActorParent} actor
 | |
|    * @param {MessageData} data
 | |
|    * @returns {Promise?}
 | |
|    */
 | |
|   _doSend(method, query, actor, data) {
 | |
|     if (query) {
 | |
|       return actor.sendQuery(method, data);
 | |
|     }
 | |
|     actor.sendAsyncMessage(method, data);
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Internal @abstract, used by sendX stubs.
 | |
|    *
 | |
|    * @param {string} _name
 | |
|    * @param {boolean} _query
 | |
|    */
 | |
|   _send(_name, _query, ..._args) {
 | |
|     throw new Error(`_send not implemented for ${this.constructor.name}`);
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * 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.
 | |
|  */
 | |
| export class PointConduit extends BaseConduit {
 | |
|   /** @type {ConduitGen} */
 | |
|   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
 | |
|    * @param {object?} arg
 | |
|    * @returns {Promise?}
 | |
|    */
 | |
|   _send(method, query, arg = {}) {
 | |
|     if (!this.actor) {
 | |
|       throw new Error(`send${method} on closed conduit ${this.id}`);
 | |
|     }
 | |
|     let sender = this.id;
 | |
|     return super._doSend(method, query, this.actor, { arg, query, sender });
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Closes the conduit from further IPC, notifies the parent side by default.
 | |
|    *
 | |
|    * @param {boolean} silent
 | |
|    */
 | |
|   close(silent = false) {
 | |
|     let { actor } = this;
 | |
|     if (actor) {
 | |
|       this.actor = null;
 | |
|       actor.conduits.delete(this.id);
 | |
|       if (!silent) {
 | |
|         // 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) {}
 | |
|       }
 | |
|     }
 | |
|     this.closeCallback?.();
 | |
|     this.closeCallback = null;
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Set the callback to be called when the conduit is closed.
 | |
|    *
 | |
|    * @param {Function} callback
 | |
|    */
 | |
|   setCloseCallback(callback) {
 | |
|     this.closeCallback = callback;
 | |
|   }
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Implements the child side of the Conduits actor, manages conduit lifetimes.
 | |
|  */
 | |
| export class ConduitsChild extends JSWindowActorChild {
 | |
|   constructor() {
 | |
|     super();
 | |
|     this.conduits = new Map();
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Public entry point a child-side subject uses to open a conduit.
 | |
|    *
 | |
|    * @type {ConduitGen}
 | |
|    */
 | |
|   openConduit(subject, address) {
 | |
|     let conduit = new PointConduit(subject, address, this);
 | |
|     this.conduits.set(conduit.id, conduit);
 | |
|     return conduit;
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * JSWindowActor method, routes the message to the target subject.
 | |
|    *
 | |
|    * @param {object} options
 | |
|    * @param {string} options.name
 | |
|    * @param {MessageData | MessageData[]} options.data
 | |
|    * @returns {Promise?}
 | |
|    */
 | |
|   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;
 | |
|     let conduit = this.conduits.get(target);
 | |
|     if (!conduit) {
 | |
|       throw new Error(`${name} for closed conduit ${target}: ${uneval(arg)}`);
 | |
|     }
 | |
|     return conduit._recv(name, arg, { sender, query, actor: this });
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * JSWindowActor method, ensure cleanup.
 | |
|    */
 | |
|   didDestroy() {
 | |
|     for (let conduit of this.conduits.values()) {
 | |
|       conduit.close(true);
 | |
|     }
 | |
|     this.conduits.clear();
 | |
|   }
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Child side of the Conduits process actor.  Same code as JSWindowActor.
 | |
|  */
 | |
| export class ProcessConduitsChild extends JSProcessActorChild {
 | |
|   constructor() {
 | |
|     super();
 | |
|     this.conduits = new Map();
 | |
|   }
 | |
| 
 | |
|   openConduit = ConduitsChild.prototype.openConduit;
 | |
|   receiveMessage = ConduitsChild.prototype.receiveMessage;
 | |
|   didDestroy = ConduitsChild.prototype.didDestroy;
 | |
| }
 | 
