commit 6e586279cd093567839f80ee5ca2549f7076b09e parent bac2666086e275e4865fe3013b7eca60dae80387 Author: Henrik Skupin <mail@hskupin.info> Date: Mon, 1 Dec 2025 20:43:59 +0000 Bug 2000801 - [wdspec] Add Mozilla-specific tests for chrome window handles. r=frontend-codestyle-reviewers,jdescottes,mossop Differential Revision: https://phabricator.services.mozilla.com/D273541 Diffstat:
20 files changed, 490 insertions(+), 11 deletions(-)
diff --git a/.stylelintignore b/.stylelintignore @@ -102,5 +102,7 @@ toolkit/components/pdfjs/content/web/viewer.css toolkit/components/pdfjs/content/web/viewer-geckoview.css build/pgo/blueprint/**/*.css -# Ignore web-platform tests as they are not necessarily under our control. +# Ignore web-platform tests as they are not necessarily +# under our control or we don't want to modify at this point: +testing/web-platform/mozilla/ testing/web-platform/tests/ diff --git a/testing/web-platform/mozilla/meta/webdriver/classic/execute_async_script/chrome.py.ini b/testing/web-platform/mozilla/meta/webdriver/classic/execute_async_script/chrome.py.ini @@ -0,0 +1,3 @@ +[chrome.py] + disabled: + if os == "android": https://bugzilla.mozilla.org/show_bug.cgi?id=1762066 diff --git a/testing/web-platform/mozilla/meta/webdriver/classic/execute_script/chrome.py.ini b/testing/web-platform/mozilla/meta/webdriver/classic/execute_script/chrome.py.ini @@ -0,0 +1,3 @@ +[chrome.py] + disabled: + if os == "android": https://bugzilla.mozilla.org/show_bug.cgi?id=1762066 diff --git a/testing/web-platform/mozilla/tests/webdriver/classic/execute_async_script/chrome.py b/testing/web-platform/mozilla/tests/webdriver/classic/execute_async_script/chrome.py @@ -0,0 +1,35 @@ +import pytest +from support.context import using_context +from tests.classic.execute_async_script import execute_async_script +from tests.support.asserts import assert_success +from webdriver.client import WebFrame, WebWindow + + +@pytest.mark.parametrize( + "expression, expected_type", + [ + ("window.frames[0]", WebFrame), + ("window", WebWindow), + ], + ids=["frame", "window"], +) +@pytest.mark.allow_system_access +def test_web_reference( + session, expression, default_chrome_handler, new_chrome_window, expected_type +): + chrome_url = f"{default_chrome_handler}test.xhtml" + new_window = new_chrome_window(chrome_url) + + with using_context(session, "chrome"): + session.window_handle = new_window.id + assert session.url == chrome_url + + result = execute_async_script(session, f"arguments[0]({expression})") + reference = assert_success(result) + + assert isinstance(reference, expected_type) + + if isinstance(reference, WebWindow): + assert reference.id in session.handles + else: + assert reference.id not in session.handles diff --git a/testing/web-platform/mozilla/tests/webdriver/classic/execute_async_script/execute_async.py b/testing/web-platform/mozilla/tests/webdriver/classic/execute_async_script/execute_async.py @@ -1,19 +1,10 @@ import pytest +from tests.classic.execute_async_script import execute_async_script from tests.support.asserts import assert_success from tests.support.sync import Poll from webdriver.error import NoSuchAlertException -def execute_async_script(session, script, args=None): - if args is None: - args = [] - body = {"script": script, "args": args} - - return session.transport.send( - "POST", "/session/{session_id}/execute/async".format(**vars(session)), body - ) - - @pytest.mark.parametrize("dialog_type", ["alert", "confirm", "prompt"]) def test_no_abort_by_user_prompt_in_other_tab(session, inline, dialog_type): original_handle = session.window_handle diff --git a/testing/web-platform/mozilla/tests/webdriver/classic/execute_script/__init__.py b/testing/web-platform/mozilla/tests/webdriver/classic/execute_script/__init__.py diff --git a/testing/web-platform/mozilla/tests/webdriver/classic/execute_script/chrome.py b/testing/web-platform/mozilla/tests/webdriver/classic/execute_script/chrome.py @@ -0,0 +1,35 @@ +import pytest +from support.context import using_context +from tests.classic.execute_script import execute_script +from tests.support.asserts import assert_success +from webdriver.client import WebFrame, WebWindow + + +@pytest.mark.parametrize( + "expression, expected_type", + [ + ("window.frames[0]", WebFrame), + ("window", WebWindow), + ], + ids=["frame", "window"], +) +@pytest.mark.allow_system_access +def test_web_reference( + session, expression, default_chrome_handler, new_chrome_window, expected_type +): + chrome_url = f"{default_chrome_handler}test.xhtml" + new_window = new_chrome_window(chrome_url) + + with using_context(session, "chrome"): + session.window_handle = new_window.id + assert session.url == chrome_url + + result = execute_script(session, f"return {expression}") + reference = assert_success(result) + + assert isinstance(reference, expected_type) + + if isinstance(reference, WebWindow): + assert reference.id in session.handles + else: + assert reference.id not in session.handles diff --git a/testing/web-platform/mozilla/tests/webdriver/classic/switch_to_window/__init__.py b/testing/web-platform/mozilla/tests/webdriver/classic/switch_to_window/__init__.py @@ -0,0 +1,6 @@ +def switch_to_window(session, handle): + return session.transport.send( + "POST", + "session/{session_id}/window".format(**vars(session)), + {"handle": handle}, + ) diff --git a/testing/web-platform/mozilla/tests/webdriver/classic/switch_to_window/chrome.py b/testing/web-platform/mozilla/tests/webdriver/classic/switch_to_window/chrome.py @@ -0,0 +1,31 @@ +import pytest +from support.context import using_context +from tests.support.asserts import assert_error, assert_success + +from . import switch_to_window + + +@pytest.mark.allow_system_access +def test_no_such_window(session): + with using_context(session, "chrome"): + response = switch_to_window(session, "foo") + assert_error(response, "no such window") + + +@pytest.mark.allow_system_access +def test_chrome_window(session): + session.new_window(type_hint="window") + + with using_context(session, "chrome"): + current_handle = session.window_handle + chrome_handles = session.handles + + chrome_handles.remove(current_handle) + + assert len(chrome_handles) + + response = switch_to_window(session, chrome_handles[0]) + assert_success(response) + + assert session.window_handle == chrome_handles[0] + assert session.window_handle != current_handle diff --git a/testing/web-platform/mozilla/tests/webdriver/support/chrome-assets/chrome.manifest b/testing/web-platform/mozilla/tests/webdriver/support/chrome-assets/chrome.manifest diff --git a/testing/web-platform/mozilla/tests/webdriver/support/chrome-assets/chrome/style.css b/testing/web-platform/mozilla/tests/webdriver/support/chrome-assets/chrome/style.css @@ -0,0 +1,13 @@ +.flex-column { + display: flex; + flex-direction: column; +} + +.dialog-box { + min-height: 500px; + min-width: 300px; +} + +.input-moz-box-flex { + -moz-box-flex: 1; +} diff --git a/testing/web-platform/mozilla/tests/webdriver/support/chrome-assets/chrome/test.xhtml b/testing/web-platform/mozilla/tests/webdriver/support/chrome-assets/chrome/test.xhtml @@ -0,0 +1,42 @@ +<?xml version="1.0" encoding="UTF-8"?> +<?csp default-src chrome:; ?> + +<!-- 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/. --> + +<!-- Test file for a XUL window by using a XHTML document instead. --> + +<!DOCTYPE html> + +<html + xmlns="http://www.w3.org/1999/xhtml" + id="remote-window" + windowtype="remote:xhtml" +> + <head> + <title>Test Window</title> + <link rel="stylesheet" href="chrome://global/skin/global.css" /> + <link rel="stylesheet" href="chrome://marionette-chrome/content/style.css" /> + </head> + + <body> + <div id="types" class="flex-column"> + <input id="textInput" size="6" value="test" label="input" /> + <input type="checkbox" id="testBox" label="box" /> + <button id="button" class="foo" label="button" /> + <button id="options-button" label="options" /> + </div> + + <div id="frames" class="flex-column"> + <iframe + id="iframe" + src="chrome://marionette-chrome/content/test_iframe.xhtml" + /> + <iframe + id="iframe-nested" + src="chrome://marionette-chrome/content/test_nested_iframe.xhtml" + /> + </div> + </body> +</html> diff --git a/testing/web-platform/mozilla/tests/webdriver/support/chrome-assets/chrome/test_dialog.dtd b/testing/web-platform/mozilla/tests/webdriver/support/chrome-assets/chrome/test_dialog.dtd @@ -0,0 +1,7 @@ +<!-- 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/. --> + +<!ENTITY testDialog.title "Test Dialog"> + +<!ENTITY settings.label "Settings"> diff --git a/testing/web-platform/mozilla/tests/webdriver/support/chrome-assets/chrome/test_dialog.properties b/testing/web-platform/mozilla/tests/webdriver/support/chrome-assets/chrome/test_dialog.properties @@ -0,0 +1,7 @@ +# 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/. + +testDialog.title=Test Dialog + +settings.label=Settings diff --git a/testing/web-platform/mozilla/tests/webdriver/support/chrome-assets/chrome/test_dialog.xhtml b/testing/web-platform/mozilla/tests/webdriver/support/chrome-assets/chrome/test_dialog.xhtml @@ -0,0 +1,43 @@ +<?xml version="1.0"?> +<?csp default-src chrome:; ?> + +<!-- 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/. --> + +<!-- Test file for a XUL dialog. --> + +<!DOCTYPE testdialog [ <!ENTITY % dialogDTD SYSTEM "chrome://marionette-chrome/content/test_dialog.dtd"> +%dialogDTD; ]> + +<window + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + xmlns:html="http://www.w3.org/1999/xhtml" + title="&testDialog.title;" +> + <html:link rel="stylesheet" href="chrome://global/skin/global.css" /> + <html:link rel="stylesheet" href="chrome://marionette-chrome/content/style.css" /> + + <dialog id="testDialog" buttons="accept,cancel"> + <vbox flex="1" class="dialog-box"> + <label>&settings.label;</label> + <separator class="thin" /> + <richlistbox id="test-list" flex="1"> + <richlistitem id="item-choose" orient="horizontal" selected="true"> + <label id="choose-label" value="First Entry" flex="1" /> + <button id="choose-button" label="Choose..." /> + </richlistitem> + </richlistbox> + <separator class="thin" /> + <checkbox id="check-box" label="Test Mode 2" /> + <hbox align="center"> + <label id="text-box-label" control="text-box">Name:</label> + <input + xmlns="http://www.w3.org/1999/xhtml" + id="text-box" + class="input-moz-box-flex" + /> + </hbox> + </vbox> + </dialog> +</window> diff --git a/testing/web-platform/mozilla/tests/webdriver/support/chrome-assets/chrome/test_iframe.xhtml b/testing/web-platform/mozilla/tests/webdriver/support/chrome-assets/chrome/test_iframe.xhtml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="UTF-8"?> +<?csp default-src 'none' ?> + +<!-- 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/. --> + +<!DOCTYPE html> + +<html + xmlns="http://www.w3.org/1999/xhtml" + id="remote-test-iframe" + windowtype="remote:test-xhtml" +> + <head> + <title>iframe</title> + <link rel="stylesheet" href="chrome://global/skin/global.css" /> + <link rel="stylesheet" href="chrome://marionette-chrome/content/style.css" /> + </head> + + <body> + <div id="types" class="flex-column"> + <input id="textInput" size="6" value="test" label="input" /> + <input type="checkbox" id="testBox" label="box" /> + </div> + </body> +</html> diff --git a/testing/web-platform/mozilla/tests/webdriver/support/chrome-assets/chrome/test_nested_iframe.xhtml b/testing/web-platform/mozilla/tests/webdriver/support/chrome-assets/chrome/test_nested_iframe.xhtml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="UTF-8"?> +<?csp default-src chrome:; ?> + +<!-- 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/. --> + +<!DOCTYPE html> + +<html + xmlns="http://www.w3.org/1999/xhtml" + id="remote-test-nested-iframe" + windowtype="remote:test-xhtml" +> + <head> + <title>nested iframe</title> + <link rel="stylesheet" href="chrome://global/skin/global.css" /> + </head> + + <body> + <iframe + id="iframe" + src="chrome://marionette-chrome/content/test_iframe.xhtml" + /> + </body> +</html> diff --git a/testing/web-platform/mozilla/tests/webdriver/support/chrome-assets/chrome/test_xul.xhtml b/testing/web-platform/mozilla/tests/webdriver/support/chrome-assets/chrome/test_xul.xhtml @@ -0,0 +1,56 @@ +<?xml version="1.0"?> +<?csp default-src chrome:; ?> + +<!-- 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/. --> + +<!-- Test file for a XUL window. --> + +<window + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + xmlns:html="http://www.w3.org/1999/xhtml" + id="remote-window" + title="Test Window" + windowtype="remote:xul" +> + <linkset> + <html:link rel="stylesheet" href="chrome://global/skin/global.css" /> + </linkset> + + <popupset id="options-popupset"> + <menupopup id="options-menupopup" position="before_end"> + <menuitem id="option-enabled" type="checkbox" label="enabled" /> + <menuitem + id="option-hidden" + type="checkbox" + label="hidden" + hidden="true" + /> + <menuitem + id="option-disabled" + type="checkbox" + label="disabled" + disabled="true" + /> + </menupopup> + </popupset> + + <vbox id="types"> + <html:input id="textInput" size="6" value="test" label="input" /> + <checkbox id="testBox" label="box" /> + <button id="button" class="foo" label="button" /> + <button id="options-button" popup="options-menupopup" label="options" /> + </vbox> + + <vbox id="frames"> + <html:iframe + id="iframe" + src="chrome://marionette-chrome/content/test_iframe.xhtml" + /> + <html:iframe + id="iframe-nested" + src="chrome://marionette-chrome/content/test_nested_iframe.xhtml" + /> + </vbox> +</window> diff --git a/testing/web-platform/mozilla/tests/webdriver/support/chrome_handler.py b/testing/web-platform/mozilla/tests/webdriver/support/chrome_handler.py @@ -0,0 +1,65 @@ +import contextlib + +from support.context import using_context + + +def register_chrome_handler(session, manifest_path, entries): + with using_context(session, "chrome"): + return session.execute_script( + """ + const { FileUtils } = ChromeUtils.importESModule( + "resource://gre/modules/FileUtils.sys.mjs" + ); + + const [manifestPath, entries] = arguments; + + const manifest = new FileUtils.File(manifestPath); + const rootURI = Services.io.newFileURI(manifest.parent); + const manifestURI = Services.io.newURI(manifest.leafName, null, rootURI); + + const handle = Cc["@mozilla.org/addons/addon-manager-startup;1"] + .getService(Ci.amIAddonManagerStartup) + .registerChrome(manifestURI, entries); + const id = Services.uuid.generateUUID().toString().slice(1, -1); + + if (globalThis.chromeProtocolHandles) { + globalThis.chromeProtocolHandles.set(id, handle); + } + + return id; + + """, + args=[manifest_path, entries], + ) + + +def unregister_chrome_handler(session, id): + with using_context(session, "chrome"): + return session.execute_script( + """ + const [id] = arguments; + + if (globalThis.chromeProtocolHandles) { + if (!globalThis.chromeProtocolHandles.has(id)) { + throw new Error( + `Id ${id} is not a known chrome protocol handler` + ); + } + + const handle = globalThis.chromeProtocolHandles.get(id); + globalThis.chromeProtocolHandles.delete(id); + handle.destruct(); + } + """, + args=[id], + ) + + +@contextlib.contextmanager +def using_chrome_handler(session, manifest_path, entries): + id = register_chrome_handler(session, manifest_path, entries) + + try: + yield + finally: + unregister_chrome_handler(session, id) diff --git a/testing/web-platform/mozilla/tests/webdriver/support/fixtures.py b/testing/web-platform/mozilla/tests/webdriver/support/fixtures.py @@ -1,7 +1,11 @@ +import os + import pytest import pytest_asyncio from tests.support.helpers import deep_update +from .chrome_handler import using_chrome_handler +from .context import using_context from .helpers import ( Browser, Geckodriver, @@ -138,10 +142,93 @@ def default_capabilities(request): @pytest.fixture +def default_chrome_handler(current_session): + manifest_path = os.path.join( + os.path.abspath(os.path.dirname(__file__)), "chrome-assets", "chrome.manifest" + ) + entries = [["content", "marionette-chrome", "chrome/"]] + + with using_chrome_handler(current_session, manifest_path, entries): + yield "chrome://marionette-chrome/content/" + + +@pytest.fixture def default_preferences(profile_folder): return read_user_preferences(profile_folder) +@pytest.fixture +def new_chrome_window(current_session): + opened_chrome_windows = [] + + def _new_chrome_window(url, focus=True): + # Bug 1944570: Replace with BiDi once scripts can be evaluated + # in the parent process. + with using_context(current_session, "chrome"): + new_window = current_session.execute_async_script( + """ + const { NavigableManager } = ChromeUtils.importESModule( + "chrome://remote/content/shared/NavigableManager.sys.mjs" + ); + + let [url, focus, resolve] = arguments; + + function waitForEvent(target, type, args) { + return new Promise(resolve => { + let params = Object.assign({once: true}, args); + target.addEventListener(type, event => { + dump(`** Received DOM event ${event.type} for ${event.target}\n`); + resolve(); + }, params); + }); + } + + function waitForFocus(win) { + return Promise.all([ + waitForEvent(win, "activate"), + waitForEvent(win, "focus", {capture: true}), + ]); + } + + (async function() { + // Open a window, wait for it to receive focus + let win = window.openDialog(url, null, "chrome,centerscreen"); + let focused = waitForFocus(win); + + win.focus(); + await focused; + + // The new window shouldn't get focused. As such set the + // focus back to the opening window. + if (!focus && Services.focus.activeWindow != window) { + let focused = waitForFocus(window); + window.focus(); + await focused; + } + + resolve(win); + })(); + """, + args=[url, focus], + ) + + # Append opened chrome window to automatic closing on teardown + opened_chrome_windows.append(new_window) + return new_window + + yield _new_chrome_window + + with using_context(current_session, "chrome"): + for win in opened_chrome_windows: + try: + current_session.window_handle = win.id + current_session.execute_script("arguments[0].close()", args=[win]) + except Exception: + pass + + current_session.window_handle = current_session.handles[0] + + @pytest.fixture(name="create_custom_profile") def fixture_create_custom_profile(default_preferences, profile_folder): profile = None