tor-browser

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

commit bac2666086e275e4865fe3013b7eca60dae80387
parent 5eea86343d639895cb6d00b05270dae576f3bc4c
Author: Henrik Skupin <mail@hskupin.info>
Date:   Mon,  1 Dec 2025 20:43:59 +0000

Bug 2000801 - [marionette] Fix JSON serialization and deserialization of chrome windows. r=jdescottes

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

Diffstat:
Mremote/marionette/driver.sys.mjs | 51++++++++++++++++++++++++++++++++++++++++++++++++---
Mremote/marionette/json.sys.mjs | 44+++++++++++++++++++++++---------------------
Mremote/shared/WindowManager.sys.mjs | 63++++-----------------------------------------------------------
Mtesting/marionette/harness/marionette_harness/runner/mixins/window_manager.py | 6+++---
Mtesting/marionette/harness/marionette_harness/tests/unit/test_chrome_element_id.py | 12+++++++++++-
Mtesting/marionette/harness/marionette_harness/tests/unit/test_chrome_window_handles.py | 8++++++--
Mtesting/marionette/harness/marionette_harness/tests/unit/test_window_handles.py | 2--
7 files changed, 95 insertions(+), 91 deletions(-)

diff --git a/remote/marionette/driver.sys.mjs b/remote/marionette/driver.sys.mjs @@ -1415,7 +1415,9 @@ GeckoDriver.prototype.getWindowHandle = function () { */ GeckoDriver.prototype.getWindowHandles = function () { if (this.context == lazy.Context.Chrome) { - return lazy.windowManager.chromeWindowHandles.map(String); + return lazy.windowManager.windows.map(window => + lazy.windowManager.getIdForWindow(window) + ); } return lazy.TabManager.getBrowsers({ unloaded: true }).map(browser => @@ -1526,6 +1528,47 @@ GeckoDriver.prototype.setWindowRect = async function (cmd) { }; /** + * Find a specific window matching the provided window handle. + * + * @param {string} handle + * The unique handle of either a chrome window or a content browser, as + * returned by :js:func:`#getIdForBrowser` or :js:func:`#getIdForWindow`. + * + * @returns {object|null} + * A window properties object, or `null` if a window cannot be found. + *. @see :js:func:`WindowManager#getWindowProperties` + */ +GeckoDriver.prototype._findWindowByHandle = function (handle) { + for (const win of lazy.windowManager.windows) { + const chromeWindowId = lazy.NavigableManager.getIdForBrowsingContext( + win.browsingContext + ); + if (chromeWindowId == handle) { + return lazy.windowManager.getWindowProperties(win); + } + + // Otherwise check if the chrome window has a tab browser, and that it + // contains a tab with the wanted window handle. + const tabBrowser = lazy.TabManager.getTabBrowser(win); + if (tabBrowser && tabBrowser.tabs) { + for (let i = 0; i < tabBrowser.tabs.length; ++i) { + let contentBrowser = lazy.TabManager.getBrowserForTab( + tabBrowser.tabs[i] + ); + let contentWindowId = + lazy.NavigableManager.getIdForBrowser(contentBrowser); + + if (contentWindowId == handle) { + return lazy.windowManager.getWindowProperties(win, { tabIndex: i }); + } + } + } + } + + return null; +}; + +/** * Switch current top-level browsing context by name or server-assigned * ID. Searches for windows by name, then ID. Content windows take * precedence. @@ -1554,7 +1597,7 @@ GeckoDriver.prototype.switchToWindow = async function (cmd) { lazy.pprint`Expected "focus" to be a boolean, got ${focus}` ); - const found = lazy.windowManager.findWindowByHandle(handle); + const found = this._findWindowByHandle(handle); let selected = false; if (found) { @@ -2831,7 +2874,9 @@ GeckoDriver.prototype.closeChromeWindow = async function () { this.currentSession.chromeBrowsingContext = null; this.currentSession.contentBrowsingContext = null; - return lazy.windowManager.chromeWindowHandles.map(String); + return lazy.windowManager.windows.map(window => + lazy.NavigableManager.getIdForBrowsingContext(window.browsingContext) + ); }; /** Delete Marionette session. */ diff --git a/remote/marionette/json.sys.mjs b/remote/marionette/json.sys.mjs @@ -178,16 +178,19 @@ json.clone = function (value, nodeCache) { } if (isWindow) { - // Convert window instances to WebReference references. let reference; + // Convert window instances to serialized WebWindow or WebFrame references. + // Because the NavigableManager is only available in the parent process, + // we need to pass the actual id of the browsing context. if (value.browsingContext.parent == null) { - reference = new WebWindow(value.browsingContext.browserId.toString()); - hasSerializedWindows = true; + reference = new WebWindow(value.browsingContext.id.toString()); } else { reference = new WebFrame(value.browsingContext.id.toString()); } + hasSerializedWindows = true; + return reference.toJSON(); } @@ -268,29 +271,27 @@ json.deserialize = function (value, nodeCache, browsingContext) { } if (webRef instanceof lazy.WebFrame) { - const browsingContext = BrowsingContext.get(webRef.uuid); + const frameContext = BrowsingContext.get(webRef.uuid); - if (browsingContext === null || browsingContext.parent === null) { + if (frameContext === null || frameContext.parent === null) { throw new lazy.error.NoSuchFrameError( `Unable to locate frame with id: ${webRef.uuid}` ); } - return browsingContext.window; + return frameContext.window; } if (webRef instanceof lazy.WebWindow) { - const browsingContext = BrowsingContext.getCurrentTopByBrowserId( - webRef.uuid - ); + const windowContext = BrowsingContext.get(webRef.uuid); - if (browsingContext === null) { + if (windowContext === null || windowContext.parent !== null) { throw new lazy.error.NoSuchWindowError( `Unable to locate window with id: ${webRef.uuid}` ); } - return browsingContext.window; + return windowContext.window; } } @@ -302,7 +303,7 @@ json.deserialize = function (value, nodeCache, browsingContext) { }; /** - * Convert unique navigable ids to internal browser ids. + * Convert unique navigable ids for windows and frames to browsing context ids. * * @param {object} serializedData * The data to process. @@ -315,10 +316,13 @@ json.mapFromNavigableIds = function (serializedData) { if (lazy.WebReference.isReference(data)) { const webRef = lazy.WebReference.fromJSON(data); - if (webRef instanceof lazy.WebWindow) { - const browser = lazy.NavigableManager.getBrowserById(webRef.uuid); - if (browser) { - webRef.uuid = browser?.browserId.toString(); + if (webRef instanceof lazy.WebFrame || webRef instanceof lazy.WebWindow) { + const context = lazy.NavigableManager.getBrowsingContextById( + webRef.uuid + ); + + if (context) { + webRef.uuid = context.id.toString(); data = webRef.toJSON(); } } @@ -335,7 +339,7 @@ json.mapFromNavigableIds = function (serializedData) { }; /** - * Convert browser ids to unique navigable ids. + * Convert browsing context ids for windows and frames to unique navigable ids. * * @param {object} serializedData * The data to process. @@ -347,10 +351,8 @@ json.mapToNavigableIds = function (serializedData) { function _processData(data) { if (lazy.WebReference.isReference(data)) { const webRef = lazy.WebReference.fromJSON(data); - if (webRef instanceof lazy.WebWindow) { - const browsingContext = BrowsingContext.getCurrentTopByBrowserId( - webRef.uuid - ); + if (webRef instanceof lazy.WebWindow || webRef instanceof lazy.WebFrame) { + const browsingContext = BrowsingContext.get(webRef.uuid); webRef.uuid = lazy.NavigableManager.getIdForBrowsingContext(browsingContext); diff --git a/remote/shared/WindowManager.sys.mjs b/remote/shared/WindowManager.sys.mjs @@ -14,7 +14,6 @@ ChromeUtils.defineESModuleGetters(lazy, { DebounceCallback: "chrome://remote/content/marionette/sync.sys.mjs", error: "chrome://remote/content/shared/webdriver/Errors.sys.mjs", 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", @@ -40,9 +39,6 @@ class WindowManager { #contextListener; constructor() { - // Maps ChromeWindow to uuid: WeakMap.<Object, string> - this._chromeWindowHandles = new WeakMap(); - /** * Keep track of the client window for any registered contexts. When the * contextDestroyed event is fired, the context is already destroyed so @@ -55,16 +51,6 @@ class WindowManager { this.#contextListener.startListening(); } - get chromeWindowHandles() { - const chromeWindowHandles = []; - - for (const win of this.windows) { - chromeWindowHandles.push(this.getIdForWindow(win)); - } - - return chromeWindowHandles; - } - /** * Retrieve all the open windows. * @@ -85,45 +71,6 @@ class WindowManager { } /** - * Find a specific window matching the provided window handle. - * - * @param {string} handle - * The unique handle of either a chrome window or a content browser, as - * returned by :js:func:`#getIdForBrowser` or :js:func:`#getIdForWindow`. - * - * @returns {object} A window properties object, - * @see :js:func:`GeckoDriver#getWindowProperties` - */ - findWindowByHandle(handle) { - for (const win of this.windows) { - // In case the wanted window is a chrome window, we are done. - const chromeWindowId = this.getIdForWindow(win); - if (chromeWindowId == handle) { - return this.getWindowProperties(win); - } - - // Otherwise check if the chrome window has a tab browser, and that it - // contains a tab with the wanted window handle. - const tabBrowser = lazy.TabManager.getTabBrowser(win); - if (tabBrowser && tabBrowser.tabs) { - for (let i = 0; i < tabBrowser.tabs.length; ++i) { - let contentBrowser = lazy.TabManager.getBrowserForTab( - tabBrowser.tabs[i] - ); - let contentWindowId = - lazy.NavigableManager.getIdForBrowser(contentBrowser); - - if (contentWindowId == handle) { - return this.getWindowProperties(win, { tabIndex: i }); - } - } - } - } - - return null; - } - - /** * A set of properties describing a window and that should allow to uniquely * identify it. The described window can either be a Chrome Window or a * Content Window. @@ -180,17 +127,15 @@ class WindowManager { /** * Retrieves an id for the given chrome window. The id is a dynamically - * generated uuid associated with the window object. + * generated uuid by the NavigableManager and associated with the + * top-level browsing context of that chrome window. * * @param {window} win - * The window object for which we want to retrieve the id. + * The chrome window for which we want to retrieve the id. * @returns {string} The unique id for this chrome window. */ getIdForWindow(win) { - if (!this._chromeWindowHandles.has(win)) { - this._chromeWindowHandles.set(win, lazy.generateUUID()); - } - return this._chromeWindowHandles.get(win); + return lazy.NavigableManager.getIdForBrowsingContext(win.browsingContext); } /** diff --git a/testing/marionette/harness/marionette_harness/runner/mixins/window_manager.py b/testing/marionette/harness/marionette_harness/runner/mixins/window_manager.py @@ -91,10 +91,10 @@ class WindowManagerMixin(object): with self.marionette.using_context("chrome"): return self.marionette.execute_script( """ - const { windowManager } = ChromeUtils.importESModule( - "chrome://remote/content/shared/WindowManager.sys.mjs" + const { NavigableManager } = ChromeUtils.importESModule( + "chrome://remote/content/shared/NavigableManager.sys.mjs" ); - const win = windowManager.findWindowByHandle(arguments[0]).win; + const win = NavigableManager.getBrowsingContextById(arguments[0]).window; return win.document.readyState == "complete"; """, script_args=[handle], diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_chrome_element_id.py b/testing/marionette/harness/marionette_harness/tests/unit/test_chrome_element_id.py @@ -57,7 +57,17 @@ class TestElementIDChrome(ChromeHandlerMixin, WindowManagerMixin, MarionetteTest self.marionette.start_session() self.marionette.set_context("chrome") - self.marionette.switch_to_window(win) + + # By default the first browser window will be selected again. + # Find and select the custom chrome window. + chrome_window_handle = self.marionette.current_chrome_window_handle + chrome_windows = [ + win + for win in self.marionette.chrome_window_handles + if win != chrome_window_handle + ] + + self.marionette.switch_to_window(chrome_windows[0]) found_el_new = self.marionette.find_element(By.ID, "textInput") self.assertNotEqual(found_el_new.id, found_el.id) diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_chrome_window_handles.py b/testing/marionette/harness/marionette_harness/tests/unit/test_chrome_window_handles.py @@ -151,9 +151,13 @@ class TestWindowHandles(ChromeHandlerMixin, WindowManagerMixin, MarionetteTestCa self.marionette.start_session() self.assert_window_handles() - self.assertEqual(chrome_window_handles, self.marionette.chrome_window_handles) - self.marionette.switch_to_window(new_window) + # For a new session the window handles will have a new uuid as value + self.assertEqual( + len(chrome_window_handles), len(self.marionette.chrome_window_handles) + ) + for item in self.marionette.chrome_window_handles: + self.assertNotIn(item, chrome_window_handles) def test_window_handles_after_opening_new_tab(self): with self.marionette.using_context("content"): 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 @@ -125,8 +125,6 @@ class TestWindowHandles(ChromeHandlerMixin, WindowManagerMixin, MarionetteTestCa for item in self.marionette.window_handles: self.assertNotIn(item, window_handles) - self.marionette.switch_to_window(new_window) - def test_window_handles_include_unloaded_tabs(self): new_tab = self.open_tab() self.assert_window_handles()