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/. */
7 ChromeUtils.defineESModuleGetters(lazy, {
8 EventEmitter: "resource://gre/modules/EventEmitter.sys.mjs",
10 EventPromise: "chrome://remote/content/shared/Sync.sys.mjs",
15 // The DOM team is working on pulling browsing context related behaviour,
16 // such as window and tab handling, out of product code and into the platform.
17 // This will have implication for the remote agent,
18 // and as the platform gains support for product-independent events
19 // we can likely get rid of this entire module.
22 * Observe Firefox tabs as they open and close.
24 * "open" fires when a tab opens.
25 * "close" fires when a tab closes.
27 export class TabObserver {
29 * @param {boolean?} [false] registerExisting
30 * Events will be fired for ChromeWIndows and their respective tabs
31 * at the time when the observer is started.
33 constructor({ registerExisting = false } = {}) {
34 lazy.EventEmitter.decorate(this);
36 this.registerExisting = registerExisting;
38 this.onTabOpen = this.onTabOpen.bind(this);
39 this.onTabClose = this.onTabClose.bind(this);
43 Services.wm.addListener(this);
45 if (this.registerExisting) {
46 // Start listening for events on already open windows
47 for (const win of Services.wm.getEnumerator("navigator:browser")) {
48 this._registerDOMWindow(win);
54 Services.wm.removeListener(this);
56 // Stop listening for events on still opened windows
57 for (const win of Services.wm.getEnumerator("navigator:browser")) {
58 this._unregisterDOMWindow(win);
64 onTabOpen({ target }) {
65 this.emit("open", target);
68 onTabClose({ target }) {
69 this.emit("close", target);
74 _registerDOMWindow(win) {
75 for (const tab of win.gBrowser.tabs) {
76 // a missing linkedBrowser means the tab is still initialising,
77 // and a TabOpen event will fire once it is ready
78 if (!tab.linkedBrowser) {
82 this.onTabOpen({ target: tab });
85 win.gBrowser.tabContainer.addEventListener("TabOpen", this.onTabOpen);
86 win.gBrowser.tabContainer.addEventListener("TabClose", this.onTabClose);
89 _unregisterDOMWindow(win) {
90 for (const tab of win.gBrowser.tabs) {
91 // a missing linkedBrowser means the tab is still initialising
92 if (!tab.linkedBrowser) {
96 // Emulate custom "TabClose" events because that event is not
97 // fired for each of the tabs when the window closes.
98 this.onTabClose({ target: tab });
101 win.gBrowser.tabContainer.removeEventListener("TabOpen", this.onTabOpen);
102 win.gBrowser.tabContainer.removeEventListener("TabClose", this.onTabClose);
105 // nsIWindowMediatorListener
107 async onOpenWindow(xulWindow) {
108 const win = xulWindow.docShell.domWindow;
110 await new lazy.EventPromise(win, "load");
112 // Return early if it's not a browser window
114 win.document.documentElement.getAttribute("windowtype") !=
120 this._registerDOMWindow(win);
123 onCloseWindow(xulWindow) {
124 const win = xulWindow.docShell.domWindow;
126 // Return early if it's not a browser window
128 win.document.documentElement.getAttribute("windowtype") !=
134 this._unregisterDOMWindow(win);
139 get QueryInterface() {
140 return ChromeUtils.generateQI(["nsIWindowMediatorListener"]);