summaryrefslogtreecommitdiffstats
path: root/remote/cdp/observers/TargetObserver.sys.mjs
blob: dfd9e2d9dc7711a002c226497351cf9c4db0fd73 (plain)
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"]);
  }
}