diff options
Diffstat (limited to 'remote/cdp/observers/TargetObserver.sys.mjs')
-rw-r--r-- | remote/cdp/observers/TargetObserver.sys.mjs | 142 |
1 files changed, 142 insertions, 0 deletions
diff --git a/remote/cdp/observers/TargetObserver.sys.mjs b/remote/cdp/observers/TargetObserver.sys.mjs new file mode 100644 index 0000000000..dfd9e2d9dc --- /dev/null +++ b/remote/cdp/observers/TargetObserver.sys.mjs @@ -0,0 +1,142 @@ +/* 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"]); + } +} |