tor-browser

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

commit 9d7ec8516734c060cf82cad12006fdf28fc68ad8
parent 07f0962d98a865bf251a057b7e51494a7e2c9ae5
Author: Jacques Newman <janewman@microsoft.com>
Date:   Wed, 19 Nov 2025 04:55:50 +0000

Bug 2000733 [wpt PR 56076] - [focusgroup] aria role inference for focusgroup items., a=testonly

Automatic update from web-platform-tests
[focusgroup] aria role inference for focusgroup items.

Implements behavior-specific item role inference, see
https://open-ui.org/components/scoped-focusgroup.explainer/#supported-behaviors.

The focusgroup item logic lives in
AXObject::ComputeFinalRoleForSerialization because it needs final
contextual information that’s only available after regular role
computation:
* Focusgroup Owner's resolved role must match the role it's focusgroup behavior maps to.
* Focusgroup Owner's behavior data

See ComputeFinalRoleForSerialization's declaration header in ax_object.h
for more details.

Related ARIA issue discussion: https://github.com/w3c/aria/issues/2602

Bug: 40210717
Change-Id: Id1c0c0703e3fcb42bad9f858617dc8c9bd7faec6
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7099362
Reviewed-by: Benjamin Beaudry <benjamin.beaudry@microsoft.com>
Auto-Submit: Jacques Newman <janewman@microsoft.com>
Commit-Queue: Jacques Newman <janewman@microsoft.com>
Reviewed-by: Mason Freed <masonf@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1546187}

--

wpt-commits: a941071742f19a792a50349529b7d79cbd308a81
wpt-pr: 56076

Diffstat:
Atesting/web-platform/tests/html/interaction/focus/focusgroup/tentative/ax-role-inference-children.html | 89+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atesting/web-platform/tests/html/interaction/focus/focusgroup/tentative/ax-role-inference-item-elementinternals-nested.html | 50++++++++++++++++++++++++++++++++++++++++++++++++++
Atesting/web-platform/tests/html/interaction/focus/focusgroup/tentative/ax-role-inference-item-elementinternals-owner-implied.html | 39+++++++++++++++++++++++++++++++++++++++
Atesting/web-platform/tests/html/interaction/focus/focusgroup/tentative/ax-role-inference-item-elementinternals.html | 69+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
4 files changed, 247 insertions(+), 0 deletions(-)

diff --git a/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/ax-role-inference-children.html b/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/ax-role-inference-children.html @@ -0,0 +1,89 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>HTML Test: focusgroup - Child implied AX role inference</title> +<meta name="assert" content="Generic child elements inside a focusgroup with implied owner role map to behavior-specific child roles; explicit and native child roles are preserved."> +<link rel="author" title="Microsoft" href="http://www.microsoft.com/"> +<link rel="help" href="https://open-ui.org/components/scoped-focusgroup.explainer/"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/resources/testdriver.js"></script> +<script src="/resources/testdriver-vendor.js"></script> +<script src="/resources/testdriver-actions.js"></script> + +<!-- Tablist behavior --> +<div id=fgTablist focusgroup="tablist"> + <span tabindex=0 id=tabChild>Tab</span> +</div> + +<!-- Radiogroup behavior --> +<div id=fgRadiogroup focusgroup="radiogroup"> + <span tabindex=0 id=radioChild>Radio</span> +</div> + +<!-- Listbox behavior --> +<div id=fgListbox focusgroup="listbox"> + <span tabindex=0 id=listboxChild>Option</span> +</div> + +<!-- Menu behavior --> +<div id=fgMenu focusgroup="menu"> + <span tabindex=0 id=menuChild>Menu Item</span> +</div> + +<!-- Menubar behavior --> +<div id=fgMenubar focusgroup="menubar"> + <span tabindex=0 id=menubarChild>Menu Item</span> +</div> + +<!-- Explicit child role (should not be overridden) --> +<div id=fgExplicitChild focusgroup="tablist"> + <span id="explicitChild" tabindex=0 role=listitem>List Item</span> +</div> + +<!-- Native child semantics (button) should be preserved --> +<div id=fgNativeChild focusgroup="radiogroup"> + <button id=nativeChild>Button</button> +</div> + +<script> +// Use WebDriver get_computed_role to assert author vs implied roles. +// This mirrors pattern in wai-aria/role/basic.html. + +async function assert_role_equals(idref,expected_role) { + const element = document.getElementById(idref); + const role = await test_driver.get_computed_role(element); + assert_equals(role, expected_role); +} + +promise_test(async t => { + await assert_role_equals('tabChild', 'tab'); +}, 'Tablist child gets implied tab role'); + +promise_test(async t => { + // Blink exposes role="radio" as AXRadioButton internally; computed role should be 'radio'. + await assert_role_equals('radioChild', 'radio'); +}, 'Radiogroup child gets implied radio role'); + +promise_test(async t => { + await assert_role_equals('listboxChild', 'option'); +}, 'Listbox child gets implied option role'); + +promise_test(async t => { + await assert_role_equals('menuChild', 'menuitem'); +}, 'Menu child gets implied menuitem role'); + +promise_test(async t => { + // Menubar items map to menuitem. + await assert_role_equals('menubarChild', 'menuitem'); +}, 'Menubar child gets implied menuitem role'); + +promise_test(async t => { + // Explicit author-supplied listitem role should be preserved. + await assert_role_equals('explicitChild', 'listitem'); +}, 'Explicit child role preserved over implied mapping'); + +promise_test(async t => { + // Native button semantics must be preserved. + await assert_role_equals('nativeChild', 'button'); +}, 'Native child semantics preserved over implied mapping'); +</script> diff --git a/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/ax-role-inference-item-elementinternals-nested.html b/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/ax-role-inference-item-elementinternals-nested.html @@ -0,0 +1,50 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>HTML Test: focusgroup - Nested owners ElementInternals item roles preserved</title> +<meta name="assert" content="In nested focusgroups, ElementInternals supplied item roles (button/menuitem) remain preserved and are not coerced by owner inference."> +<link rel="author" title="Microsoft" href="http://www.microsoft.com/"> +<link rel="help" href="https://open-ui.org/components/scoped-focusgroup.explainer/"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<div id="outerToolbar" focusgroup="toolbar"> + <focusgroup-toolbar-button-internals id="tbItem" tabindex="0"></focusgroup-toolbar-button-internals> + <div id="innerMenu" focusgroup="menu"> + <focusgroup-menuitem-internals id="menuItem" tabindex="0"></focusgroup-menuitem-internals> + </div> +</div> + +<script> +class FocusgroupToolbarButtonInternals extends HTMLElement { + constructor() { + super(); + this.internals_ = this.attachInternals(); + this.internals_.role = 'button'; + this.textContent = 'Tool'; + } +} +customElements.define('focusgroup-toolbar-button-internals', FocusgroupToolbarButtonInternals); + +class FocusgroupMenuItemInternals extends HTMLElement { + constructor() { + super(); + this.internals_ = this.attachInternals(); + this.internals_.role = 'menuitem'; + this.textContent = 'Action'; + } +} +customElements.define('focusgroup-menuitem-internals', FocusgroupMenuItemInternals); +</script> + +<script> +if (!window.accessibilityController) { + test(() => { assert_true(true); }, 'accessibilityController not available (noop)'); +} else { + test(() => { + const tbAX = accessibilityController.accessibleElementById('tbItem'); + assert_equals(tbAX.role, 'AXRole: AXButton'); + const menuItemAX = accessibilityController.accessibleElementById('menuItem'); + assert_equals(menuItemAX.role, 'AXRole: AXMenuItem'); + }, 'Nested focusgroup ElementInternals item roles preserved'); +} +</script> diff --git a/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/ax-role-inference-item-elementinternals-owner-implied.html b/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/ax-role-inference-item-elementinternals-owner-implied.html @@ -0,0 +1,39 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>HTML Test: focusgroup - ElementInternals radio item roles preserved (radiogroup mapping)</title> +<meta name="assert" content="ElementInternals supplied 'radio' roles must not be overridden by implied item mapping within a radiogroup focusgroup."> +<link rel="author" title="Microsoft" href="http://www.microsoft.com/"> +<link rel="help" href="https://open-ui.org/components/scoped-focusgroup.explainer/"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<div id="rgOwner" focusgroup="radiogroup"> + <focusgroup-radio-internals id="radio1" tabindex="0"></focusgroup-radio-internals> + <focusgroup-radio-internals id="radio2" tabindex="-1"></focusgroup-radio-internals> +</div> + +<script> +class FocusgroupRadioInternals extends HTMLElement { + constructor() { + super(); + this.internals_ = this.attachInternals(); + this.internals_.role = 'radio'; + this.textContent = 'Choice'; + } +} +customElements.define('focusgroup-radio-internals', FocusgroupRadioInternals); +</script> + +<script> +if (!window.accessibilityController) { + test(() => { assert_true(true); }, 'accessibilityController not available (noop)'); +} else { + test(() => { + const radio1AX = accessibilityController.accessibleElementById('radio1'); + // Blink exposes ARIA role="radio" as AXRadioButton in the accessibility tree. + assert_equals(radio1AX.role, 'AXRole: AXRadioButton'); + const radio2AX = accessibilityController.accessibleElementById('radio2'); + assert_equals(radio2AX.role, 'AXRole: AXRadioButton'); + }, 'ElementInternals radio roles preserved (radiogroup case)'); +} +</script> diff --git a/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/ax-role-inference-item-elementinternals.html b/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/ax-role-inference-item-elementinternals.html @@ -0,0 +1,69 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>HTML Test: focusgroup - ElementInternals item role never overridden</title> +<meta name="assert" content="Author supplied ElementInternals role for a focusgroup item must never be overridden by implied item role inference, even when mismatching expected mapping."> +<link rel="author" title="Microsoft" href="http://www.microsoft.com/"> +<link rel="help" href="https://open-ui.org/components/scoped-focusgroup.explainer/"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<!-- Scenario A: Owner implied tablist, item supplies expected mapped role 'tab'. --> +<div id="fgOwnerTablist" focusgroup="tablist"> + <focusgroup-item-internals-expected id="tabItem" tabindex="0"></focusgroup-item-internals-expected> +</div> + +<!-- Scenario B: Same owner type (tablist) but item supplies a different role ('button'); ensure we do NOT coerce it to tab. --> +<div id="fgOwnerTablistMismatch" focusgroup="tablist"> + <focusgroup-item-internals-mismatch id="mismatchItem" tabindex="0"></focusgroup-item-internals-mismatch> +</div> + +<script> +class FocusgroupItemInternalsExpected extends HTMLElement { + constructor() { + super(); + this.internals_ = this.attachInternals(); + // ElementInternals author supplied role should be preserved. + this.internals_.role = 'tab'; + this.textContent = 'Item'; + } +} +customElements.define('focusgroup-item-internals-expected', FocusgroupItemInternalsExpected); + +class FocusgroupItemInternalsMismatch extends HTMLElement { + constructor() { + super(); + this.internals_ = this.attachInternals(); + // Deliberately provide a role that does NOT match the implied item role mapping. + this.internals_.role = 'button'; + this.textContent = 'Other'; + } +} +customElements.define('focusgroup-item-internals-mismatch', FocusgroupItemInternalsMismatch); +</script> + +<script> +if (!window.accessibilityController) { + test(() => { assert_true(true); }, 'accessibilityController not available (noop)'); +} else { + test(() => { + const ownerAX = accessibilityController.accessibleElementById('fgOwnerTablist'); + assert_equals(ownerAX.role, 'AXRole: AXTabList'); + }, 'Scenario A: Owner receives implied tablist role'); + + test(() => { + const itemAX = accessibilityController.accessibleElementById('tabItem'); + assert_equals(itemAX.role, 'AXRole: AXTab'); + }, 'Scenario A: Expected mapped ElementInternals role (tab) preserved'); + + test(() => { + const ownerAX = accessibilityController.accessibleElementById('fgOwnerTablistMismatch'); + assert_equals(ownerAX.role, 'AXRole: AXTabList'); + }, 'Scenario B: Owner implied tablist role still inferred with mismatched child role'); + + test(() => { + const itemAX = accessibilityController.accessibleElementById('mismatchItem'); + // Critical assertion: we do NOT coerce to AXTab; remains AXButton per author intent. + assert_equals(itemAX.role, 'AXRole: AXButton'); + }, 'Scenario B: Mismatched ElementInternals role (button) preserved (no coercion)'); +} +</script>