/* 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", EventPromise: "chrome://remote/content/shared/Sync.sys.mjs", }); // TODO(ato): // // The DOM team is working on pulling browsing context related behaviour, // such as window and tab handling, out of product code and into the platform. // This will have implication for the remote agent, // and as the platform gains support for product-independent events // we can likely get rid of this entire module. /** * Observe Firefox tabs as they open and close. * * "open" fires when a tab opens. * "close" fires when a tab closes. */ export class TabObserver { /** * @param {boolean?} [false] registerExisting * Events will be fired for ChromeWIndows and their respective tabs * at the time when the observer is started. */ constructor({ registerExisting = false } = {}) { lazy.EventEmitter.decorate(this); this.registerExisting = registerExisting; this.onTabOpen = this.onTabOpen.bind(this); this.onTabClose = this.onTabClose.bind(this); } async start() { Services.wm.addListener(this); if (this.registerExisting) { // Start listening for events on already open windows for (const win of Services.wm.getEnumerator("navigator:browser")) { this._registerDOMWindow(win); } } } stop() { Services.wm.removeListener(this); // Stop listening for events on still opened windows for (const win of Services.wm.getEnumerator("navigator:browser")) { this._unregisterDOMWindow(win); } } // Event emitters onTabOpen({ target }) { this.emit("open", target); } onTabClose({ target }) { this.emit("close", target); } // Internal methods _registerDOMWindow(win) { for (const tab of win.gBrowser.tabs) { // a missing linkedBrowser means the tab is still initialising, // and a TabOpen event will fire once it is ready if (!tab.linkedBrowser) { continue; } this.onTabOpen({ target: tab }); } win.gBrowser.tabContainer.addEventListener("TabOpen", this.onTabOpen); win.gBrowser.tabContainer.addEventListener("TabClose", this.onTabClose); } _unregisterDOMWindow(win) { for (const tab of win.gBrowser.tabs) { // a missing linkedBrowser means the tab is still initialising if (!tab.linkedBrowser) { continue; } // Emulate custom "TabClose" events because that event is not // fired for each of the tabs when the window closes. this.onTabClose({ target: tab }); } win.gBrowser.tabContainer.removeEventListener("TabOpen", this.onTabOpen); win.gBrowser.tabContainer.removeEventListener("TabClose", this.onTabClose); } // nsIWindowMediatorListener async onOpenWindow(xulWindow) { const win = xulWindow.docShell.domWindow; await new lazy.EventPromise(win, "load"); // Return early if it's not a browser window if ( win.document.documentElement.getAttribute("windowtype") != "navigator:browser" ) { return; } this._registerDOMWindow(win); } onCloseWindow(xulWindow) { const win = xulWindow.docShell.domWindow; // Return early if it's not a browser window if ( win.document.documentElement.getAttribute("windowtype") != "navigator:browser" ) { return; } this._unregisterDOMWindow(win); } // XPCOM get QueryInterface() { return ChromeUtils.generateQI(["nsIWindowMediatorListener"]); } }