1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
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"]);
}
}
|