tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

commit 691fb29014ebe1d56445188790e9a251d9bb3900
parent c7d5cc8e8660b695e913dda77856c0a244cf9da3
Author: Henrik Skupin <mail@hskupin.info>
Date:   Wed, 15 Oct 2025 10:13:55 +0000

Bug 1989563 - [remote] Introduce a NavigableManager singleton. r=webdriver-reviewers,jdescottes,webcompat-reviewers,twisniewski

Differential Revision: https://phabricator.services.mozilla.com/D266244

Diffstat:
Mremote/jar.mn | 1+
Mremote/marionette/browser.sys.mjs | 8++++++++
Mremote/marionette/driver.sys.mjs | 15+++++++++++----
Mremote/marionette/json.sys.mjs | 7++++---
Aremote/shared/NavigableManager.sys.mjs | 264+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mremote/shared/NavigationManager.sys.mjs | 33++++++++++++++++++---------------
Mremote/shared/NetworkRequest.sys.mjs | 10+++++-----
Mremote/shared/TabManager.sys.mjs | 301+++++++++++++------------------------------------------------------------------
Mremote/shared/UserContextManager.sys.mjs | 14++++----------
Mremote/shared/WindowManager.sys.mjs | 38++++++++++++++++++++------------------
Mremote/shared/listeners/test/browser/browser_NetworkListener.js | 14+++++++-------
Mremote/shared/test/browser/browser.toml | 2++
Aremote/shared/test/browser/browser_NavigableManager.js | 442+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mremote/shared/test/browser/browser_NavigationManager.js | 18+++++++++---------
Mremote/shared/test/browser/browser_NavigationManager_failed_navigation.js | 8++++----
Mremote/shared/test/browser/browser_NavigationManager_notify.js | 10+++++-----
Mremote/shared/test/browser/browser_TabManager.js | 103++-----------------------------------------------------------------------------
Mremote/shared/webdriver/Session.sys.mjs | 19++++++++++++++++---
Mremote/webdriver-bidi/modules/Intercept.sys.mjs | 5+++--
Mremote/webdriver-bidi/modules/root/browser.sys.mjs | 2+-
Mremote/webdriver-bidi/modules/root/browsingContext.sys.mjs | 21++++++++++++---------
Mremote/webdriver-bidi/modules/root/emulation.sys.mjs | 5+++--
Mremote/webdriver-bidi/modules/root/input.sys.mjs | 8++++----
Mremote/webdriver-bidi/modules/root/network.sys.mjs | 22++++++++++++----------
Mremote/webdriver-bidi/modules/root/script.sys.mjs | 12++++++++----
Mremote/webdriver-bidi/modules/root/session.sys.mjs | 26+++++++++++++++-----------
Mremote/webdriver-bidi/modules/root/storage.sys.mjs | 4++--
Mremote/webdriver-bidi/modules/windowglobal-in-root/browsingContext.sys.mjs | 3++-
Mremote/webdriver-bidi/modules/windowglobal-in-root/log.sys.mjs | 3++-
Mremote/webdriver-bidi/modules/windowglobal-in-root/network.sys.mjs | 4+++-
Mremote/webdriver-bidi/modules/windowglobal-in-root/script.sys.mjs | 3++-
Mtesting/marionette/harness/marionette_harness/tests/unit/test_window_handles.py | 6+++++-
Mtesting/web-platform/mozilla/tests/webdriver/bidi/browsing_context/create/reference_context.py | 11+++++++----
Mtesting/webcompat/client.py | 5+++--
34 files changed, 953 insertions(+), 494 deletions(-)

diff --git a/remote/jar.mn b/remote/jar.mn @@ -23,6 +23,7 @@ remote.jar: content/shared/Format.sys.mjs (shared/Format.sys.mjs) content/shared/Log.sys.mjs (shared/Log.sys.mjs) content/shared/MobileTabBrowser.sys.mjs (shared/MobileTabBrowser.sys.mjs) + content/shared/NavigableManager.sys.mjs (shared/NavigableManager.sys.mjs) content/shared/Navigate.sys.mjs (shared/Navigate.sys.mjs) content/shared/NavigationManager.sys.mjs (shared/NavigationManager.sys.mjs) content/shared/NetworkCacheManager.sys.mjs (shared/NetworkCacheManager.sys.mjs) diff --git a/remote/marionette/browser.sys.mjs b/remote/marionette/browser.sys.mjs @@ -10,6 +10,7 @@ ChromeUtils.defineESModuleGetters(lazy, { EventPromise: "chrome://remote/content/shared/Sync.sys.mjs", MessageManagerDestroyedPromise: "chrome://remote/content/marionette/sync.sys.mjs", + NavigableManager: "chrome://remote/content/shared/NavigableManager.sys.mjs", TabManager: "chrome://remote/content/shared/TabManager.sys.mjs", windowManager: "chrome://remote/content/shared/WindowManager.sys.mjs", }); @@ -107,6 +108,13 @@ browser.Context = class { return null; } + /** + * Return the unique id of the content browser. + */ + get contentBrowserId() { + return lazy.NavigableManager.getIdForBrowser(this.contentBrowser); + } + get messageManager() { if (this.contentBrowser) { return this.contentBrowser.messageManager; diff --git a/remote/marionette/driver.sys.mjs b/remote/marionette/driver.sys.mjs @@ -27,6 +27,7 @@ ChromeUtils.defineESModuleGetters(lazy, { Marionette: "chrome://remote/content/components/Marionette.sys.mjs", MarionettePrefs: "chrome://remote/content/marionette/prefs.sys.mjs", modal: "chrome://remote/content/shared/Prompt.sys.mjs", + NavigableManager: "chrome://remote/content/shared/NavigableManager.sys.mjs", navigate: "chrome://remote/content/marionette/navigate.sys.mjs", permissions: "chrome://remote/content/shared/Permissions.sys.mjs", pprint: "chrome://remote/content/shared/Format.sys.mjs", @@ -1357,7 +1358,8 @@ GeckoDriver.prototype.getWindowHandle = function () { if (this.context == lazy.Context.Chrome) { return lazy.windowManager.getIdForWindow(this.curBrowser.window); } - return lazy.TabManager.getIdForBrowser(this.curBrowser.contentBrowser); + + return this.curBrowser.contentBrowserId; }; /** @@ -1378,7 +1380,10 @@ GeckoDriver.prototype.getWindowHandles = function () { if (this.context == lazy.Context.Chrome) { return lazy.windowManager.chromeWindowHandles.map(String); } - return lazy.TabManager.allBrowserUniqueIds.map(String); + + return lazy.TabManager.getBrowsers({ unloaded: true }).map(browser => + lazy.NavigableManager.getIdForBrowser(browser) + ); }; /** @@ -2706,7 +2711,7 @@ GeckoDriver.prototype.newWindow = async function (cmd) { } ); - const id = lazy.TabManager.getIdForBrowser(contentBrowser); + const id = lazy.NavigableManager.getIdForBrowser(contentBrowser); return { handle: id.toString(), type }; }; @@ -2747,7 +2752,9 @@ GeckoDriver.prototype.close = async function () { await this.curBrowser.closeTab(); this.currentSession.contentBrowsingContext = null; - return lazy.TabManager.allBrowserUniqueIds.map(String); + return lazy.TabManager.getBrowsers({ unloaded: true }).map(browser => + lazy.NavigableManager.getIdForBrowser(browser) + ); }; /** diff --git a/remote/marionette/json.sys.mjs b/remote/marionette/json.sys.mjs @@ -10,9 +10,9 @@ ChromeUtils.defineESModuleGetters(lazy, { dom: "chrome://remote/content/shared/DOM.sys.mjs", error: "chrome://remote/content/shared/webdriver/Errors.sys.mjs", Log: "chrome://remote/content/shared/Log.sys.mjs", + NavigableManager: "chrome://remote/content/shared/NavigableManager.sys.mjs", pprint: "chrome://remote/content/shared/Format.sys.mjs", ShadowRoot: "chrome://remote/content/marionette/web-reference.sys.mjs", - TabManager: "chrome://remote/content/shared/TabManager.sys.mjs", WebElement: "chrome://remote/content/marionette/web-reference.sys.mjs", WebFrame: "chrome://remote/content/marionette/web-reference.sys.mjs", WebReference: "chrome://remote/content/marionette/web-reference.sys.mjs", @@ -312,7 +312,7 @@ json.mapFromNavigableIds = function (serializedData) { const webRef = lazy.WebReference.fromJSON(data); if (webRef instanceof lazy.WebWindow) { - const browser = lazy.TabManager.getBrowserById(webRef.uuid); + const browser = lazy.NavigableManager.getBrowserById(webRef.uuid); if (browser) { webRef.uuid = browser?.browserId.toString(); data = webRef.toJSON(); @@ -348,7 +348,8 @@ json.mapToNavigableIds = function (serializedData) { webRef.uuid ); - webRef.uuid = lazy.TabManager.getIdForBrowsingContext(browsingContext); + webRef.uuid = + lazy.NavigableManager.getIdForBrowsingContext(browsingContext); data = webRef.toJSON(); } } else if (typeof data == "object") { diff --git a/remote/shared/NavigableManager.sys.mjs b/remote/shared/NavigableManager.sys.mjs @@ -0,0 +1,264 @@ +/* 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, { + BrowsingContextListener: + "chrome://remote/content/shared/listeners/BrowsingContextListener.sys.mjs", + generateUUID: "chrome://remote/content/shared/UUID.sys.mjs", + TabManager: "chrome://remote/content/shared/TabManager.sys.mjs", +}); + +/** + * The navigable manager is intended to be used as a singleton and is + * responsible for tracking open browsing contexts by assigning each a + * unique identifier. This allows them to be referenced unambiguously. + * For top-level browsing contexts, the content browser instance itself + * is used as the anchor, since cross-origin navigations can result in + * browsing context replacements. Using the browser as a stable reference + * ensures that protocols like WebDriver BiDi and Marionette can reliably + * point to the intended "navigable" — a concept from the HTML specification + * that is not implemented in Firefox. + */ +class NavigableManagerClass { + #tracking; + #browserIds; + #contextListener; + #navigableIds; + + constructor() { + this.#tracking = false; + + // Maps browser's `permanentKey` to an uuid: WeakMap.<Object, string> + // + // It's required as a fallback, since in the case when a context was + // discarded embedderElement is gone, and we cannot retrieve the + // context id from the formerly known browser. + this.#browserIds = new WeakMap(); + + // Maps browsing contexts to uuid: WeakMap.<BrowsingContext, string>. + this.#navigableIds = new WeakMap(); + + // Start tracking by default when the class gets instantiated. + this.startTracking(); + } + + /** + * 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 tab of lazy.TabManager.allTabs) { + const contentBrowser = lazy.TabManager.getBrowserForTab(tab); + if (this.getIdForBrowser(contentBrowser) == id) { + return contentBrowser; + } + } + + return null; + } + + /* Retrieve the browsing context corresponding to the provided navigabl 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 or + * browsing context is discarded. + */ + getBrowsingContextById(id) { + let browsingContext; + + const browser = this.getBrowserById(id); + if (browser) { + // top-level browsing context + browsingContext = browser.browsingContext; + } else { + browsingContext = BrowsingContext.get(id); + } + + if (!browsingContext || browsingContext.isDiscarded) { + return null; + } + + return browsingContext; + } + + /** + * 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} browser + * The <xul:browser> for which we want to retrieve the id. + * + * @returns {string|null} + * The unique id for this browser or `null` if invalid. + */ + getIdForBrowser(browser) { + if (!(XULElement.isInstance(browser) && browser.permanentKey)) { + // Ignore those browsers that do not have a permanentKey + // attached like the print preview (bug 1990485), but which + // we need to uniquely identify a top-level browsing context. + return null; + } + + const key = browser.permanentKey; + if (!this.#browserIds.has(key)) { + this.#browserIds.set(key, lazy.generateUUID()); + } + return this.#browserIds.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|null} + * The unique id of the browsing context or `null` if invalid. + */ + getIdForBrowsingContext(browsingContext) { + if (!BrowsingContext.isInstance(browsingContext)) { + return null; + } + + if (!browsingContext.parent) { + // For top-level browsing contexts always try to use the browser + // as navigable first because it survives a cross-process navigation. + const browser = this.#getBrowserForBrowsingContext(browsingContext); + if (browser) { + return this.getIdForBrowser(browser); + } + + // If no browser can be found fallback to use the navigable id instead. + return this.#navigableIds.has(browsingContext) + ? this.#navigableIds.get(browsingContext) + : null; + } + + // Child browsing context (frame) + return browsingContext.id.toString(); + } + + /** + * Get the navigable for the given browsing context. + * + * Because Gecko doesn't support the Navigable concept in content + * scope the content browser could be used to uniquely identify + * top-level browsing contexts. + * + * @param {BrowsingContext} browsingContext + * + * @returns {BrowsingContext|XULBrowser} The navigable + * + * @throws {TypeError} + * If `browsingContext` is not a CanonicalBrowsingContext instance. + */ + getNavigableForBrowsingContext(browsingContext) { + if (!lazy.TabManager.isValidCanonicalBrowsingContext(browsingContext)) { + throw new TypeError( + `Expected browsingContext to be a CanonicalBrowsingContext, got ${browsingContext}` + ); + } + + if (browsingContext.isContent && browsingContext.parent === null) { + return this.#getBrowserForBrowsingContext(browsingContext); + } + + return browsingContext; + } + + startTracking() { + if (this.#tracking) { + return; + } + + lazy.TabManager.getBrowsers().forEach(browser => + this.#setIdForBrowsingContext(browser.browsingContext) + ); + + this.#contextListener = new lazy.BrowsingContextListener(); + this.#contextListener.on("attached", this.#onContextAttached); + this.#contextListener.startListening(); + + this.#tracking = true; + } + + stopTracking() { + if (!this.#tracking) { + return; + } + + this.#contextListener.stopListening(); + this.#contextListener = null; + + this.#browserIds = new WeakMap(); + this.#navigableIds = new WeakMap(); + + this.#tracking = false; + } + + /** Private methods */ + + /** + * Try to find the browser element to browsing context is attached to. + * + * @param {BrowsingContext} browsingContext + * The browsing context to find the related browser for. + * + * @returns {XULBrowser|null} + * The <xul:browser> element, or `null` if no browser exists. + */ + #getBrowserForBrowsingContext(browsingContext) { + return browsingContext.top.embedderElement + ? browsingContext.top.embedderElement + : null; + } + + /** + * Update the internal maps for a new browsing context. + * + * @param {BrowsingContext} browsingContext + * The browsing context that needs to be observed. + */ + #setIdForBrowsingContext(browsingContext) { + const id = this.getIdForBrowsingContext(browsingContext); + + // Add a fallback to the navigable weak map so that an id can + // also be retrieved when the related browser was closed. + this.#navigableIds.set(browsingContext, id); + } + + /** Event handlers */ + + #onContextAttached = (_, data = {}) => { + const { browsingContext } = data; + + if (lazy.TabManager.isValidCanonicalBrowsingContext(browsingContext)) { + this.#setIdForBrowsingContext(browsingContext); + } + }; +} + +// Expose a shared singleton. +export const NavigableManager = new NavigableManagerClass(); diff --git a/remote/shared/NavigationManager.sys.mjs b/remote/shared/NavigationManager.sys.mjs @@ -13,6 +13,7 @@ ChromeUtils.defineESModuleGetters(lazy, { "chrome://remote/content/shared/listeners/DownloadListener.sys.mjs", generateUUID: "chrome://remote/content/shared/UUID.sys.mjs", Log: "chrome://remote/content/shared/Log.sys.mjs", + NavigableManager: "chrome://remote/content/shared/NavigableManager.sys.mjs", ParentWebProgressListener: "chrome://remote/content/shared/listeners/ParentWebProgressListener.sys.mjs", PromptListener: @@ -147,7 +148,7 @@ class NavigationRegistry extends EventEmitter { return null; } - const navigableId = lazy.TabManager.getIdForBrowsingContext(context); + const navigableId = lazy.NavigableManager.getIdForBrowsingContext(context); if (!this.#navigations.has(navigableId)) { return null; } @@ -211,7 +212,7 @@ class NavigationRegistry extends EventEmitter { const { contextDetails, url } = data; const context = this.#getContextFromContextDetails(contextDetails); - const navigableId = lazy.TabManager.getIdForBrowsingContext(context); + const navigableId = lazy.NavigableManager.getIdForBrowsingContext(context); const navigationId = this.#getOrCreateNavigationId(navigableId); const navigation = this.#createNavigationObject({ @@ -261,7 +262,7 @@ class NavigationRegistry extends EventEmitter { const { contextDetails, url } = data; const context = this.#getContextFromContextDetails(contextDetails); - const navigableId = lazy.TabManager.getIdForBrowsingContext(context); + const navigableId = lazy.NavigableManager.getIdForBrowsingContext(context); // History updates are immediately done, fire a single event. this.emit(NAVIGATION_EVENTS.HistoryUpdated, { navigableId, url }); @@ -287,7 +288,7 @@ class NavigationRegistry extends EventEmitter { const { contextDetails, url } = data; const context = this.#getContextFromContextDetails(contextDetails); - const navigableId = lazy.TabManager.getIdForBrowsingContext(context); + const navigableId = lazy.NavigableManager.getIdForBrowsingContext(context); const navigationId = this.#getOrCreateNavigationId(navigableId); const navigation = this.#createNavigationObject({ @@ -339,7 +340,7 @@ class NavigationRegistry extends EventEmitter { const { contextDetails, errorName, url } = data; const context = this.#getContextFromContextDetails(contextDetails); - const navigableId = lazy.TabManager.getIdForBrowsingContext(context); + const navigableId = lazy.NavigableManager.getIdForBrowsingContext(context); const navigation = this.#navigations.get(navigableId); @@ -404,7 +405,7 @@ class NavigationRegistry extends EventEmitter { const { contextDetails, errorName, url } = data; const context = this.#getContextFromContextDetails(contextDetails); - const navigableId = lazy.TabManager.getIdForBrowsingContext(context); + const navigableId = lazy.NavigableManager.getIdForBrowsingContext(context); const navigation = this.#navigations.get(navigableId); @@ -458,7 +459,7 @@ class NavigationRegistry extends EventEmitter { notifyNavigationStarted(data) { const { contextDetails, url } = data; const context = this.#getContextFromContextDetails(contextDetails); - const navigableId = lazy.TabManager.getIdForBrowsingContext(context); + const navigableId = lazy.NavigableManager.getIdForBrowsingContext(context); let navigation = this.#navigations.get(navigableId); @@ -566,7 +567,7 @@ class NavigationRegistry extends EventEmitter { const { contextDetails, url } = data; const context = this.#getContextFromContextDetails(contextDetails); - const navigableId = lazy.TabManager.getIdForBrowsingContext(context); + const navigableId = lazy.NavigableManager.getIdForBrowsingContext(context); const navigation = this.#navigations.get(navigableId); if (!navigation) { @@ -611,7 +612,7 @@ class NavigationRegistry extends EventEmitter { registerNavigationId(data) { const { contextDetails } = data; const context = this.#getContextFromContextDetails(contextDetails); - const navigableId = lazy.TabManager.getIdForBrowsingContext(context); + const navigableId = lazy.NavigableManager.getIdForBrowsingContext(context); const existingNavigation = this.#navigations.get(navigableId); if ( @@ -690,7 +691,7 @@ class NavigationRegistry extends EventEmitter { } const navigableId = - lazy.TabManager.getIdForBrowsingContext(browsingContext); + lazy.NavigableManager.getIdForBrowsingContext(browsingContext); let navigation = this.#navigations.get(navigableId); if (navigation) { @@ -731,7 +732,7 @@ class NavigationRegistry extends EventEmitter { } const navigableId = - lazy.TabManager.getIdForBrowsingContext(browsingContext); + lazy.NavigableManager.getIdForBrowsingContext(browsingContext); const navigation = this.#navigations.get(navigableId); // No need to fail navigation, if there is no navigation in progress. @@ -755,13 +756,14 @@ class NavigationRegistry extends EventEmitter { const { download } = data; const contextId = download.source.browsingContextId; - const browsingContext = lazy.TabManager.getBrowsingContextById(contextId); + const browsingContext = + lazy.NavigableManager.getBrowsingContextById(contextId); if (!browsingContext) { return; } const navigableId = - lazy.TabManager.getIdForBrowsingContext(browsingContext); + lazy.NavigableManager.getIdForBrowsingContext(browsingContext); const url = download.source.url; const navigation = this.#navigations.get(navigableId); @@ -791,13 +793,14 @@ class NavigationRegistry extends EventEmitter { const { download } = data; const contextId = download.source.browsingContextId; - const browsingContext = lazy.TabManager.getBrowsingContextById(contextId); + const browsingContext = + lazy.NavigableManager.getBrowsingContextById(contextId); if (!browsingContext) { return; } const navigableId = - lazy.TabManager.getIdForBrowsingContext(browsingContext); + lazy.NavigableManager.getIdForBrowsingContext(browsingContext); const url = download.source.url; let navigationId = null; diff --git a/remote/shared/NetworkRequest.sys.mjs b/remote/shared/NetworkRequest.sys.mjs @@ -4,14 +4,14 @@ const lazy = {}; ChromeUtils.defineESModuleGetters(lazy, { + NavigableManager: "chrome://remote/content/shared/NavigableManager.sys.mjs", + NavigationState: "chrome://remote/content/shared/NavigationManager.sys.mjs", NetworkHelper: "resource://devtools/shared/network-observer/NetworkHelper.sys.mjs", NetworkUtils: "resource://devtools/shared/network-observer/NetworkUtils.sys.mjs", - NavigationState: "chrome://remote/content/shared/NavigationManager.sys.mjs", notifyNavigationStarted: "chrome://remote/content/shared/NavigationManager.sys.mjs", - TabManager: "chrome://remote/content/shared/TabManager.sys.mjs", }); /** @@ -377,7 +377,7 @@ export class NetworkRequest { #getContextId() { const id = lazy.NetworkUtils.getChannelBrowsingContextID(this.#channel); const browsingContext = BrowsingContext.get(id); - return lazy.TabManager.getIdForBrowsingContext(browsingContext); + return lazy.NavigableManager.getIdForBrowsingContext(browsingContext); } /** @@ -476,7 +476,7 @@ export class NetworkRequest { return null; } - const browsingContext = lazy.TabManager.getBrowsingContextById( + const browsingContext = lazy.NavigableManager.getBrowsingContextById( this.#contextId ); @@ -501,7 +501,7 @@ export class NetworkRequest { return false; } - const browsingContext = lazy.TabManager.getBrowsingContextById( + const browsingContext = lazy.NavigableManager.getBrowsingContextById( this.#contextId ); return !browsingContext.parent; diff --git a/remote/shared/TabManager.sys.mjs b/remote/shared/TabManager.sys.mjs @@ -6,10 +6,7 @@ const lazy = {}; ChromeUtils.defineESModuleGetters(lazy, { AppInfo: "chrome://remote/content/shared/AppInfo.sys.mjs", - BrowsingContextListener: - "chrome://remote/content/shared/listeners/BrowsingContextListener.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", UserContextManager: "chrome://remote/content/shared/UserContextManager.sys.mjs", @@ -17,125 +14,58 @@ ChromeUtils.defineESModuleGetters(lazy, { }); class TabManagerClass { - #browserUniqueIds; - #contextListener; - #navigableIds; - - constructor() { - // Maps browser's permanentKey to uuid: WeakMap.<Object, string> - this.#browserUniqueIds = new WeakMap(); - - // Maps browsing contexts to uuid: WeakMap.<BrowsingContext, string>. - // It's required as a fallback, since in the case when a context was discarded - // embedderElement is gone, and we cannot retrieve - // the context id from this.#browserUniqueIds. - this.#navigableIds = new WeakMap(); - - this.#contextListener = new lazy.BrowsingContextListener(); - this.#contextListener.on("attached", this.#onContextAttached); - this.#contextListener.startListening(); - - this.getBrowsers().forEach(browser => { - if (this.isValidCanonicalBrowsingContext(browser.browsingContext)) { - this.#navigableIds.set( - browser.browsingContext, - this.getIdForBrowsingContext(browser.browsingContext) - ); - } - }); - } - - /** - * Retrieve all the browser elements from tabs as contained in open windows. - * By default excludes browsers with a null browsingContext (unloaded tabs). - * - * @param {object=} options - * @param {boolean=} options.unloaded - * Pass true to also retrieve browsers for unloaded tabs. Defaults to - * false. - * - * @returns {Array<XULBrowser>} - * All the found <xul:browser>s. Will return an empty array if - * no windows and tabs can be found. - */ - getBrowsers(options = {}) { - const { unloaded = false } = options; - const browsers = []; - - for (const win of lazy.windowManager.windows) { - for (const tab of this.getTabsForWindow(win)) { - const contentBrowser = this.getBrowserForTab(tab); - if ( - contentBrowser !== null && - (unloaded || contentBrowser.browsingContext != null) - ) { - browsers.push(contentBrowser); - } - } - } - - return browsers; - } - /** - * Retrieve all the browser tabs in open windows. + * Retrieve all the tabs in open browser windows. * * @returns {Array<Tab>} * All the open browser tabs. Will return an empty list if tab browser * is not available or tabs are undefined. */ - get tabs() { - const tabs = []; - - for (const win of lazy.windowManager.windows) { - tabs.push(...this.getTabsForWindow(win)); - } - - return tabs; + get allTabs() { + return lazy.windowManager.windows.flatMap(win => + this.getTabsForWindow(win) + ); } /** - * Array of unique browser ids (UUIDs) for all content browsers of all - * windows. + * Get the linked `xul:browser` for the specified tab. * - * TODO: Similarly to getBrowserById, we should improve the performance of - * this getter in Bug 1750065. + * @param {Tab} tab + * The tab whose browser needs to be returned. * - * @returns {Array<string>} - * Array of UUIDs for all content browsers. + * @returns {XULBrowser|null} + * The linked browser for the tab or `null` if no browser can be found. */ - get allBrowserUniqueIds() { - const browserIds = []; - - for (const win of lazy.windowManager.windows) { - // Only return handles for browser windows - for (const tab of this.getTabsForWindow(win)) { - const contentBrowser = this.getBrowserForTab(tab); - const winId = this.getIdForBrowser(contentBrowser); - if (winId !== null) { - browserIds.push(winId); - } - } - } - - return browserIds; + getBrowserForTab(tab) { + return tab?.linkedBrowser ?? null; } /** - * Get the <code>&lt;xul:browser&gt;</code> for the specified tab. + * Retrieve all the browser elements from tabs as contained in open windows. * - * @param {Tab} tab - * The tab whose browser needs to be returned. + * By default excludes browsers for unloaded tabs. + * + * @param {object=} options + * @param {boolean=} options.unloaded + * Pass true to also retrieve browsers for unloaded tabs. Defaults to + * false. * - * @returns {XULBrowser} - * The linked browser for the tab or null if no browser can be found. + * @returns {Array<XULBrowser>} + * All the found <xul:browser>s. Will return an empty array if + * no windows and tabs can be found. */ - getBrowserForTab(tab) { - if (tab && "linkedBrowser" in tab) { - return tab.linkedBrowser; - } + getBrowsers(options = {}) { + const { unloaded = false } = options; - return null; + return this.allTabs + .map(tab => this.getBrowserForTab(tab)) + .filter(browser => { + return ( + browser !== null && + (unloaded || + this.isValidCanonicalBrowsingContext(browser.browsingContext)) + ); + }); } /** @@ -144,10 +74,14 @@ class TabManagerClass { * @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. + * @returns {TabBrowser|null} + * Tab browser or `null` if it's not a browser window. */ getTabBrowser(win) { + if (!win) { + return null; + } + if (lazy.AppInfo.isAndroid) { return new lazy.MobileTabBrowser(win); } else if (lazy.AppInfo.isFirefox) { @@ -211,149 +145,16 @@ class TabManagerClass { } /** - * 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. + * Retrieve the count of all the open tabs. * - * @returns {XULBrowser} - * The <xul:browser> corresponding to the provided id. Will return null if - * no matching browser element is found. + * @returns {number} Number of open tabs. */ - getBrowserById(id) { - for (const win of lazy.windowManager.windows) { - for (const tab of this.getTabsForWindow(win)) { - const contentBrowser = this.getBrowserForTab(tab); - 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 or - * browsing context is discarded. - */ - getBrowsingContextById(id) { - const browser = this.getBrowserById(id); - let browsingContext; - if (browser) { - browsingContext = browser.browsingContext; - } else { - browsingContext = BrowsingContext.get(id); - } - - if (!browsingContext || browsingContext.isDiscarded) { - return null; - } - return browsingContext; - } - - /** - * 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 (key === undefined) { - return null; - } - - if (!this.#browserUniqueIds.has(key)) { - this.#browserUniqueIds.set(key, lazy.generateUUID()); - } - return this.#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. - // If a context was discarded, embedderElement is already gone, - // so use navigable id instead. - return browsingContext.embedderElement - ? this.getIdForBrowser(browsingContext.embedderElement) - : this.#navigableIds.get(browsingContext); - } - - return browsingContext.id.toString(); - } - - /** - * Get the navigable for the given browsing context. - * - * Because Gecko doesn't support the Navigable concept in content - * scope the content browser could be used to uniquely identify - * top-level browsing contexts. - * - * @param {BrowsingContext} browsingContext - * - * @returns {BrowsingContext|XULBrowser} The navigable - * - * @throws {TypeError} - * If `browsingContext` is not a CanonicalBrowsingContext instance. - */ - getNavigableForBrowsingContext(browsingContext) { - if (!this.isValidCanonicalBrowsingContext(browsingContext)) { - throw new TypeError( - `Expected browsingContext to be a CanonicalBrowsingContext, got ${browsingContext}` - ); - } - - if (browsingContext.isContent && browsingContext.parent === null) { - return browsingContext.embedderElement; - } - - return browsingContext; - } - getTabCount() { - let count = 0; - for (const win of lazy.windowManager.windows) { + return lazy.windowManager.windows.reduce((total, win) => { // For browser windows count the tabs. Otherwise take the window itself. const tabsLength = this.getTabsForWindow(win).length; - count += tabsLength ? tabsLength : 1; - } - return count; + return total + (tabsLength ? tabsLength : 1); + }, 0); } /** @@ -379,7 +180,7 @@ class TabManagerClass { * Retrieve the list of tabs for a given window. * * @param {ChromeWindow} win - * Window whose <code>tabs</code> need to be returned. + * Window whose tabs need to be returned. * * @returns {Array<Tab>} * The list of tabs. Will return an empty list if tab browser is not available @@ -387,11 +188,13 @@ class TabManagerClass { */ getTabsForWindow(win) { const tabBrowser = this.getTabBrowser(win); + // For web-platform reftests a faked tabbrowser is used, // which does not actually have tabs. if (tabBrowser && tabBrowser.tabs) { return tabBrowser.tabs; } + return []; } @@ -482,16 +285,6 @@ class TabManagerClass { supportsTabs() { return lazy.AppInfo.isAndroid || lazy.AppInfo.isFirefox; } - - #onContextAttached = (eventName, data = {}) => { - const { browsingContext } = data; - if (this.isValidCanonicalBrowsingContext(browsingContext)) { - this.#navigableIds.set( - browsingContext, - this.getIdForBrowsingContext(browsingContext) - ); - } - }; } // Expose a shared singleton. diff --git a/remote/shared/UserContextManager.sys.mjs b/remote/shared/UserContextManager.sys.mjs @@ -163,19 +163,13 @@ export class UserContextManagerClass { * The array of tabs. */ getTabsForUserContext(internalId) { - const tabs = []; - - for (const tab of lazy.TabManager.tabs) { - if ( + return lazy.TabManager.allTabs.filter(tab => { + return ( (tab.hasAttribute("usercontextid") && parseInt(tab.getAttribute("usercontextid"), 10) == internalId) || (!tab.hasAttribute("usercontextid") && internalId === 0) - ) { - tabs.push(tab); - } - } - - return tabs; + ); + }); } /** diff --git a/remote/shared/WindowManager.sys.mjs b/remote/shared/WindowManager.sys.mjs @@ -16,6 +16,7 @@ ChromeUtils.defineESModuleGetters(lazy, { EventPromise: "chrome://remote/content/shared/Sync.sys.mjs", generateUUID: "chrome://remote/content/shared/UUID.sys.mjs", Log: "chrome://remote/content/shared/Log.sys.mjs", + NavigableManager: "chrome://remote/content/shared/NavigableManager.sys.mjs", TabManager: "chrome://remote/content/shared/TabManager.sys.mjs", TimedPromise: "chrome://remote/content/shared/Sync.sys.mjs", UserContextManager: @@ -109,7 +110,8 @@ class WindowManager { let contentBrowser = lazy.TabManager.getBrowserForTab( tabBrowser.tabs[i] ); - let contentWindowId = lazy.TabManager.getIdForBrowser(contentBrowser); + let contentWindowId = + lazy.NavigableManager.getIdForBrowser(contentBrowser); if (contentWindowId == handle) { return this.getWindowProperties(win, { tabIndex: i }); @@ -169,7 +171,7 @@ class WindowManager { * The ID of the window associated with the browsing context. */ getIdForBrowsingContext(context) { - const window = this.#getBrowsingContextWindow(context); + const window = this.getWindowForBrowsingContext(context); return window ? this.getIdForWindow(window) @@ -338,6 +340,21 @@ class WindowManager { } /** + * Returns the window for a specific browsing context. + * + * @param {BrowsingContext} context + * The browsing context for which we want to retrieve the window. + * + * @returns {ChromeWindow} + * The chrome window associated with the browsing context. + */ + getWindowForBrowsingContext(context) { + return lazy.AppInfo.isAndroid + ? context.top.embedderElement?.ownerGlobal + : context.topChromeWindow; + } + + /** * Open a new browser window. * * @param {object=} options @@ -518,25 +535,10 @@ class WindowManager { ); } - /** - * Returns the window for a specific browsing context. - * - * @param {BrowsingContext} context - * The browsing context for which we want to retrieve the window. - * - * @returns {(window|undefined)} - * The window associated with the browsing context. - */ - #getBrowsingContextWindow(context) { - return lazy.AppInfo.isAndroid - ? context.top.embedderElement?.ownerGlobal - : context.topChromeWindow; - } - #onContextAttached = (_, data = {}) => { const { browsingContext } = data; - const window = this.#getBrowsingContextWindow(browsingContext); + const window = this.getWindowForBrowsingContext(browsingContext); if (!window) { // Short-lived iframes might already be disconnected from their parent // window. diff --git a/remote/shared/listeners/test/browser/browser_NetworkListener.js b/remote/shared/listeners/test/browser/browser_NetworkListener.js @@ -6,15 +6,15 @@ const { NetworkDecodedBodySizeMap } = ChromeUtils.importESModule( "chrome://remote/content/shared/NetworkDecodedBodySizeMap.sys.mjs" ); +const { NavigableManager } = ChromeUtils.importESModule( + "chrome://remote/content/shared/NavigableManager.sys.mjs" +); const { NavigationManager } = ChromeUtils.importESModule( "chrome://remote/content/shared/NavigationManager.sys.mjs" ); const { NetworkListener } = ChromeUtils.importESModule( "chrome://remote/content/shared/listeners/NetworkListener.sys.mjs" ); -const { TabManager } = ChromeUtils.importESModule( - "chrome://remote/content/shared/TabManager.sys.mjs" -); add_task(async function test_beforeRequestSent() { const decodedBodySizeMap = new NetworkDecodedBodySizeMap(); @@ -31,14 +31,14 @@ add_task(async function test_beforeRequestSent() { "https://example.com/document-builder.sjs?html=tab" ); await BrowserTestUtils.browserLoaded(tab1.linkedBrowser); - const contextId1 = TabManager.getIdForBrowser(tab1.linkedBrowser); + const contextId1 = NavigableManager.getIdForBrowser(tab1.linkedBrowser); const tab2 = BrowserTestUtils.addTab( gBrowser, "https://example.com/document-builder.sjs?html=tab2" ); await BrowserTestUtils.browserLoaded(tab2.linkedBrowser); - const contextId2 = TabManager.getIdForBrowser(tab2.linkedBrowser); + const contextId2 = NavigableManager.getIdForBrowser(tab2.linkedBrowser); listener.startListening(); @@ -83,7 +83,7 @@ add_task(async function test_beforeRequestSent_newTab() { "https://example.com/document-builder.sjs?html=tab" ); await BrowserTestUtils.browserLoaded(tab.linkedBrowser); - const contextId = TabManager.getIdForBrowser(tab.linkedBrowser); + const contextId = NavigableManager.getIdForBrowser(tab.linkedBrowser); const event = await onBeforeRequestSent; assertNetworkEvent( @@ -108,7 +108,7 @@ add_task(async function test_fetchError() { info("Check fetchError event when loading a new tab"); const tab = BrowserTestUtils.addTab(gBrowser, "https://not_a_valid_url/"); BrowserTestUtils.browserLoaded(tab.linkedBrowser); - const contextId = TabManager.getIdForBrowser(tab.linkedBrowser); + const contextId = NavigableManager.getIdForBrowser(tab.linkedBrowser); const event = await onFetchError; assertNetworkEvent(event, contextId, "https://not_a_valid_url/"); diff --git a/remote/shared/test/browser/browser.toml b/remote/shared/test/browser/browser.toml @@ -8,6 +8,8 @@ support-files = [ ["browser_Addon.js"] +["browser_NavigableManager.js"] + ["browser_NavigationManager.js"] ["browser_NavigationManager_committed.js"] diff --git a/remote/shared/test/browser/browser_NavigableManager.js b/remote/shared/test/browser/browser_NavigableManager.js @@ -0,0 +1,442 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const { NavigableManager } = ChromeUtils.importESModule( + "chrome://remote/content/shared/NavigableManager.sys.mjs" +); + +const BUILDER_URL = "https://example.com/document-builder.sjs?html="; +const FRAME_URL = "https://example.com/document-builder.sjs?html=frame"; +const FRAME_MARKUP = ` + <iframe src="${encodeURI(FRAME_URL)}"></iframe> + <iframe src="${encodeURI(FRAME_URL)}"></iframe> +`; +const TEST_URL = BUILDER_URL + encodeURI(FRAME_MARKUP); + +const numberRegex = /[0-9]+/i; +const uuidRegex = + /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i; + +describe("NavigableManager", function () { + let testData; + + beforeEach(async () => { + NavigableManager.startTracking(); + + const initialBrowser = gBrowser.selectedBrowser; + const initialContext = initialBrowser.browsingContext; + + info(`Open a new tab and navigate to ${TEST_URL}`); + const newTab = await addTabAndWaitForNavigated(gBrowser, TEST_URL); + + const newBrowser = newTab.linkedBrowser; + const newContext = newBrowser.browsingContext; + const newFrameContexts = newContext + .getAllBrowsingContextsInSubtree() + .filter(context => context.parent); + + is(newFrameContexts.length, 2, "Top context has 2 child contexts"); + + testData = { + initialBrowser, + initialContext, + newBrowser, + newContext, + newFrameContexts, + newTab, + }; + }); + + afterEach(() => { + NavigableManager.stopTracking(); + + gBrowser.removeAllTabsBut(gBrowser.tabs[0]); + }); + + it("Get the browser by its Navigable id", async function test_getBrowserById() { + const { initialBrowser, newBrowser } = testData; + + const invalidValues = [undefined, null, 1, "foo", {}, []]; + invalidValues.forEach(value => + is(NavigableManager.getBrowserById(value), null) + ); + + const initialBrowserId = NavigableManager.getIdForBrowser(initialBrowser); + const newBrowserId = NavigableManager.getIdForBrowser(newBrowser); + + Assert.stringMatches( + initialBrowserId, + uuidRegex, + "Initial browser is a valid uuid" + ); + Assert.stringMatches( + newBrowserId, + uuidRegex, + "New tab's browser is a valid uuid" + ); + isnot(initialBrowserId, newBrowserId, "Both browsers have different ids"); + + is(NavigableManager.getBrowserById(initialBrowserId), initialBrowser); + is(NavigableManager.getBrowserById(newBrowserId), newBrowser); + }); + + it("Get the BrowsingContext by its Navigable id", async function test_getBrowsingContextById() { + const { newContext, newFrameContexts } = testData; + + const invalidValues = [undefined, null, "foo", {}, []]; + invalidValues.forEach(value => + is(NavigableManager.getBrowsingContextById(value), null) + ); + + const newContextId = NavigableManager.getIdForBrowsingContext(newContext); + const newFrameContextIds = newFrameContexts.map(context => + NavigableManager.getIdForBrowsingContext(context) + ); + + Assert.stringMatches( + newContextId, + uuidRegex, + "Top context is a valid uuid" + ); + Assert.stringMatches( + newFrameContextIds[0], + numberRegex, + "First child context has a valid id" + ); + Assert.stringMatches( + newFrameContextIds[1], + numberRegex, + "Second child context has a valid id" + ); + isnot( + newContextId, + newFrameContextIds[0], + "Id of top-level context is different from first child context" + ); + isnot( + newContextId, + newFrameContextIds[0], + "Id of top-level context is different from second child context" + ); + is( + NavigableManager.getBrowsingContextById(newFrameContextIds[0]), + newFrameContexts[0], + "Context of first child can be retrieved by id" + ); + is( + NavigableManager.getBrowsingContextById(newFrameContextIds[1]), + newFrameContexts[1], + "Context of second child can be retrieved by id" + ); + }); + + it("Get the Navigable id for a browser", async function test_getIdForBrowser() { + const { initialBrowser, newBrowser, newContext } = testData; + + const invalidValues = [undefined, null, 1, "foo", {}, []]; + invalidValues.forEach(value => + is(NavigableManager.getBrowserById(value), null) + ); + + is( + NavigableManager.getIdForBrowser(newContext), + null, + "Requires a browser instance as argument" + ); + + const newBrowserId = NavigableManager.getIdForBrowser(newBrowser); + Assert.stringMatches( + NavigableManager.getIdForBrowser(newBrowser), + uuidRegex, + "Got a valid uuid for the browser" + ); + is( + NavigableManager.getIdForBrowser(newBrowser), + newBrowserId, + "For the same browser the identical id is returned" + ); + isnot( + NavigableManager.getIdForBrowser(initialBrowser), + newBrowserId, + "For a different browser the id is not the same" + ); + }); + + it("Get the Navigable id for a BrowsingContext", async function test_getIdForBrowsingContext() { + const { newBrowser, newContext, newFrameContexts } = testData; + + const invalidValues = [undefined, null, 1, "foo", {}, []]; + invalidValues.forEach(value => + is(NavigableManager.getIdForBrowsingContext(value), null) + ); + + const newContextId = NavigableManager.getIdForBrowsingContext(newContext); + const newFrameContextIds = newFrameContexts.map(context => + NavigableManager.getIdForBrowsingContext(context) + ); + + Assert.stringMatches( + newContextId, + uuidRegex, + "Got a valid uuid for top-level context" + ); + is( + NavigableManager.getIdForBrowsingContext(newContext), + newContextId, + "Id is always the same for a top-level context" + ); + is( + NavigableManager.getIdForBrowsingContext(newContext), + NavigableManager.getIdForBrowser(newBrowser), + "Id of a top-level context is equal to the browser id" + ); + + Assert.stringMatches( + newFrameContextIds[0], + numberRegex, + "Got a valid id for a child context" + ); + is( + NavigableManager.getIdForBrowsingContext(newFrameContexts[0]), + newFrameContextIds[0], + "Id is always the same for a child context" + ); + }); + + it("Get the Navigable for a BrowsingContext", async function test_getNavigableForBrowsingContext() { + const { newBrowser, newContext, newFrameContexts, newTab } = testData; + + const invalidValues = [undefined, null, 1, "test", {}, [], newBrowser]; + invalidValues.forEach(invalidValue => + Assert.throws( + () => NavigableManager.getNavigableForBrowsingContext(invalidValue), + /Expected browsingContext to be a CanonicalBrowsingContext/ + ) + ); + + is( + NavigableManager.getNavigableForBrowsingContext(newContext), + newBrowser, + "Top-Level context has the content browser as navigable" + ); + is( + NavigableManager.getNavigableForBrowsingContext(newFrameContexts[0]), + newFrameContexts[0], + "Child context has itself as navigable" + ); + + gBrowser.removeTab(newTab); + }); + + it("Get discarded BrowsingContext by id", async function test_getDiscardedBrowsingContextById() { + const { newBrowser, newContext, newFrameContexts, newTab } = testData; + + const newContextId = NavigableManager.getIdForBrowsingContext(newContext); + const newFrameContextIds = newFrameContexts.map(context => + NavigableManager.getIdForBrowsingContext(context) + ); + + is( + NavigableManager.getBrowsingContextById(newContextId), + newContext, + "Top context can be retrieved by its id" + ); + is( + NavigableManager.getBrowsingContextById(newFrameContextIds[0]), + newFrameContexts[0], + "Child context can be retrieved by its id" + ); + + // Remove all the iframes + await SpecialPowers.spawn(newBrowser, [], async () => { + const frames = content.document.querySelectorAll("iframe"); + frames.forEach(frame => frame.remove()); + }); + + is( + NavigableManager.getBrowsingContextById(newContextId), + newContext, + "Top context can still be retrieved after removing all the frames" + ); + is( + NavigableManager.getBrowsingContextById(newFrameContextIds[0]), + null, + "Child context can no longer be retrieved by its id after removing all the frames" + ); + + gBrowser.removeTab(newTab); + + is( + NavigableManager.getBrowsingContextById(newContextId), + null, + "Top context can no longer be retrieved by its id after the tab is closed" + ); + }); + + it("Support unloaded browsers", async function test_unloadedBrowser() { + const { newBrowser, newContext, newTab } = testData; + + const newBrowserId = NavigableManager.getIdForBrowser(newBrowser); + const newContextId = NavigableManager.getIdForBrowsingContext(newContext); + + await gBrowser.discardBrowser(newTab); + + is( + NavigableManager.getIdForBrowser(newBrowser), + newBrowserId, + "Id for the browser is still available after unloading the tab" + ); + is( + NavigableManager.getBrowserById(newBrowserId), + newBrowser, + "Unloaded browser can still be retrieved by id" + ); + + is( + NavigableManager.getIdForBrowsingContext(newContext), + newContextId, + "Id for the browsing context is still available after unloading the tab" + ); + is( + NavigableManager.getBrowsingContextById(newContextId), + null, + "Browsing context can no longer be retrieved after unloading the tab" + ); + Assert.throws( + () => NavigableManager.getNavigableForBrowsingContext(newContext), + /Expected browsingContext to be a CanonicalBrowsingContext/ + ); + + // Loading a new page for new browsing contexts + await loadURL(newBrowser, TEST_URL); + + const newBrowsingContext = newBrowser.browsingContext; + + is( + NavigableManager.getIdForBrowser(newBrowser), + newBrowserId, + "Id for the browser is still the same when navigating after unloading the tab" + ); + is( + NavigableManager.getBrowserById(newBrowserId), + newBrowser, + "New browser can be retrieved by its id" + ); + + is( + NavigableManager.getIdForBrowsingContext(newBrowsingContext), + newContextId, + "Id for the new top-level context is still the same" + ); + is( + NavigableManager.getBrowsingContextById(newContextId), + newBrowsingContext, + "Top-level context can be retrieved again" + ); + is( + NavigableManager.getNavigableForBrowsingContext(newBrowsingContext), + newBrowser, + "The navigable can be retrieved again" + ); + }); + + it("Retrieve id for cross-group opener", async function test_crossGroupOpener() { + const { newContext, newBrowser, newTab } = testData; + + const newContextId = NavigableManager.getIdForBrowsingContext(newContext); + + await SpecialPowers.spawn(newBrowser, [], async () => { + content.open("", "_blank", ""); + }); + + const browser = gBrowser.selectedBrowser; + const openerContext = browser.browsingContext.crossGroupOpener; + + isnot( + browser.browsingContext.crossGroupOpener, + null, + "Opened popup window has a cross-group opener" + ); + is( + NavigableManager.getIdForBrowsingContext(openerContext), + newContextId, + "Id of cross-group opener context is correct" + ); + + // Remove the tab which opened the popup window + gBrowser.removeTab(newTab); + + is( + NavigableManager.getIdForBrowsingContext(openerContext), + newContextId, + "Id of cross-group opener context is still correct after closing the opener tab" + ); + }); + + it("Start and stop tracking of browsing contexts", async function test_startStopTracking() { + async function addUnloadedTab(tabBrowser) { + info(`Open a new tab, navigate, and unload it immediately`); + const newTab = await addTabAndWaitForNavigated(tabBrowser, TEST_URL); + + const newBrowser = newTab.linkedBrowser; + const newContext = newBrowser.browsingContext; + + const newFrameContexts = newContext + .getAllBrowsingContextsInSubtree() + .filter(context => context.parent); + + info(`Unload the newly opened tab`); + await tabBrowser.discardBrowser(newTab); + + return { + newBrowser, + newContext, + newFrameContexts, + newTab, + }; + } + + // Calling start tracking multiple times doesn't cause failures. + NavigableManager.startTracking(); + NavigableManager.startTracking(); + + { + // Stop tracking of new browsing contexts + NavigableManager.stopTracking(); + + let { newBrowser, newContext } = await addUnloadedTab(gBrowser); + + Assert.stringMatches( + NavigableManager.getIdForBrowser(newBrowser), + uuidRegex, + "There is always a valid uuid for the browser" + ); + is( + NavigableManager.getIdForBrowsingContext(newContext), + null, + "There is no id of a temporarily open top-level context" + ); + } + + // Calling stop tracking multiple times doesn't cause failures. + NavigableManager.stopTracking(); + + // Re-enable tracking + NavigableManager.startTracking(); + + let { newBrowser, newContext } = await addUnloadedTab(gBrowser); + + Assert.stringMatches( + NavigableManager.getIdForBrowser(newBrowser), + uuidRegex, + "Got a valid uuid for the browser" + ); + Assert.stringMatches( + NavigableManager.getIdForBrowsingContext(newContext), + uuidRegex, + "Got a valid uuid for the top-level context" + ); + }); +}); diff --git a/remote/shared/test/browser/browser_NavigationManager.js b/remote/shared/test/browser/browser_NavigationManager.js @@ -2,8 +2,8 @@ * 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 { TabManager } = ChromeUtils.importESModule( - "chrome://remote/content/shared/TabManager.sys.mjs" +const { NavigableManager } = ChromeUtils.importESModule( + "chrome://remote/content/shared/NavigableManager.sys.mjs" ); const FIRST_URL = "https://example.com/document-builder.sjs?html=first"; @@ -26,7 +26,7 @@ add_task(async function test_simpleNavigation() { const tab = await addTabAndWaitForNavigated(gBrowser, FIRST_URL); const browser = tab.linkedBrowser; - const navigableId = TabManager.getIdForBrowser(browser); + const navigableId = NavigableManager.getIdForBrowser(browser); navigationManager.startMonitoring(); is( @@ -97,12 +97,12 @@ add_task(async function test_loadTwoTabsSimultaneously() { info("Add two tabs simultaneously"); const tab1 = addTab(gBrowser, FIRST_URL); const browser1 = tab1.linkedBrowser; - const navigableId1 = TabManager.getIdForBrowser(browser1); + const navigableId1 = NavigableManager.getIdForBrowser(browser1); const onLoad1 = BrowserTestUtils.browserLoaded(browser1, false, FIRST_URL); const tab2 = addTab(gBrowser, SECOND_URL); const browser2 = tab2.linkedBrowser; - const navigableId2 = TabManager.getIdForBrowser(browser2); + const navigableId2 = NavigableManager.getIdForBrowser(browser2); const onLoad2 = BrowserTestUtils.browserLoaded(browser2, false, SECOND_URL); info("Wait for the tabs to load"); @@ -179,7 +179,7 @@ add_task(async function test_loadPageWithIframes() { for (const context of contexts) { const navigation = navigationManager.getNavigationForBrowsingContext(context); - const navigable = TabManager.getIdForBrowsingContext(context); + const navigable = NavigableManager.getIdForBrowsingContext(context); const url = context.currentWindowGlobal.documentURI.spec; assertNavigation(navigation, url); @@ -197,7 +197,7 @@ add_task(async function test_loadPageWithIframes() { for (const context of newContexts) { const navigation = navigationManager.getNavigationForBrowsingContext(context); - const navigable = TabManager.getIdForBrowsingContext(context); + const navigable = NavigableManager.getIdForBrowsingContext(context); const url = context.currentWindowGlobal.documentURI.spec; assertNavigation(navigation, url); @@ -224,7 +224,7 @@ add_task(async function test_loadPageWithCoop() { navigationManager.startMonitoring(); - const navigableId = TabManager.getIdForBrowser(browser); + const navigableId = NavigableManager.getIdForBrowser(browser); await loadURL(browser, SECOND_COOP_URL); await BrowserTestUtils.waitForCondition(() => events.length === 2); @@ -261,7 +261,7 @@ add_task(async function test_sameDocumentNavigation() { const browser = tab.linkedBrowser; navigationManager.startMonitoring(); - const navigableId = TabManager.getIdForBrowser(browser); + const navigableId = NavigableManager.getIdForBrowser(browser); is(events.length, 0, "No event recorded"); diff --git a/remote/shared/test/browser/browser_NavigationManager_failed_navigation.js b/remote/shared/test/browser/browser_NavigationManager_failed_navigation.js @@ -2,8 +2,8 @@ * 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 { TabManager } = ChromeUtils.importESModule( - "chrome://remote/content/shared/TabManager.sys.mjs" +const { NavigableManager } = ChromeUtils.importESModule( + "chrome://remote/content/shared/NavigableManager.sys.mjs" ); const TEST_URL = "https://example.com/document-builder.sjs?html=test1"; @@ -22,7 +22,7 @@ add_task(async function testClosedPort() { const browser = tab.linkedBrowser; navigationManager.startMonitoring(); - const navigableId = TabManager.getIdForBrowser(browser); + const navigableId = NavigableManager.getIdForBrowser(browser); is( navigationManager.getNavigationForBrowsingContext(browser.browsingContext), @@ -64,7 +64,7 @@ add_task(async function testWrongURI() { navigationManager.startMonitoring(); - const navigableId = TabManager.getIdForBrowser(browser); + const navigableId = NavigableManager.getIdForBrowser(browser); is( navigationManager.getNavigationForBrowsingContext(browser.browsingContext), diff --git a/remote/shared/test/browser/browser_NavigationManager_notify.js b/remote/shared/test/browser/browser_NavigationManager_notify.js @@ -2,13 +2,13 @@ * 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 { NavigableManager } = ChromeUtils.importESModule( + "chrome://remote/content/shared/NavigableManager.sys.mjs" +); const { notifyNavigationStarted, notifyNavigationStopped } = ChromeUtils.importESModule( "chrome://remote/content/shared/NavigationManager.sys.mjs" ); -const { TabManager } = ChromeUtils.importESModule( - "chrome://remote/content/shared/TabManager.sys.mjs" -); const FIRST_URL = "https://example.com/document-builder.sjs?html=first"; const SECOND_URL = "https://example.com/document-builder.sjs?html=second"; @@ -26,7 +26,7 @@ add_task(async function test_notifyNavigationStartedStopped() { navigationManager.startMonitoring(); - const navigableId = TabManager.getIdForBrowser(browser); + const navigableId = NavigableManager.getIdForBrowser(browser); info("Programmatically start a navigation"); const startedNavigation = notifyNavigationStarted({ @@ -115,7 +115,7 @@ add_task(async function test_notifyNavigationWithContextDetails() { navigationManager.startMonitoring(); - const navigableId = TabManager.getIdForBrowser(browser); + const navigableId = NavigableManager.getIdForBrowser(browser); info("Programmatically start a navigation using browsing context details"); const startedNavigation = notifyNavigationStarted({ diff --git a/remote/shared/test/browser/browser_TabManager.js b/remote/shared/test/browser/browser_TabManager.js @@ -125,105 +125,6 @@ add_task(async function test_addTab_window() { } }); -add_task(async function test_getBrowsingContextById() { - const browser = gBrowser.selectedBrowser; - - is(TabManager.getBrowsingContextById(null), null); - is(TabManager.getBrowsingContextById(undefined), null); - is(TabManager.getBrowsingContextById("wrong-id"), null); - - info(`Navigate to ${TEST_URL}`); - await loadURL(browser, TEST_URL); - - const contexts = browser.browsingContext.getAllBrowsingContextsInSubtree(); - is(contexts.length, 2, "Top context has 1 child"); - - const topContextId = TabManager.getIdForBrowsingContext(contexts[0]); - is(TabManager.getBrowsingContextById(topContextId), contexts[0]); - const childContextId = TabManager.getIdForBrowsingContext(contexts[1]); - is(TabManager.getBrowsingContextById(childContextId), contexts[1]); -}); - -add_task(async function test_getDiscardedBrowsingContextById() { - const tab = await TabManager.addTab(); - const browser = tab.linkedBrowser; - const browsingContext = browser.browsingContext; - const contextId = TabManager.getIdForBrowsingContext(browsingContext); - - is( - TabManager.getBrowsingContextById(contextId), - browsingContext, - "Browsing context is accessible by its ID" - ); - - gBrowser.removeTab(tab); - - is( - TabManager.getBrowsingContextById(contextId), - null, - "Browsing context is no longer accessible after the tab is removed" - ); -}); - -add_task(async function test_getIdForBrowsingContext() { - const browser = gBrowser.selectedBrowser; - - // Browsing context not set. - is(TabManager.getIdForBrowsingContext(null), null); - is(TabManager.getIdForBrowsingContext(undefined), null); - - info(`Navigate to ${TEST_URL}`); - await loadURL(browser, TEST_URL); - - const contexts = browser.browsingContext.getAllBrowsingContextsInSubtree(); - is(contexts.length, 2, "Top context has 1 child"); - - is( - TabManager.getIdForBrowsingContext(contexts[0]), - TabManager.getIdForBrowser(browser), - "Got expected id for top-level browsing context" - ); - is( - TabManager.getIdForBrowsingContext(contexts[1]), - contexts[1].id.toString(), - "Got expected id for child browsing context" - ); -}); - -add_task(async function test_getNavigableForBrowsingContext() { - const browser = gBrowser.selectedBrowser; - - info(`Navigate to ${TEST_URL}`); - await loadURL(browser, TEST_URL); - - const contexts = browser.browsingContext.getAllBrowsingContextsInSubtree(); - is(contexts.length, 2, "Top context has 1 child"); - - // For a top-level browsing context the content browser is returned. - const topContext = contexts[0]; - is( - TabManager.getNavigableForBrowsingContext(topContext), - browser, - "Top-Level browsing context has the content browser as navigable" - ); - - // For child browsing contexts the browsing context itself is returned. - const childContext = contexts[1]; - is( - TabManager.getNavigableForBrowsingContext(childContext), - childContext, - "Child browsing context has itself as navigable" - ); - - const invalidValues = [undefined, null, 1, "test", {}, []]; - for (const invalidValue of invalidValues) { - Assert.throws( - () => TabManager.getNavigableForBrowsingContext(invalidValue), - /Expected browsingContext to be a CanonicalBrowsingContext/ - ); - } -}); - add_task(async function test_getTabForBrowsingContext() { const tab = await TabManager.addTab(); try { @@ -338,9 +239,9 @@ add_task(async function test_tabs() { const expectedTabs = [...gBrowser.tabs, ...win1.gBrowser.tabs]; try { - is(TabManager.tabs.length, 3, "Got expected amount of open tabs"); + is(TabManager.allTabs.length, 3, "Got expected amount of open tabs"); ok( - expectedTabs.every(tab => TabManager.tabs.includes(tab)), + expectedTabs.every(tab => TabManager.allTabs.includes(tab)), "Expected tabs were returned" ); } finally { diff --git a/remote/shared/webdriver/Session.sys.mjs b/remote/shared/webdriver/Session.sys.mjs @@ -16,6 +16,7 @@ ChromeUtils.defineESModuleGetters(lazy, { error: "chrome://remote/content/shared/webdriver/Errors.sys.mjs", generateUUID: "chrome://remote/content/shared/UUID.sys.mjs", Log: "chrome://remote/content/shared/Log.sys.mjs", + NavigableManager: "chrome://remote/content/shared/NavigableManager.sys.mjs", registerProcessDataActor: "chrome://remote/content/shared/webdriver/process-actors/WebDriverProcessDataParent.sys.mjs", RootMessageHandler: @@ -68,6 +69,7 @@ export class WebDriverSession { #http; #id; #messageHandler; + #navigableSeenNodes; #path; static SESSION_FLAG_BIDI = "bidi"; @@ -275,19 +277,26 @@ export class WebDriverSession { // Maps a Navigable (browsing context or content browser for top-level // browsing contexts) to a Set of nodeId's. - this.navigableSeenNodes = new WeakMap(); + this.#navigableSeenNodes = new WeakMap(); lazy.registerProcessDataActor(); + // Start the tracking of browsing contexts to create Navigable ids. + lazy.NavigableManager.startTracking(); + webDriverSessions.set(this.#id, this); } destroy() { webDriverSessions.delete(this.#id); + // Stop the tracking of browsing contexts when no WebDriver + // session exists anymore. + lazy.NavigableManager.stopTracking(); + lazy.unregisterProcessDataActor(); - this.navigableSeenNodes = null; + this.#navigableSeenNodes = null; lazy.Certificates.enableSecurityChecks(); @@ -360,6 +369,10 @@ export class WebDriverSession { return this.#messageHandler; } + get navigableSeenNodes() { + return this.#navigableSeenNodes; + } + get pageLoadStrategy() { return this.#capabilities.get("pageLoadStrategy"); } @@ -538,7 +551,7 @@ export function getSeenNodesForBrowsingContext(sessionId, browsingContext) { } const navigable = - lazy.TabManager.getNavigableForBrowsingContext(browsingContext); + lazy.NavigableManager.getNavigableForBrowsingContext(browsingContext); const session = getWebDriverSessionById(sessionId); if (!session.navigableSeenNodes.has(navigable)) { diff --git a/remote/webdriver-bidi/modules/Intercept.sys.mjs b/remote/webdriver-bidi/modules/Intercept.sys.mjs @@ -7,7 +7,7 @@ const lazy = {}; ChromeUtils.defineESModuleGetters(lazy, { getSeenNodesForBrowsingContext: "chrome://remote/content/shared/webdriver/Session.sys.mjs", - TabManager: "chrome://remote/content/shared/TabManager.sys.mjs", + NavigableManager: "chrome://remote/content/shared/NavigableManager.sys.mjs", }); /** @@ -87,7 +87,8 @@ function addContextIdToSerializedWindow(serialized) { ); serialized.value = { - context: lazy.TabManager.getIdForBrowsingContext(browsingContext), + context: + lazy.NavigableManager.getIdForBrowsingContext(browsingContext), }; } break; diff --git a/remote/webdriver-bidi/modules/root/browser.sys.mjs b/remote/webdriver-bidi/modules/root/browser.sys.mjs @@ -125,7 +125,7 @@ class BrowserModule extends RootBiDiModule { } // Close all open top-level browsing contexts by not prompting for beforeunload. - for (const tab of lazy.TabManager.tabs) { + for (const tab of lazy.TabManager.allTabs) { lazy.TabManager.removeTab(tab, { skipPermitUnload: true }); } } diff --git a/remote/webdriver-bidi/modules/root/browsingContext.sys.mjs b/remote/webdriver-bidi/modules/root/browsingContext.sys.mjs @@ -23,6 +23,7 @@ ChromeUtils.defineESModuleGetters(lazy, { modal: "chrome://remote/content/shared/Prompt.sys.mjs", registerNavigationId: "chrome://remote/content/shared/NavigationManager.sys.mjs", + NavigableManager: "chrome://remote/content/shared/NavigableManager.sys.mjs", NavigationListener: "chrome://remote/content/shared/listeners/NavigationListener.sys.mjs", PollPromise: "chrome://remote/content/shared/Sync.sys.mjs", @@ -539,7 +540,7 @@ class BrowsingContextModule extends RootBiDiModule { lazy.pprint`Expected "promptUnload" to be a boolean, got ${promptUnload}` ); - const context = lazy.TabManager.getBrowsingContextById(contextId); + const context = lazy.NavigableManager.getBrowsingContextById(contextId); if (!context) { throw new lazy.error.NoSuchFrameError( `Browsing Context with id ${contextId} not found` @@ -613,7 +614,7 @@ class BrowsingContextModule extends RootBiDiModule { ); referenceContext = - lazy.TabManager.getBrowsingContextById(referenceContextId); + lazy.NavigableManager.getBrowsingContextById(referenceContextId); if (!referenceContext) { throw new lazy.error.NoSuchFrameError( `Browsing Context with id ${referenceContextId} not found` @@ -768,7 +769,7 @@ class BrowsingContextModule extends RootBiDiModule { browser.parentElement.clientHeight; return { - context: lazy.TabManager.getIdForBrowser(browser), + context: lazy.NavigableManager.getIdForBrowser(browser), }; } @@ -1893,7 +1894,7 @@ class BrowsingContextModule extends RootBiDiModule { return null; } - const context = lazy.TabManager.getBrowsingContextById(contextId); + const context = lazy.NavigableManager.getBrowsingContextById(contextId); if (context === null) { throw new lazy.error.NoSuchFrameError( `Browsing Context with id ${contextId} not found` @@ -2089,7 +2090,7 @@ class BrowsingContextModule extends RootBiDiModule { #onPromptClosed = async (eventName, data) => { if (this.#subscribedEvents.has("browsingContext.userPromptClosed")) { const { contentBrowser, detail } = data; - const navigableId = lazy.TabManager.getIdForBrowser(contentBrowser); + const navigableId = lazy.NavigableManager.getIdForBrowser(contentBrowser); if (navigableId === null) { return; @@ -2120,7 +2121,7 @@ class BrowsingContextModule extends RootBiDiModule { return; } - const navigableId = lazy.TabManager.getIdForBrowser(contentBrowser); + const navigableId = lazy.NavigableManager.getIdForBrowser(contentBrowser); const session = lazy.getWebDriverSessionById( this.messageHandler.sessionId @@ -2478,11 +2479,11 @@ export const getBrowsingContextInfo = (context, options = {}) => { const userContext = lazy.UserContextManager.getIdByBrowsingContext(context); const originalOpener = context.crossGroupOpener !== null - ? lazy.TabManager.getIdForBrowsingContext(context.crossGroupOpener) + ? lazy.NavigableManager.getIdForBrowsingContext(context.crossGroupOpener) : null; const contextInfo = { children, - context: lazy.TabManager.getIdForBrowsingContext(context), + context: lazy.NavigableManager.getIdForBrowsingContext(context), // TODO: Bug 1904641. If a browsing context was not tracked in TabManager, // because it was created and discarded before the WebDriver BiDi session was // started, we get undefined as id for this browsing context. @@ -2495,7 +2496,9 @@ export const getBrowsingContextInfo = (context, options = {}) => { if (includeParentId) { // Only emit the parent id for the top-most browsing context. - const parentId = lazy.TabManager.getIdForBrowsingContext(context.parent); + const parentId = lazy.NavigableManager.getIdForBrowsingContext( + context.parent + ); contextInfo.parent = parentId; } diff --git a/remote/webdriver-bidi/modules/root/emulation.sys.mjs b/remote/webdriver-bidi/modules/root/emulation.sys.mjs @@ -12,6 +12,7 @@ ChromeUtils.defineESModuleGetters(lazy, { "chrome://remote/content/shared/messagehandler/MessageHandler.sys.mjs", error: "chrome://remote/content/shared/webdriver/Errors.sys.mjs", Log: "chrome://remote/content/shared/Log.sys.mjs", + NavigableManager: "chrome://remote/content/shared/NavigableManager.sys.mjs", pprint: "chrome://remote/content/shared/Format.sys.mjs", SessionDataMethod: "chrome://remote/content/shared/messagehandler/sessiondata/SessionData.sys.mjs", @@ -1153,7 +1154,7 @@ class EmulationModule extends RootBiDiModule { try { context.customUserAgent = userAgent; } catch (e) { - const contextId = lazy.TabManager.getIdForBrowsingContext(context); + const contextId = lazy.NavigableManager.getIdForBrowsingContext(context); lazy.logger.warn( `Failed to override user agent for context with id: ${contextId} (${e.message})` @@ -1203,7 +1204,7 @@ class EmulationModule extends RootBiDiModule { } #getBrowsingContext(contextId) { - const context = lazy.TabManager.getBrowsingContextById(contextId); + const context = lazy.NavigableManager.getBrowsingContextById(contextId); if (context === null) { throw new lazy.error.NoSuchFrameError( `Browsing Context with id ${contextId} not found` diff --git a/remote/webdriver-bidi/modules/root/input.sys.mjs b/remote/webdriver-bidi/modules/root/input.sys.mjs @@ -11,8 +11,8 @@ ChromeUtils.defineESModuleGetters(lazy, { assert: "chrome://remote/content/shared/webdriver/Assert.sys.mjs", error: "chrome://remote/content/shared/webdriver/Errors.sys.mjs", event: "chrome://remote/content/shared/webdriver/Event.sys.mjs", + NavigableManager: "chrome://remote/content/shared/NavigableManager.sys.mjs", pprint: "chrome://remote/content/shared/Format.sys.mjs", - TabManager: "chrome://remote/content/shared/TabManager.sys.mjs", }); class InputModule extends RootBiDiModule { @@ -267,7 +267,7 @@ class InputModule extends RootBiDiModule { lazy.pprint`Expected "context" to be a string, got ${contextId}` ); - const context = lazy.TabManager.getBrowsingContextById(contextId); + const context = lazy.NavigableManager.getBrowsingContextById(contextId); if (!context) { throw new lazy.error.NoSuchFrameError( `Browsing context with id ${contextId} not found` @@ -313,7 +313,7 @@ class InputModule extends RootBiDiModule { lazy.pprint`Expected "context" to be a string, got ${contextId}` ); - const context = lazy.TabManager.getBrowsingContextById(contextId); + const context = lazy.NavigableManager.getBrowsingContextById(contextId); if (!context) { throw new lazy.error.NoSuchFrameError( `Browsing context with id ${contextId} not found` @@ -366,7 +366,7 @@ class InputModule extends RootBiDiModule { lazy.pprint`Expected "context" to be a string, got ${contextId}` ); - const context = lazy.TabManager.getBrowsingContextById(contextId); + const context = lazy.NavigableManager.getBrowsingContextById(contextId); if (!context) { throw new lazy.error.NoSuchFrameError( `Browsing context with id ${contextId} not found` diff --git a/remote/webdriver-bidi/modules/root/network.sys.mjs b/remote/webdriver-bidi/modules/root/network.sys.mjs @@ -16,6 +16,7 @@ ChromeUtils.defineESModuleGetters(lazy, { Log: "chrome://remote/content/shared/Log.sys.mjs", matchURLPattern: "chrome://remote/content/shared/webdriver/URLPattern.sys.mjs", + NavigableManager: "chrome://remote/content/shared/NavigableManager.sys.mjs", NetworkDecodedBodySizeMap: "chrome://remote/content/shared/NetworkDecodedBodySizeMap.sys.mjs", NetworkListener: @@ -26,7 +27,6 @@ ChromeUtils.defineESModuleGetters(lazy, { parseURLPattern: "chrome://remote/content/shared/webdriver/URLPattern.sys.mjs", pprint: "chrome://remote/content/shared/Format.sys.mjs", - TabManager: "chrome://remote/content/shared/TabManager.sys.mjs", truncate: "chrome://remote/content/shared/Format.sys.mjs", updateCacheBehavior: "chrome://remote/content/shared/NetworkCacheManager.sys.mjs", @@ -1881,7 +1881,7 @@ class NetworkModule extends RootBiDiModule { } #getBrowsingContext(contextId) { - const context = lazy.TabManager.getBrowsingContextById(contextId); + const context = lazy.NavigableManager.getBrowsingContextById(contextId); if (context === null) { throw new lazy.error.NoSuchFrameError( `Browsing Context with id ${contextId} not found` @@ -2124,7 +2124,8 @@ class NetworkModule extends RootBiDiModule { */ #matchCollectorForNavigable(collector, navigable) { if (collector.contexts.size) { - const navigableId = lazy.TabManager.getIdForBrowsingContext(navigable); + const navigableId = + lazy.NavigableManager.getIdForBrowsingContext(navigable); return collector.contexts.has(navigableId); } @@ -2199,7 +2200,7 @@ class NetworkModule extends RootBiDiModule { return; } - const browsingContext = lazy.TabManager.getBrowsingContextById( + const browsingContext = lazy.NavigableManager.getBrowsingContextById( request.contextId ); if (!browsingContext) { @@ -2294,7 +2295,7 @@ class NetworkModule extends RootBiDiModule { let isBlocked = false; try { - const browsingContext = lazy.TabManager.getBrowsingContextById( + const browsingContext = lazy.NavigableManager.getBrowsingContextById( request.contextId ); if (!browsingContext) { @@ -2367,7 +2368,7 @@ class NetworkModule extends RootBiDiModule { return; } - const browsingContext = lazy.TabManager.getBrowsingContextById( + const browsingContext = lazy.NavigableManager.getBrowsingContextById( request.contextId ); if (!browsingContext) { @@ -2425,7 +2426,7 @@ class NetworkModule extends RootBiDiModule { #onFetchError = (name, data) => { const { request } = data; - const browsingContext = lazy.TabManager.getBrowsingContextById( + const browsingContext = lazy.NavigableManager.getBrowsingContextById( request.contextId ); if (!browsingContext) { @@ -2467,7 +2468,7 @@ class NetworkModule extends RootBiDiModule { #onResponseEvent = async (name, data) => { const { request, response } = data; - const browsingContext = lazy.TabManager.getBrowsingContextById( + const browsingContext = lazy.NavigableManager.getBrowsingContextById( request.contextId ); if (!browsingContext) { @@ -2544,8 +2545,9 @@ class NetworkModule extends RootBiDiModule { if (request.contextId) { // Retrieve the top browsing context id for this network event. contextId = request.contextId; - const browsingContext = lazy.TabManager.getBrowsingContextById(contextId); - topContextId = lazy.TabManager.getIdForBrowsingContext( + const browsingContext = + lazy.NavigableManager.getBrowsingContextById(contextId); + topContextId = lazy.NavigableManager.getIdForBrowsingContext( browsingContext.top ); } diff --git a/remote/webdriver-bidi/modules/root/script.sys.mjs b/remote/webdriver-bidi/modules/root/script.sys.mjs @@ -12,6 +12,7 @@ ChromeUtils.defineESModuleGetters(lazy, { "chrome://remote/content/shared/messagehandler/MessageHandler.sys.mjs", error: "chrome://remote/content/shared/webdriver/Errors.sys.mjs", generateUUID: "chrome://remote/content/shared/UUID.sys.mjs", + NavigableManager: "chrome://remote/content/shared/NavigableManager.sys.mjs", OwnershipModel: "chrome://remote/content/webdriver-bidi/RemoteValue.sys.mjs", pprint: "chrome://remote/content/shared/Format.sys.mjs", processExtraData: @@ -21,7 +22,6 @@ ChromeUtils.defineESModuleGetters(lazy, { "chrome://remote/content/shared/messagehandler/sessiondata/SessionData.sys.mjs", setDefaultAndAssertSerializationOptions: "chrome://remote/content/webdriver-bidi/RemoteValue.sys.mjs", - TabManager: "chrome://remote/content/shared/TabManager.sys.mjs", UserContextManager: "chrome://remote/content/shared/UserContextManager.sys.mjs", WindowGlobalMessageHandler: @@ -856,7 +856,7 @@ class ScriptModule extends RootBiDiModule { } #getBrowsingContext(contextId) { - const context = lazy.TabManager.getBrowsingContextById(contextId); + const context = lazy.NavigableManager.getBrowsingContextById(contextId); if (context === null) { throw new lazy.error.NoSuchFrameError( `Browsing Context with id ${contextId} not found` @@ -912,7 +912,9 @@ class ScriptModule extends RootBiDiModule { .flat() .map(realm => { // Resolve browsing context to a TabManager id. - realm.context = lazy.TabManager.getIdForBrowsingContext(realm.context); + realm.context = lazy.NavigableManager.getIdForBrowsingContext( + realm.context + ); return realm; }) .filter(realm => realm.context !== null); @@ -920,7 +922,9 @@ class ScriptModule extends RootBiDiModule { #onRealmCreated = (eventName, { realmInfo }) => { // Resolve browsing context to a TabManager id. - const context = lazy.TabManager.getIdForBrowsingContext(realmInfo.context); + const context = lazy.NavigableManager.getIdForBrowsingContext( + realmInfo.context + ); const browsingContextId = realmInfo.context.id; // Do not emit the event, if the browsing context is gone. diff --git a/remote/webdriver-bidi/modules/root/session.sys.mjs b/remote/webdriver-bidi/modules/root/session.sys.mjs @@ -14,6 +14,7 @@ ChromeUtils.defineESModuleGetters(lazy, { generateUUID: "chrome://remote/content/shared/UUID.sys.mjs", getWebDriverSessionById: "chrome://remote/content/shared/webdriver/Session.sys.mjs", + NavigableManager: "chrome://remote/content/shared/NavigableManager.sys.mjs", pprint: "chrome://remote/content/shared/Format.sys.mjs", RootMessageHandler: "chrome://remote/content/shared/messagehandler/RootMessageHandler.sys.mjs", @@ -141,7 +142,7 @@ class SessionModule extends RootBiDiModule { for (const navigable of subscriptionNavigables) { topLevelTraversableContextIds.add( - lazy.TabManager.getIdForBrowsingContext(navigable) + lazy.NavigableManager.getIdForBrowsingContext(navigable) ); } } else if (inputUserContextIds.size !== 0) { @@ -162,7 +163,7 @@ class SessionModule extends RootBiDiModule { userContextIds.add(internalId); } } else { - for (const tab of lazy.TabManager.tabs) { + for (const tab of lazy.TabManager.allTabs) { subscriptionNavigables.add(tab); } } @@ -274,7 +275,8 @@ class SessionModule extends RootBiDiModule { id: userContextId, }; } else { - const traversable = lazy.TabManager.getBrowsingContextById(traversableId); + const traversable = + lazy.NavigableManager.getBrowsingContextById(traversableId); if (traversable === null) { return null; @@ -320,7 +322,7 @@ class SessionModule extends RootBiDiModule { const { topLevelTraversableIds } = subscription; if (this.#isSubscriptionGlobal(subscription)) { - for (const traversable of lazy.TabManager.tabs) { + for (const traversable of lazy.TabManager.allTabs) { result.add(traversable); } @@ -408,7 +410,7 @@ class SessionModule extends RootBiDiModule { } const traversableId = - lazy.TabManager.getIdForBrowsingContext(navigable); + lazy.NavigableManager.getIdForBrowsingContext(navigable); listeners.push( this.#createListenerToSubscribe({ eventName, @@ -479,7 +481,7 @@ class SessionModule extends RootBiDiModule { for (const traversableId of item.topLevelTraversableIds) { const traversable = - lazy.TabManager.getBrowsingContextById(traversableId); + lazy.NavigableManager.getBrowsingContextById(traversableId); // Do nothing if traversable doesn't exist anymore or // there is already a subscription to the associated user context. @@ -504,7 +506,8 @@ class SessionModule extends RootBiDiModule { #getListenersToUnsubscribeFromTraversable(eventName, traversableId) { // Do nothing if traversable is already closed or still has another subscription. - const traversable = lazy.TabManager.getBrowsingContextById(traversableId); + const traversable = + lazy.NavigableManager.getBrowsingContextById(traversableId); if ( traversable === null || this.#hasSubscriptionByAssociatedUserContext(eventName, traversable) || @@ -566,7 +569,7 @@ class SessionModule extends RootBiDiModule { return null; } - const navigable = lazy.TabManager.getBrowsingContextById(navigableId); + const navigable = lazy.NavigableManager.getBrowsingContextById(navigableId); if (!navigable) { throw new lazy.error.NoSuchFrameError( `Browsing context with id ${navigableId} not found` @@ -591,7 +594,8 @@ class SessionModule extends RootBiDiModule { const result = new Set(); for (const navigableId of navigableIds) { - const navigable = lazy.TabManager.getBrowsingContextById(navigableId); + const navigable = + lazy.NavigableManager.getBrowsingContextById(navigableId); if (navigable !== null) { result.add(navigable); @@ -635,7 +639,7 @@ class SessionModule extends RootBiDiModule { for (const navigable of topLevelTraversable) { topLevelTraversableContextIds.add( - lazy.TabManager.getIdForBrowsingContext(navigable) + lazy.NavigableManager.getIdForBrowsingContext(navigable) ); } } @@ -782,7 +786,7 @@ class SessionModule extends RootBiDiModule { for (const traversableId of topLevelTraversableIds) { const traversable = - lazy.TabManager.getBrowsingContextById(traversableId); + lazy.NavigableManager.getBrowsingContextById(traversableId); if (traversable === null) { continue; diff --git a/remote/webdriver-bidi/modules/root/storage.sys.mjs b/remote/webdriver-bidi/modules/root/storage.sys.mjs @@ -13,8 +13,8 @@ ChromeUtils.defineESModuleGetters(lazy, { deserializeBytesValue: "chrome://remote/content/webdriver-bidi/modules/root/network.sys.mjs", error: "chrome://remote/content/shared/webdriver/Errors.sys.mjs", + NavigableManager: "chrome://remote/content/shared/NavigableManager.sys.mjs", pprint: "chrome://remote/content/shared/Format.sys.mjs", - TabManager: "chrome://remote/content/shared/TabManager.sys.mjs", UserContextManager: "chrome://remote/content/shared/UserContextManager.sys.mjs", }); @@ -707,7 +707,7 @@ class StorageModule extends RootBiDiModule { * If the browsing context cannot be found. */ #getBrowsingContext(contextId) { - const context = lazy.TabManager.getBrowsingContextById(contextId); + const context = lazy.NavigableManager.getBrowsingContextById(contextId); if (context === null) { throw new lazy.error.NoSuchFrameError( `Browsing Context with id ${contextId} not found` diff --git a/remote/webdriver-bidi/modules/windowglobal-in-root/browsingContext.sys.mjs b/remote/webdriver-bidi/modules/windowglobal-in-root/browsingContext.sys.mjs @@ -9,6 +9,7 @@ const lazy = {}; ChromeUtils.defineESModuleGetters(lazy, { getBrowsingContextInfo: "chrome://remote/content/webdriver-bidi/modules/root/browsingContext.sys.mjs", + NavigableManager: "chrome://remote/content/shared/NavigableManager.sys.mjs", TabManager: "chrome://remote/content/shared/TabManager.sys.mjs", }); @@ -29,7 +30,7 @@ class BrowsingContextModule extends Module { // Resolve browsing context to a Navigable id. payload.context = - lazy.TabManager.getIdForBrowsingContext(browsingContext); + lazy.NavigableManager.getIdForBrowsingContext(browsingContext); if (name == "browsingContext.contextCreated") { payload = { diff --git a/remote/webdriver-bidi/modules/windowglobal-in-root/log.sys.mjs b/remote/webdriver-bidi/modules/windowglobal-in-root/log.sys.mjs @@ -7,6 +7,7 @@ import { Module } from "chrome://remote/content/shared/messagehandler/Module.sys const lazy = {}; ChromeUtils.defineESModuleGetters(lazy, { + NavigableManager: "chrome://remote/content/shared/NavigableManager.sys.mjs", processExtraData: "chrome://remote/content/webdriver-bidi/modules/Intercept.sys.mjs", TabManager: "chrome://remote/content/shared/TabManager.sys.mjs", @@ -25,7 +26,7 @@ class LogModule extends Module { // Resolve browsing context to a Navigable id. payload.source.context = - lazy.TabManager.getIdForBrowsingContext(browsingContext); + lazy.NavigableManager.getIdForBrowsingContext(browsingContext); payload = lazy.processExtraData(this.messageHandler.sessionId, payload); } diff --git a/remote/webdriver-bidi/modules/windowglobal-in-root/network.sys.mjs b/remote/webdriver-bidi/modules/windowglobal-in-root/network.sys.mjs @@ -7,6 +7,7 @@ import { Module } from "chrome://remote/content/shared/messagehandler/Module.sys const lazy = {}; ChromeUtils.defineESModuleGetters(lazy, { + NavigableManager: "chrome://remote/content/shared/NavigableManager.sys.mjs", RootMessageHandler: "chrome://remote/content/shared/messagehandler/RootMessageHandler.sys.mjs", TabManager: "chrome://remote/content/shared/TabManager.sys.mjs", @@ -39,7 +40,8 @@ class NetworkModule extends Module { } // Resolve browsing context to a Navigable id. - request.contextId = lazy.TabManager.getIdForBrowsingContext(context); + request.contextId = + lazy.NavigableManager.getIdForBrowsingContext(context); this.messageHandler.handleCommand({ moduleName: "network", diff --git a/remote/webdriver-bidi/modules/windowglobal-in-root/script.sys.mjs b/remote/webdriver-bidi/modules/windowglobal-in-root/script.sys.mjs @@ -7,6 +7,7 @@ import { Module } from "chrome://remote/content/shared/messagehandler/Module.sys const lazy = {}; ChromeUtils.defineESModuleGetters(lazy, { + NavigableManager: "chrome://remote/content/shared/NavigableManager.sys.mjs", processExtraData: "chrome://remote/content/webdriver-bidi/modules/Intercept.sys.mjs", TabManager: "chrome://remote/content/shared/TabManager.sys.mjs", @@ -25,7 +26,7 @@ class ScriptModule extends Module { // Resolve browsing context to a Navigable id. payload.source.context = - lazy.TabManager.getIdForBrowsingContext(browsingContext); + lazy.NavigableManager.getIdForBrowsingContext(browsingContext); payload = lazy.processExtraData(this.messageHandler.sessionId, payload); } diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_window_handles.py b/testing/marionette/harness/marionette_harness/tests/unit/test_window_handles.py @@ -119,7 +119,11 @@ class TestWindowHandles(ChromeHandlerMixin, WindowManagerMixin, MarionetteTestCa self.marionette.start_session() self.assert_window_handles() - self.assertEqual(window_handles, self.marionette.window_handles) + + # For a new session the window handles will have a new uuid as value + self.assertEqual(len(window_handles), len(self.marionette.window_handles)) + for item in self.marionette.window_handles: + self.assertNotIn(item, window_handles) self.marionette.switch_to_window(new_window) diff --git a/testing/web-platform/mozilla/tests/webdriver/bidi/browsing_context/create/reference_context.py b/testing/web-platform/mozilla/tests/webdriver/bidi/browsing_context/create/reference_context.py @@ -12,13 +12,16 @@ def assert_tab_order(session, expected_context_ids): with using_context(session, "chrome"): context_ids = session.execute_script( """ - const contextId = arguments[0]; + const { NavigableManager } = + ChromeUtils.importESModule("chrome://remote/content/shared/NavigableManager.sys.mjs"); const { TabManager } = ChromeUtils.importESModule("chrome://remote/content/shared/TabManager.sys.mjs"); - const browsingContext = TabManager.getBrowsingContextById(contextId); - const chromeWindow = browsingContext.embedderElement.ownerGlobal; + + const contextId = arguments[0]; + const browsingContext = NavigableManager.getBrowsingContextById(contextId); + const chromeWindow = browsingContext.top.embedderWindowGlobal.browsingContext.window; const tabBrowser = TabManager.getTabBrowser(chromeWindow); - return tabBrowser.browsers.map(browser => TabManager.getIdForBrowser(browser)); + return tabBrowser.browsers.map(browser => NavigableManager.getIdForBrowser(browser)); """, args=(expected_context_ids[0],), ) diff --git a/testing/webcompat/client.py b/testing/webcompat/client.py @@ -897,12 +897,13 @@ class Client: ChromeUtils.defineESModuleGetters(lazy, { EventPromise: "chrome://remote/content/shared/Sync.sys.mjs", modal: "chrome://remote/content/shared/Prompt.sys.mjs", + NavigableManager: "chrome://remote/content/shared/NavigableManager.sys.mjs", PromptListener: "chrome://remote/content/shared/listeners/PromptListener.sys.mjs", TabManager: "chrome://remote/content/shared/TabManager.sys.mjs", }); async function tryClosePrompt(contextId) { - const context = lazy.TabManager.getBrowsingContextById(contextId); + const context = lazy.NavigableManager.getBrowsingContextById(contextId); if (!context) { return; } @@ -960,7 +961,7 @@ class Client: promptListener.on("opened", async (eventName, data) => { const { contentBrowser, prompt } = data; const type = prompt.promptType; - const context = lazy.TabManager.getIdForBrowser(contentBrowser); + const context = lazy.NavigableManager.getIdForBrowser(contentBrowser); const message = await prompt.getText(); alerts.push({type, context, message}); tryClosePrompt(context);