commit 50c4de911c377283c861efd035067bd5e313e6bc
parent 7a607605563fcfb274fc87e56ff933d9308a601b
Author: Henrik Skupin <mail@hskupin.info>
Date: Tue, 16 Dec 2025 12:03:47 +0000
Bug 1851788 - [webdriver-bidi] Enhance "browsingContext.getTree" command for chrome scope when root parameter is set. r=Sasha,jdescottes
Differential Revision: https://phabricator.services.mozilla.com/D275812
Diffstat:
8 files changed, 229 insertions(+), 53 deletions(-)
diff --git a/remote/webdriver-bidi/modules/RootBiDiModule.sys.mjs b/remote/webdriver-bidi/modules/RootBiDiModule.sys.mjs
@@ -7,9 +7,8 @@ import { Module } from "chrome://remote/content/shared/messagehandler/Module.sys
const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
+ assert: "chrome://remote/content/shared/webdriver/Assert.sys.mjs",
error: "chrome://remote/content/shared/webdriver/Errors.sys.mjs",
- MozContextScope:
- "chrome://remote/content/webdriver-bidi/modules/root/browsingContext.sys.mjs",
NavigableManager: "chrome://remote/content/shared/NavigableManager.sys.mjs",
WindowGlobalMessageHandler:
"chrome://remote/content/shared/messagehandler/WindowGlobalMessageHandler.sys.mjs",
@@ -47,8 +46,9 @@ export class RootBiDiModule extends Module {
* @param {string} navigableId
* Unique id of the browsing context.
* @param {object=} options
- * @param {MozContextScope=} options.scope
- * Scope of the browsing context. Defaults to "content".
+ * @param {boolean=} options.supportsChromeScope
+ * If set to `true` chrome browsing contexts are supported
+ * for the BiDi command. Defaults to `false`.
*
* @returns {BrowsingContext|null}
* The browsing context, or null if `navigableId` is null.
@@ -57,7 +57,7 @@ export class RootBiDiModule extends Module {
* If the browsing context cannot be found.
*/
_getNavigable(navigableId, options = {}) {
- const { scope = lazy.MozContextScope.CONTENT } = options;
+ const { supportsChromeScope = false } = options;
if (navigableId === null) {
// The WebDriver BiDi specification expects `null` to be
@@ -65,11 +65,16 @@ export class RootBiDiModule extends Module {
return null;
}
- let context = lazy.NavigableManager.getBrowsingContextById(navigableId);
+ const context = lazy.NavigableManager.getBrowsingContextById(navigableId);
- // Only allow to retrieve contexts for chrome windows if explicitly allowed.
- if (context && !context.isContent && scope != lazy.MozContextScope.CHROME) {
- context = null;
+ if (context && !context.isContent) {
+ lazy.assert.hasSystemAccess();
+
+ if (!supportsChromeScope) {
+ throw new lazy.error.UnsupportedOperationError(
+ "The command does not support browsing contexts in privileged (chrome) scope"
+ );
+ }
}
if (context === null) {
diff --git a/remote/webdriver-bidi/modules/root/browsingContext.sys.mjs b/remote/webdriver-bidi/modules/root/browsingContext.sys.mjs
@@ -842,7 +842,8 @@ class BrowsingContextModule extends RootBiDiModule {
* @param {string=} options.root
* Id of the root browsing context.
* @param {MozContextScope=} options."moz:scope"
- * The scope to retrieve browsing contexts from.
+ * The scope from which browsing contexts are retrieved. This
+ * parameter cannot be used when a root browsing context is specified.
*
* @returns {BrowsingContextGetTreeResult}
* Tree of browsing context information.
@@ -853,7 +854,7 @@ class BrowsingContextModule extends RootBiDiModule {
const {
maxDepth = null,
root: rootId = null,
- "moz:scope": scope = MozContextScope.CONTENT,
+ "moz:scope": scope = null,
} = options;
if (maxDepth !== null) {
@@ -863,16 +864,18 @@ class BrowsingContextModule extends RootBiDiModule {
);
}
- const contextScopes = Object.values(MozContextScope);
- lazy.assert.that(
- _scope => contextScopes.includes(_scope),
- `Expected "moz:scope" to be one of ${contextScopes}, ` +
- lazy.pprint`got ${scope}`
- )(scope);
-
- if (scope != MozContextScope.CONTENT) {
- // By default only content browsing contexts are allowed.
- lazy.assert.hasSystemAccess();
+ if (scope !== null) {
+ const contextScopes = Object.values(MozContextScope);
+ lazy.assert.that(
+ _scope => contextScopes.includes(_scope),
+ `Expected "moz:scope" to be one of ${contextScopes}, ` +
+ lazy.pprint`got ${scope}`
+ )(scope);
+
+ if (scope != MozContextScope.CONTENT) {
+ // By default only content browsing contexts are allowed.
+ lazy.assert.hasSystemAccess();
+ }
}
let contexts;
@@ -884,7 +887,15 @@ class BrowsingContextModule extends RootBiDiModule {
lazy.pprint`Expected "root" to be a string, got ${rootId}`
);
- contexts = [this._getNavigable(rootId, { scope })];
+ if (scope) {
+ // At the moment we only allow to set a specific scope
+ // when querying at the top-level.
+ throw new lazy.error.InvalidArgumentError(
+ `"root" and "moz:scope" are mutual exclusive`
+ );
+ }
+
+ contexts = [this._getNavigable(rootId, { supportsChromeScope: true })];
} else {
switch (scope) {
case MozContextScope.CHROME: {
@@ -2585,6 +2596,7 @@ class BrowsingContextModule extends RootBiDiModule {
* @param {number=} options.maxDepth
* Depth of the browsing context tree to traverse. If not specified
* the whole tree is returned.
+ *
* @returns {BrowsingContextInfo}
* The information about the browsing context.
*/
diff --git a/testing/web-platform/mozilla/meta/webdriver/bidi/browsing_context/get_tree/moz_scope.py.ini b/testing/web-platform/mozilla/meta/webdriver/bidi/browsing_context/get_tree/moz_scope.py.ini
@@ -11,3 +11,7 @@
[test_custom_chrome_window_with_iframes]
disabled:
if os == "android": https://bugzilla.mozilla.org/show_bug.cgi?id=1762066
+
+ [test_child_context_without_chrome_scope]
+ disabled:
+ if os == "android": https://bugzilla.mozilla.org/show_bug.cgi?id=1762066
diff --git a/testing/web-platform/mozilla/meta/webdriver/bidi/browsing_context/get_tree/root.py.ini b/testing/web-platform/mozilla/meta/webdriver/bidi/browsing_context/get_tree/root.py.ini
@@ -0,0 +1,4 @@
+[root.py]
+ [test_custom_chrome_window]
+ disabled:
+ if os == "android": https://bugzilla.mozilla.org/show_bug.cgi?id=1762066
diff --git a/testing/web-platform/mozilla/tests/webdriver/bidi/browsing_context/get_tree/conftest.py b/testing/web-platform/mozilla/tests/webdriver/bidi/browsing_context/get_tree/conftest.py
@@ -0,0 +1,9 @@
+import pytest
+
+
+@pytest.fixture
+def browser_chrome_url(current_session):
+ if current_session.capabilities["platformName"] == "android":
+ return "chrome://geckoview/content/geckoview.xhtml"
+ else:
+ return "chrome://browser/content/browser.xhtml"
diff --git a/testing/web-platform/mozilla/tests/webdriver/bidi/browsing_context/get_tree/invalid.py b/testing/web-platform/mozilla/tests/webdriver/bidi/browsing_context/get_tree/invalid.py
@@ -0,0 +1,12 @@
+import pytest
+from webdriver.bidi import error
+
+pytestmark = pytest.mark.asyncio
+
+
+@pytest.mark.allow_system_access
+async def test_mutual_exclusive_with_scope(bidi_session, top_context):
+ with pytest.raises(error.InvalidArgumentException):
+ await bidi_session.browsing_context.get_tree(
+ root=top_context["context"], _extension_params={"moz:scope": "chrome"}
+ )
diff --git a/testing/web-platform/mozilla/tests/webdriver/bidi/browsing_context/get_tree/moz_scope.py b/testing/web-platform/mozilla/tests/webdriver/bidi/browsing_context/get_tree/moz_scope.py
@@ -5,14 +5,6 @@ from webdriver.bidi import error
pytestmark = pytest.mark.asyncio
-@pytest.fixture
-def browser_chrome_url(current_session):
- if current_session.capabilities["platformName"] == "android":
- return "chrome://geckoview/content/geckoview.xhtml"
- else:
- return "chrome://browser/content/browser.xhtml"
-
-
async def test_without_system_access(bidi_session):
with pytest.raises(error.UnsupportedOperationException):
await bidi_session.browsing_context.get_tree(
@@ -74,26 +66,30 @@ async def test_custom_chrome_window_without_iframes(
# Retrieve all browsing contexts for the custom chrome window
parent_contexts = await bidi_session.browsing_context.get_tree(
- root=new_window.id, _extension_params={"moz:scope": "chrome"}
+ max_depth=None, _extension_params={"moz:scope": "chrome"}
)
- assert len(parent_contexts) == 1
+ assert len(parent_contexts) == 2
+
+ filtered_contexts = [
+ context for context in parent_contexts if context["context"] == new_window.id
+ ]
+
+ assert len(filtered_contexts) == 1
assert_browsing_context(
- parent_contexts[0],
- None,
+ filtered_contexts[0],
+ new_window.id,
children=0,
parent=None,
url=chrome_url,
client_window=None,
)
- assert len(parent_contexts) == 1
+ assert filtered_contexts[0]["clientWindow"] != top_context["clientWindow"]
- assert parent_contexts[0]["clientWindow"] != top_context["clientWindow"]
-
- assert parent_contexts[0]["moz:scope"] == "chrome"
- assert parent_contexts[0]["moz:name"] == "null"
+ assert filtered_contexts[0]["moz:scope"] == "chrome"
+ assert filtered_contexts[0]["moz:name"] == "null"
@pytest.mark.allow_system_access
@@ -114,23 +110,31 @@ async def test_custom_chrome_window_with_iframes(
# Retrieve all browsing contexts for the custom chrome window
parent_contexts = await bidi_session.browsing_context.get_tree(
- root=new_window.id, _extension_params={"moz:scope": "chrome"}
+ _extension_params={"moz:scope": "chrome"}
)
+ assert len(parent_contexts) == 2
+
+ filtered_contexts = [
+ context for context in parent_contexts if context["context"] == new_window.id
+ ]
+
+ assert len(filtered_contexts) == 1
+
assert_browsing_context(
- parent_contexts[0],
- None,
+ filtered_contexts[0],
+ new_window.id,
children=2,
parent=None,
url=chrome_url,
client_window=None,
)
- assert parent_contexts[0]["clientWindow"] != top_context["clientWindow"]
+ assert filtered_contexts[0]["clientWindow"] != top_context["clientWindow"]
- assert parent_contexts[0]["moz:scope"] == "chrome"
- assert parent_contexts[0]["moz:name"] == "null"
+ assert filtered_contexts[0]["moz:scope"] == "chrome"
+ assert filtered_contexts[0]["moz:name"] == "null"
- iframes = parent_contexts[0]["children"]
+ iframes = filtered_contexts[0]["children"]
# First iframe has no children
assert_browsing_context(
@@ -139,9 +143,9 @@ async def test_custom_chrome_window_with_iframes(
children=0,
parent_expected=False,
url=iframe_url,
- client_window=parent_contexts[0]["clientWindow"],
+ client_window=filtered_contexts[0]["clientWindow"],
)
- assert iframes[0]["context"] != parent_contexts[0]["context"]
+ assert iframes[0]["context"] != filtered_contexts[0]["context"]
assert iframes[0]["moz:scope"] == "chrome"
assert iframes[0]["moz:name"] == "iframe"
@@ -153,9 +157,9 @@ async def test_custom_chrome_window_with_iframes(
children=1,
parent_expected=False,
url=nested_iframe_url,
- client_window=parent_contexts[0]["clientWindow"],
+ client_window=filtered_contexts[0]["clientWindow"],
)
- assert iframes[1]["context"] != parent_contexts[0]["context"]
+ assert iframes[1]["context"] != filtered_contexts[0]["context"]
assert iframes[1]["context"] != iframes[0]["context"]
assert iframes[1]["moz:scope"] == "chrome"
@@ -170,12 +174,58 @@ async def test_custom_chrome_window_with_iframes(
children=0,
parent_expected=False,
url=iframe_url,
- client_window=parent_contexts[0]["clientWindow"],
+ client_window=filtered_contexts[0]["clientWindow"],
)
- assert iframes[1]["context"] != parent_contexts[0]["context"]
+ assert iframes[1]["context"] != filtered_contexts[0]["context"]
assert iframes[1]["context"] != iframes[0]["context"]
assert nested_iframes[0]["context"] != iframes[1]["context"]
- assert nested_iframes[0]["context"] != parent_contexts[0]["context"]
+ assert nested_iframes[0]["context"] != filtered_contexts[0]["context"]
assert nested_iframes[0]["moz:scope"] == "chrome"
assert nested_iframes[0]["moz:name"] == "iframe"
+
+
+@pytest.mark.allow_system_access
+async def test_child_context_without_chrome_scope(
+ bidi_session, default_chrome_handler, new_chrome_window
+):
+ # Bug 1762066: Skip this test on Android, as it currently causes the browser to crash
+ # when attempting to register a chrome handler for files that cannot be found.
+ # Since we cannot disable the test via the manifest, we return early instead.
+ if bidi_session.capabilities["platformName"] == "android":
+ return
+
+ chrome_url = f"{default_chrome_handler}test.xhtml"
+ iframe_url = f"{default_chrome_handler}test_iframe.xhtml"
+
+ new_window = new_chrome_window(chrome_url)
+
+ # Retrieve all browsing contexts for the custom chrome window
+ top_level_contexts = await bidi_session.browsing_context.get_tree(
+ root=new_window.id, max_depth=1
+ )
+
+ assert len(top_level_contexts) == 1
+
+ assert_browsing_context(
+ top_level_contexts[0],
+ new_window.id,
+ children=2,
+ parent=None,
+ url=chrome_url,
+ client_window=None,
+ )
+
+ iframes = top_level_contexts[0]["children"]
+
+ assert len(iframes) == 2
+
+ # Check that child chrome browsing context are still returned
+ assert_browsing_context(
+ iframes[0],
+ None,
+ children=None,
+ parent_expected=False,
+ url=iframe_url,
+ client_window=top_level_contexts[0]["clientWindow"],
+ )
diff --git a/testing/web-platform/mozilla/tests/webdriver/bidi/browsing_context/get_tree/root.py b/testing/web-platform/mozilla/tests/webdriver/bidi/browsing_context/get_tree/root.py
@@ -0,0 +1,80 @@
+import pytest
+from tests.bidi.browsing_context import assert_browsing_context
+
+pytestmark = pytest.mark.asyncio
+
+
+@pytest.mark.allow_system_access
+async def test_custom_chrome_window(
+ bidi_session, default_chrome_handler, new_chrome_window
+):
+ # Bug 1762066: Skip this test on Android, as it currently causes the browser to crash
+ # when attempting to register a chrome handler for files that cannot be found.
+ # Since we cannot disable the test via the manifest, we return early instead.
+ if bidi_session.capabilities["platformName"] == "android":
+ return
+
+ chrome_url = f"{default_chrome_handler}test.xhtml"
+ iframe_url = f"{default_chrome_handler}test_iframe.xhtml"
+ nested_iframe_url = f"{default_chrome_handler}test_nested_iframe.xhtml"
+
+ new_window = new_chrome_window(chrome_url)
+
+ # Retrieve all browsing contexts for the custom chrome window
+ top_level_contexts = await bidi_session.browsing_context.get_tree(
+ root=new_window.id, max_depth=1
+ )
+
+ assert len(top_level_contexts) == 1
+
+ assert_browsing_context(
+ top_level_contexts[0],
+ new_window.id,
+ children=2,
+ parent=None,
+ url=chrome_url,
+ client_window=None,
+ )
+
+ iframes = top_level_contexts[0]["children"]
+
+ # Retrieve all browsing contexts for the second iframe
+ iframe_contexts = await bidi_session.browsing_context.get_tree(
+ root=iframes[1]["context"]
+ )
+
+ assert len(iframe_contexts) == 1
+
+ assert_browsing_context(
+ iframe_contexts[0],
+ iframes[1]["context"],
+ children=1,
+ parent=top_level_contexts[0]["context"],
+ url=nested_iframe_url,
+ client_window=top_level_contexts[0]["clientWindow"],
+ )
+
+ assert iframe_contexts[0]["context"] != top_level_contexts[0]["context"]
+ assert iframe_contexts[0]["context"] == iframes[1]["context"]
+
+ assert iframe_contexts[0]["moz:scope"] == "chrome"
+ assert iframe_contexts[0]["moz:name"] == "iframe-nested"
+
+ nested_iframes = iframe_contexts[0]["children"]
+
+ # Check the first nested iframe
+ assert_browsing_context(
+ nested_iframes[0],
+ None,
+ children=0,
+ parent_expected=False,
+ url=iframe_url,
+ client_window=top_level_contexts[0]["clientWindow"],
+ )
+ assert iframes[1]["context"] != top_level_contexts[0]["context"]
+ assert iframes[1]["context"] != iframes[0]["context"]
+ assert nested_iframes[0]["context"] != iframes[1]["context"]
+ assert nested_iframes[0]["context"] != top_level_contexts[0]["context"]
+
+ assert nested_iframes[0]["moz:scope"] == "chrome"
+ assert nested_iframes[0]["moz:name"] == "iframe"