/* 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, { AppInfo: "chrome://remote/content/shared/AppInfo.sys.mjs", EventPromise: "chrome://remote/content/shared/Sync.sys.mjs", generateUUID: "chrome://remote/content/shared/UUID.sys.mjs", MobileTabBrowser: "chrome://remote/content/shared/MobileTabBrowser.sys.mjs", }); // Maps browser's permanentKey to uuid: WeakMap. const browserUniqueIds = new WeakMap(); export var TabManager = { /** * Retrieve all the browser elements from tabs as contained in open windows. * * @returns {Array} * All the found s. Will return an empty array if * no windows and tabs can be found. */ get browsers() { const browsers = []; for (const win of this.windows) { const tabBrowser = this.getTabBrowser(win); if (tabBrowser && tabBrowser.tabs) { const contentBrowsers = tabBrowser.tabs.map(tab => { return this.getBrowserForTab(tab); }); browsers.push(...contentBrowsers); } } return browsers; }, get windows() { return Services.wm.getEnumerator(null); }, /** * Array of unique browser ids (UUIDs) for all content browsers of all * windows. * * TODO: Similarly to getBrowserById, we should improve the performance of * this getter in Bug 1750065. * * @returns {Array} * Array of UUIDs for all content browsers. */ get allBrowserUniqueIds() { const browserIds = []; for (const win of this.windows) { const tabBrowser = this.getTabBrowser(win); // Only return handles for browser windows if (tabBrowser && tabBrowser.tabs) { for (const tab of tabBrowser.tabs) { const contentBrowser = this.getBrowserForTab(tab); const winId = this.getIdForBrowser(contentBrowser); if (winId !== null) { browserIds.push(winId); } } } } return browserIds; }, /** * Get the <xul:browser> for the specified tab. * * @param {Tab} tab * The tab whose browser needs to be returned. * * @returns {XULBrowser} * The linked browser for the tab or null if no browser can be found. */ getBrowserForTab(tab) { if (tab && "linkedBrowser" in tab) { return tab.linkedBrowser; } return null; }, /** * Return the tab browser for the specified chrome window. * * @param {ChromeWindow} win * Window whose tabbrowser needs to be accessed. * * @returns {Tab} * Tab browser or null if it's not a browser window. */ getTabBrowser(win) { if (lazy.AppInfo.isAndroid) { return new lazy.MobileTabBrowser(win); } else if (lazy.AppInfo.isFirefox) { return win.gBrowser; } return null; }, /** * Create a new tab. * * @param {object} options * @param {boolean=} options.focus * Set to true if the new tab should be focused (selected). Defaults to * false. * @param {Tab=} options.referenceTab * The reference tab after which the new tab will be added. If no * reference tab is provided, the new tab will be added after all the * other tabs. * @param {number} options.userContextId * The user context (container) id. * @param {window=} options.window * The window where the new tab will open. Defaults to Services.wm.getMostRecentWindow * if no window is provided. Will be ignored if referenceTab is provided. */ async addTab(options = {}) { let { focus = false, referenceTab = null, userContextId, window = Services.wm.getMostRecentWindow(null), } = options; let index; if (referenceTab != null) { // If a reference tab was specified, the window should be the window // owning the reference tab. window = this._getWindowForTab(referenceTab); } const tabBrowser = this.getTabBrowser(window); if (referenceTab != null) { index = tabBrowser.tabs.indexOf(referenceTab) + 1; } const tab = await tabBrowser.addTab("about:blank", { index, triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(), userContextId, }); if (focus) { await this.selectTab(tab); } return tab; }, /** * Retrieve the browser element corresponding to the provided unique id, * previously generated via getIdForBrowser. * * TODO: To avoid creating strong references on browser elements and * potentially leaking those elements, this method loops over all windows and * all tabs. It should be replaced by a faster implementation in Bug 1750065. * * @param {string} id * A browser unique id created by getIdForBrowser. * @returns {XULBrowser} * The corresponding to the provided id. Will return null if * no matching browser element is found. */ getBrowserById(id) { for (const win of this.windows) { const tabBrowser = this.getTabBrowser(win); if (tabBrowser && tabBrowser.tabs) { for (let i = 0; i < tabBrowser.tabs.length; ++i) { const contentBrowser = this.getBrowserForTab(tabBrowser.tabs[i]); if (this.getIdForBrowser(contentBrowser) == id) { return contentBrowser; } } } } return null; }, /** * Retrieve the browsing context corresponding to the provided unique id. * * @param {string} id * A browsing context unique id (created by getIdForBrowsingContext). * @returns {BrowsingContext=} * The browsing context found for this id, null if none was found. */ getBrowsingContextById(id) { const browser = this.getBrowserById(id); if (browser) { return browser.browsingContext; } return BrowsingContext.get(id); }, /** * Retrieve the unique id for the given xul browser element. The id is a * dynamically generated uuid associated with the permanentKey property of the * given browser element. This method is preferable over getIdForBrowsingContext * in case of working with browser element of a tab, since we can not guarantee * that browsing context is attached to it. * * @param {XULBrowser} browserElement * The for which we want to retrieve the id. * @returns {string} The unique id for this browser. */ getIdForBrowser(browserElement) { if (browserElement === null) { return null; } const key = browserElement.permanentKey; if (!browserUniqueIds.has(key)) { browserUniqueIds.set(key, lazy.generateUUID()); } return browserUniqueIds.get(key); }, /** * Retrieve the id of a Browsing Context. * * For a top-level browsing context a custom unique id will be returned. * * @param {BrowsingContext=} browsingContext * The browsing context to get the id from. * * @returns {string} * The id of the browsing context. */ getIdForBrowsingContext(browsingContext) { if (!browsingContext) { return null; } if (!browsingContext.parent) { // Top-level browsing contexts have their own custom unique id. return this.getIdForBrowser(browsingContext.embedderElement); } return browsingContext.id.toString(); }, getTabCount() { let count = 0; for (const win of this.windows) { // For browser windows count the tabs. Otherwise take the window itself. const tabbrowser = this.getTabBrowser(win); if (tabbrowser?.tabs) { count += tabbrowser.tabs.length; } else { count += 1; } } return count; }, /** * Retrieve the tab owning a Browsing Context. * * @param {BrowsingContext=} browsingContext * The browsing context to get the tab from. * * @returns {Tab|null} * The tab owning the Browsing Context. */ getTabForBrowsingContext(browsingContext) { const browser = browsingContext?.top.embedderElement; if (!browser) { return null; } const tabBrowser = this.getTabBrowser(browser.ownerGlobal); return tabBrowser.getTabForBrowser(browser); }, /** * Remove the given tab. * * @param {Tab} tab * Tab to remove. */ async removeTab(tab) { if (!tab) { return; } const ownerWindow = this._getWindowForTab(tab); const tabBrowser = this.getTabBrowser(ownerWindow); await tabBrowser.removeTab(tab); }, /** * Select the given tab. * * @param {Tab} tab * Tab to select. * * @returns {Promise} * Promise that resolves when the given tab has been selected. */ selectTab(tab) { if (!tab) { return Promise.resolve(); } const ownerWindow = this._getWindowForTab(tab); const tabBrowser = this.getTabBrowser(ownerWindow); if (tab === tabBrowser.selectedTab) { return Promise.resolve(); } const selected = new lazy.EventPromise(ownerWindow, "TabSelect"); tabBrowser.selectedTab = tab; return selected; }, supportsTabs() { return lazy.AppInfo.isAndroid || lazy.AppInfo.isFirefox; }, _getWindowForTab(tab) { // `.linkedBrowser.ownerGlobal` works both with Firefox Desktop and Mobile. // Other accessors (eg `.ownerGlobal` or `.browser.ownerGlobal`) fail on one // of the platforms. return tab.linkedBrowser.ownerGlobal; }, };