/* 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/. */ const lazy = {}; ChromeUtils.defineESModuleGetters(lazy, { EventEmitter: "resource://gre/modules/EventEmitter.sys.mjs", MainProcessTarget: "chrome://remote/content/cdp/targets/MainProcessTarget.sys.mjs", TabManager: "chrome://remote/content/shared/TabManager.sys.mjs", TabObserver: "chrome://remote/content/cdp/observers/TargetObserver.sys.mjs", TabTarget: "chrome://remote/content/cdp/targets/TabTarget.sys.mjs", }); export class TargetList { constructor() { // Target ID -> Target this._targets = new Map(); lazy.EventEmitter.decorate(this); } /** * Start listing and listening for all the debuggable targets */ async watchForTargets() { await this.watchForTabs(); } unwatchForTargets() { this.unwatchForTabs(); } /** * Watch for all existing and new tabs being opened. * So that we can create the related TabTarget instance for * each of them. */ async watchForTabs() { if (this.tabObserver) { throw new Error("Targets is already watching for new tabs"); } this.tabObserver = new lazy.TabObserver({ registerExisting: true }); // Handle creation of tab targets for opened tabs. this.tabObserver.on("open", async (eventName, tab) => { const target = new lazy.TabTarget(this, tab.linkedBrowser); this.registerTarget(target); }); // Handle removal of tab targets when tabs are closed. this.tabObserver.on("close", (eventName, tab) => { const browser = tab.linkedBrowser; // Ignore unloaded tabs. if (browser.browserId === 0) { return; } const id = lazy.TabManager.getIdForBrowser(browser); const target = Array.from(this._targets.values()).find( target => target.id == id ); if (target) { this.destroyTarget(target); } }); await this.tabObserver.start(); } unwatchForTabs() { if (this.tabObserver) { this.tabObserver.stop(); this.tabObserver = null; } } /** * To be called right after instantiating a new Target instance. * This will hold the new instance in the list and notify about * its creation. */ registerTarget(target) { this._targets.set(target.id, target); this.emit("target-created", target); } /** * To be called when the debuggable target has been destroy. * So that we can notify it no longer exists and disconnect * all connecting made to debug it. */ destroyTarget(target) { target.destructor(); this._targets.delete(target.id); this.emit("target-destroyed", target); } /** * Destroy all the registered target of all kinds. * This will end up dropping all connections made to debug any of them. */ destructor() { for (const target of this) { this.destroyTarget(target); } this._targets.clear(); if (this.mainProcessTarget) { this.mainProcessTarget = null; } this.unwatchForTargets(); } get size() { return this._targets.size; } /** * Get Target instance by target id * * @param {string} id * Target id * * @returns {Target} */ getById(id) { return this._targets.get(id); } /** * Get the Target instance for the main process. * This target is a singleton and only exposes a subset of domains. */ getMainProcessTarget() { if (!this.mainProcessTarget) { this.mainProcessTarget = new lazy.MainProcessTarget(this); this.registerTarget(this.mainProcessTarget); } return this.mainProcessTarget; } *[Symbol.iterator]() { for (const target of this._targets.values()) { yield target; } } toJSON() { return [...this]; } toString() { return `[object TargetList ${this.size}]`; } }