Bug 1886451: Add missing ifdef Nightly guards. r=dminor
[gecko.git] / remote / shared / listeners / BrowsingContextListener.sys.mjs
blobd4e3539ca92967a4bf10ab201567570fd6c9052e
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/. */
5 const lazy = {};
7 ChromeUtils.defineESModuleGetters(lazy, {
8   EventEmitter: "resource://gre/modules/EventEmitter.sys.mjs",
9 });
11 const OBSERVER_TOPIC_ATTACHED = "browsing-context-attached";
12 const OBSERVER_TOPIC_DISCARDED = "browsing-context-discarded";
14 const OBSERVER_TOPIC_SET_EMBEDDER = "browsing-context-did-set-embedder";
16 /**
17  * The BrowsingContextListener can be used to listen for notifications coming
18  * from browsing contexts that get attached or discarded.
19  *
20  * Example:
21  * ```
22  * const listener = new BrowsingContextListener();
23  * listener.on("attached", onAttached);
24  * listener.startListening();
25  *
26  * const onAttached = (eventName, data = {}) => {
27  *   const { browsingContext, why } = data;
28  *   ...
29  * };
30  * ```
31  *
32  * @fires message
33  *    The BrowsingContextListener emits "attached" and "discarded" events,
34  *    with the following object as payload:
35  *      - {BrowsingContext} browsingContext
36  *            Browsing context the notification relates to.
37  *      - {string} why
38  *            Usually "attach" or "discard", but will contain "replace" if the
39  *            browsing context gets replaced by a cross-group navigation.
40  */
41 export class BrowsingContextListener {
42   #listening;
43   #topContextsToAttach;
45   /**
46    * Create a new BrowsingContextListener instance.
47    */
48   constructor() {
49     lazy.EventEmitter.decorate(this);
51     // A map that temporarily holds attached top-level browsing contexts until
52     // their embedder element is set, which is required to successfully
53     // retrieve a unique id for the content browser by the TabManager.
54     this.#topContextsToAttach = new Map();
56     this.#listening = false;
57   }
59   destroy() {
60     this.stopListening();
61     this.#topContextsToAttach = null;
62   }
64   observe(subject, topic, data) {
65     switch (topic) {
66       case OBSERVER_TOPIC_ATTACHED:
67         // Delay emitting the event for top-level browsing contexts until
68         // the embedder element has been set.
69         if (!subject.parent) {
70           this.#topContextsToAttach.set(subject, data);
71           return;
72         }
74         this.emit("attached", { browsingContext: subject, why: data });
75         break;
77       case OBSERVER_TOPIC_DISCARDED:
78         // Remove a recently attached top-level browsing context if it's
79         // immediately discarded.
80         if (this.#topContextsToAttach.has(subject)) {
81           this.#topContextsToAttach.delete(subject);
82         }
84         this.emit("discarded", { browsingContext: subject, why: data });
85         break;
87       case OBSERVER_TOPIC_SET_EMBEDDER:
88         const why = this.#topContextsToAttach.get(subject);
89         if (why !== undefined) {
90           this.emit("attached", { browsingContext: subject, why });
91           this.#topContextsToAttach.delete(subject);
92         }
93         break;
94     }
95   }
97   startListening() {
98     if (this.#listening) {
99       return;
100     }
102     Services.obs.addObserver(this, OBSERVER_TOPIC_ATTACHED);
103     Services.obs.addObserver(this, OBSERVER_TOPIC_DISCARDED);
104     Services.obs.addObserver(this, OBSERVER_TOPIC_SET_EMBEDDER);
106     this.#listening = true;
107   }
109   stopListening() {
110     if (!this.#listening) {
111       return;
112     }
114     Services.obs.removeObserver(this, OBSERVER_TOPIC_ATTACHED);
115     Services.obs.removeObserver(this, OBSERVER_TOPIC_DISCARDED);
116     Services.obs.removeObserver(this, OBSERVER_TOPIC_SET_EMBEDDER);
118     this.#topContextsToAttach.clear();
120     this.#listening = false;
121   }