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:
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()