diff options
Diffstat (limited to 'remote/shared/TabManager.sys.mjs')
-rw-r--r-- | remote/shared/TabManager.sys.mjs | 341 |
1 files changed, 341 insertions, 0 deletions
diff --git a/remote/shared/TabManager.sys.mjs b/remote/shared/TabManager.sys.mjs new file mode 100644 index 0000000000..bd0f7ae910 --- /dev/null +++ b/remote/shared/TabManager.sys.mjs @@ -0,0 +1,341 @@ +/* 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.<Object, string> +const browserUniqueIds = new WeakMap(); + +export var TabManager = { + /** + * Retrieve all the browser elements from tabs as contained in open windows. + * + * @returns {Array<XULBrowser>} + * All the found <xul:browser>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<string>} + * 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 <code><xul:browser></code> 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 <code>tabbrowser</code> 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 <xul:browser> 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 <xul:browser> 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; + }, +}; |