Bug 1792034 [wpt PR 36019] - Make location.search always expect UTF-8, a=testonly
[gecko.git] / remote / shared / TabManager.sys.mjs
blob9123968a68671bfbd5c7b699902b7488c514e6e3
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   AppInfo: "chrome://remote/content/shared/AppInfo.sys.mjs",
9   EventPromise: "chrome://remote/content/shared/Sync.sys.mjs",
10   MobileTabBrowser: "chrome://remote/content/shared/MobileTabBrowser.sys.mjs",
11 });
13 // Maps browser's permanentKey to uuid: WeakMap.<Object, string>
14 const browserUniqueIds = new WeakMap();
16 export var TabManager = {
17   /**
18    * Retrieve all the browser elements from tabs as contained in open windows.
19    *
20    * @return {Array<xul:browser>}
21    *     All the found <xul:browser>s. Will return an empty array if
22    *     no windows and tabs can be found.
23    */
24   get browsers() {
25     const browsers = [];
27     for (const win of this.windows) {
28       const tabBrowser = this.getTabBrowser(win);
30       if (tabBrowser && tabBrowser.tabs) {
31         const contentBrowsers = tabBrowser.tabs.map(tab => {
32           return this.getBrowserForTab(tab);
33         });
34         browsers.push(...contentBrowsers);
35       }
36     }
38     return browsers;
39   },
41   get windows() {
42     return Services.wm.getEnumerator(null);
43   },
45   /**
46    * Array of unique browser ids (UUIDs) for all content browsers of all
47    * windows.
48    *
49    * TODO: Similarly to getBrowserById, we should improve the performance of
50    * this getter in Bug 1750065.
51    *
52    * @return {Array<String>}
53    *     Array of UUIDs for all content browsers.
54    */
55   get allBrowserUniqueIds() {
56     const browserIds = [];
58     for (const win of this.windows) {
59       const tabBrowser = this.getTabBrowser(win);
61       // Only return handles for browser windows
62       if (tabBrowser && tabBrowser.tabs) {
63         for (const tab of tabBrowser.tabs) {
64           const contentBrowser = this.getBrowserForTab(tab);
65           const winId = this.getIdForBrowser(contentBrowser);
66           if (winId !== null) {
67             browserIds.push(winId);
68           }
69         }
70       }
71     }
73     return browserIds;
74   },
76   /**
77    * Get the <code>&lt;xul:browser&gt;</code> for the specified tab.
78    *
79    * @param {Tab} tab
80    *     The tab whose browser needs to be returned.
81    *
82    * @return {xul:browser}
83    *     The linked browser for the tab or null if no browser can be found.
84    */
85   getBrowserForTab(tab) {
86     if (tab && "linkedBrowser" in tab) {
87       return tab.linkedBrowser;
88     }
90     return null;
91   },
93   /**
94    * Return the tab browser for the specified chrome window.
95    *
96    * @param {ChromeWindow} win
97    *     Window whose <code>tabbrowser</code> needs to be accessed.
98    *
99    * @return {Tab}
100    *     Tab browser or null if it's not a browser window.
101    */
102   getTabBrowser(win) {
103     if (lazy.AppInfo.isAndroid) {
104       return new lazy.MobileTabBrowser(win);
105     } else if (lazy.AppInfo.isFirefox) {
106       return win.gBrowser;
107     }
109     return null;
110   },
112   /**
113    * Create a new tab.
114    *
115    * @param {Object} options
116    * @param {Boolean=} options.focus
117    *     Set to true if the new tab should be focused (selected). Defaults to
118    *     false.
119    * @param {Number} options.userContextId
120    *     The user context (container) id.
121    * @param {window=} options.window
122    *     The window where the new tab will open. Defaults to Services.wm.getMostRecentWindow
123    *     if no window is provided.
124    */
125   async addTab(options = {}) {
126     const {
127       focus = false,
128       userContextId,
129       window = Services.wm.getMostRecentWindow(null),
130     } = options;
131     const tabBrowser = this.getTabBrowser(window);
133     const tab = await tabBrowser.addTab("about:blank", {
134       triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(),
135       userContextId,
136     });
138     if (focus) {
139       await this.selectTab(tab);
140     }
142     return tab;
143   },
145   /**
146    * Retrieve the browser element corresponding to the provided unique id,
147    * previously generated via getIdForBrowser.
148    *
149    * TODO: To avoid creating strong references on browser elements and
150    * potentially leaking those elements, this method loops over all windows and
151    * all tabs. It should be replaced by a faster implementation in Bug 1750065.
152    *
153    * @param {String} id
154    *     A browser unique id created by getIdForBrowser.
155    * @return {xul:browser}
156    *     The <xul:browser> corresponding to the provided id. Will return null if
157    *     no matching browser element is found.
158    */
159   getBrowserById(id) {
160     for (const win of this.windows) {
161       const tabBrowser = this.getTabBrowser(win);
162       if (tabBrowser && tabBrowser.tabs) {
163         for (let i = 0; i < tabBrowser.tabs.length; ++i) {
164           const contentBrowser = this.getBrowserForTab(tabBrowser.tabs[i]);
165           if (this.getIdForBrowser(contentBrowser) == id) {
166             return contentBrowser;
167           }
168         }
169       }
170     }
171     return null;
172   },
174   /**
175    * Retrieve the browsing context corresponding to the provided unique id.
176    *
177    * @param {String} id
178    *     A browsing context unique id (created by getIdForBrowsingContext).
179    * @return {BrowsingContext=}
180    *     The browsing context found for this id, null if none was found.
181    */
182   getBrowsingContextById(id) {
183     const browser = this.getBrowserById(id);
184     if (browser) {
185       return browser.browsingContext;
186     }
188     return BrowsingContext.get(id);
189   },
191   /**
192    * Retrieve the unique id for the given xul browser element. The id is a
193    * dynamically generated uuid associated with the permanentKey property of the
194    * given browser element. This method is preferable over getIdForBrowsingContext
195    * in case of working with browser element of a tab, since we can not guarantee
196    * that browsing context is attached to it.
197    *
198    * @param {xul:browser} browserElement
199    *     The <xul:browser> for which we want to retrieve the id.
200    * @return {String} The unique id for this browser.
201    */
202   getIdForBrowser(browserElement) {
203     if (browserElement === null) {
204       return null;
205     }
207     const key = browserElement.permanentKey;
208     if (!browserUniqueIds.has(key)) {
209       const uuid = Services.uuid.generateUUID().toString();
210       browserUniqueIds.set(key, uuid.substring(1, uuid.length - 1));
211     }
212     return browserUniqueIds.get(key);
213   },
215   /**
216    * Retrieve the id of a Browsing Context.
217    *
218    * For a top-level browsing context a custom unique id will be returned.
219    *
220    * @param {BrowsingContext=} browsingContext
221    *     The browsing context to get the id from.
222    *
223    * @returns {String}
224    *     The id of the browsing context.
225    */
226   getIdForBrowsingContext(browsingContext) {
227     if (!browsingContext) {
228       return null;
229     }
231     if (!browsingContext.parent) {
232       // Top-level browsing contexts have their own custom unique id.
233       return this.getIdForBrowser(browsingContext.embedderElement);
234     }
236     return browsingContext.id.toString();
237   },
239   getTabCount() {
240     let count = 0;
241     for (const win of this.windows) {
242       // For browser windows count the tabs. Otherwise take the window itself.
243       const tabbrowser = this.getTabBrowser(win);
244       if (tabbrowser?.tabs) {
245         count += tabbrowser.tabs.length;
246       } else {
247         count += 1;
248       }
249     }
250     return count;
251   },
253   /**
254    * Remove the given tab.
255    *
256    * @param {Tab} tab
257    *     Tab to remove.
258    */
259   async removeTab(tab) {
260     const ownerWindow = this._getWindowForTab(tab);
261     const tabBrowser = this.getTabBrowser(ownerWindow);
262     await tabBrowser.removeTab(tab);
263   },
265   /**
266    * Select the given tab.
267    *
268    * @param {Tab} tab
269    *     Tab to select.
270    *
271    * @returns {Promise}
272    *     Promise that resolves when the given tab has been selected.
273    */
274   selectTab(tab) {
275     const ownerWindow = this._getWindowForTab(tab);
276     const tabBrowser = this.getTabBrowser(ownerWindow);
278     if (tab === tabBrowser.selectedTab) {
279       return Promise.resolve();
280     }
282     const selected = new lazy.EventPromise(ownerWindow, "TabSelect");
283     tabBrowser.selectedTab = tab;
284     return selected;
285   },
287   supportsTabs() {
288     return lazy.AppInfo.isAndroid || lazy.AppInfo.isFirefox;
289   },
291   _getWindowForTab(tab) {
292     // `.linkedBrowser.ownerGlobal` works both with Firefox Desktop and Mobile.
293     // Other accessors (eg `.ownerGlobal` or `.browser.ownerGlobal`) fail on one
294     // of the platforms.
295     return tab.linkedBrowser.ownerGlobal;
296   },