commit 0589ea25800788c7dafb4cb7dcb61c3ae535b8a4 parent 3d0c92c0068eed69081e95fcb4c656c9d5945e4a Author: Jacques Newman <janewman@microsoft.com> Date: Thu, 11 Dec 2025 09:26:52 +0000 Bug 2004831 [wpt PR 56582] - [focusgroup] Remove non-entry items from sequential focus navigation, a=testonly Automatic update from web-platform-tests [focusgroup] Remove non-entry items from sequential focus navigation Updates focus_controller to use the new utils for focusgroup. Since each focusgroup segment is guaranteed to have just a single tab stop, this means that focusgroup effectively removes all other elements from sequential focus navigation. This change updates the focus algorithm to not consider these elements when seeking to find the next focusable item. Bug: 40210717 Change-Id: I05d355b1161b2b89ef23f50b31c1e60100893ba7 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7200224 Reviewed-by: Mason Freed <masonf@chromium.org> Commit-Queue: Jacques Newman <janewman@microsoft.com> Cr-Commit-Position: refs/heads/main@{#1555866} -- wpt-commits: 543646821e7e03e460045b597043575f2120f49f wpt-pr: 56582 Diffstat:
7 files changed, 647 insertions(+), 0 deletions(-)
diff --git a/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/sequential-navigation/basic-tab-behavior.html b/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/sequential-navigation/basic-tab-behavior.html @@ -0,0 +1,65 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>HTML Test: focusgroup - Guaranteed tab stop entry and exit</title> +<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> +<script src="/shadow-dom/focus-navigation/resources/focus-utils.js"></script> +<script src="../resources/focusgroup-utils.js"></script> + +<!-- Basic focusgroup for entry/exit testing --> +<div id=before tabindex=0>Before focusgroup</div> + +<div id=focusgroup1 focusgroup="toolbar no-memory"> + <span id=item1 tabindex=0>Item 1</span> + <span id=item2 tabindex=0>Item 2</span> + <span id=item3 tabindex=0>Item 3</span> +</div> + +<div id=after tabindex=0>After focusgroup</div> + +<script> + promise_test(async t => { + var before = document.getElementById("before"); + var item1 = document.getElementById("item1"); + var after = document.getElementById("after"); + + before.focus(); + await navigateFocusForward(); + assert_equals(document.activeElement, item1, "Tab should enter focusgroup at first item in tree order"); + + await navigateFocusForward(); + assert_equals(document.activeElement, after, "Tab should exit focusgroup to next focusable element"); + }, "Tab enters focusgroup at first item in tree order and exits normally"); + + promise_test(async t => { + var after = document.getElementById("after"); + var item1 = document.getElementById("item1"); + var before = document.getElementById("before"); + + after.focus(); + await navigateFocusBackward(); + assert_equals(document.activeElement, item1, "Shift+Tab should enter focusgroup at first item in tree order"); + + await navigateFocusBackward(); + assert_equals(document.activeElement, before, "Shift+Tab should exit focusgroup to previous focusable element"); + }, "Shift+Tab enters focusgroup at first item in tree order and exits normally"); + + promise_test(async t => { + var item1 = document.getElementById("item1"); + var item2 = document.getElementById("item2"); + var item3 = document.getElementById("item3"); + + item1.focus(); + + await focusAndKeyPress(item1, kArrowRight); + assert_equals(document.activeElement, item2, "Arrow key navigation should work within focusgroup"); + + await focusAndKeyPress(item2, kArrowRight); + assert_equals(document.activeElement, item3, "Arrow key navigation should continue working"); + }, "Arrow key navigation continues to work normally within focusgroup"); +</script> diff --git a/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/sequential-navigation/dynamic-changes.html b/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/sequential-navigation/dynamic-changes.html @@ -0,0 +1,62 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>HTML Test: focusgroup - Dynamic DOM and focusgroup-entry-priority changes</title> +<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> +<script src="/shadow-dom/focus-navigation/resources/focus-utils.js"></script> +<script src="../resources/focusgroup-utils.js"></script> + +<!-- Test focusgroup-entry-priority changes during navigation --> +<div id=before1 tabindex=0>Before test 1</div> + +<div id=fg1 focusgroup="toolbar no-memory"> + <span id=item1 tabindex=0>Item 1</span> + <span id=item2 tabindex=0>Item 2</span> +</div> + +<div id=after1 tabindex=0>After test 1</div> + +<!-- Test disabled element changes --> +<div id=before2 tabindex=0>Before test 2</div> + +<div id=fg2 focusgroup="toolbar no-memory"> + <button id=btn1>Button 1</button> + <button id=btn2 disabled>Button 2</button> +</div> + +<div id=after2 tabindex=0>After test 2</div> + +<script> + promise_test(async t => { + document.getElementById("before1").focus(); + await navigateFocusForward(); + assert_equals(document.activeElement, document.getElementById("item1"), + "First item selected as entry element initially (tree order)"); + + document.getElementById("item2").setAttribute("focusgroup-entry-priority", ""); + + document.getElementById("before1").focus(); + await navigateFocusForward(); + assert_equals(document.activeElement, document.getElementById("item2"), + "After adding focusgroup-entry-priority, new priority is respected"); + }, "Dynamic focusgroup-entry-priority changes affect entry element selection"); + + promise_test(async t => { + document.getElementById("before2").focus(); + await navigateFocusForward(); + assert_equals(document.activeElement, document.getElementById("btn1"), + "Tab skips disabled button"); + + document.getElementById("btn2").removeAttribute("disabled"); + + document.getElementById("before2").focus(); + await navigateFocusForward(); + assert_equals(document.activeElement, document.getElementById("btn1"), + "First enabled button selected in tree order"); + }, "Enabling disabled elements makes them available for tab stop"); +</script> diff --git a/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/sequential-navigation/empty-and-non-focusable.html b/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/sequential-navigation/empty-and-non-focusable.html @@ -0,0 +1,67 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>HTML Test: focusgroup - Empty and non-focusable focusgroups</title> +<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> +<script src="/shadow-dom/focus-navigation/resources/focus-utils.js"></script> +<script src="../resources/focusgroup-utils.js"></script> + +<!-- Empty focusgroup --> +<div id=before1 tabindex=0>Before empty</div> +<div id=empty focusgroup="toolbar"></div> +<div id=after1 tabindex=0>After empty</div> + +<!-- Focusgroup with only non-focusable elements --> +<div id=before2 tabindex=0>Before non-focusable</div> +<div id=nonfocus focusgroup="toolbar"> + <span id=span1>Non-focusable 1</span> + <span id=span2>Non-focusable 2</span> +</div> +<div id=after2 tabindex=0>After non-focusable</div> + +<!-- Focusgroup with only disabled elements --> +<div id=before3 tabindex=0>Before disabled</div> +<div id=disabled focusgroup="toolbar"> + <button id=btn-disabled disabled>Disabled</button> +</div> +<div id=after3 tabindex=0>After disabled</div> + +<!-- Empty focusgroup container but container itself is focusable --> +<div id=before4 tabindex=0>Before focusable container</div> +<div id=container tabindex=0 focusgroup="toolbar"></div> +<div id=after4 tabindex=0>After focusable container</div> + +<script> + promise_test(async t => { + document.getElementById("before1").focus(); + await navigateFocusForward(); + assert_equals(document.activeElement, document.getElementById("after1"), + "Tab skips empty focusgroup"); + }, "Empty focusgroup is skipped during Tab navigation"); + + promise_test(async t => { + document.getElementById("before2").focus(); + await navigateFocusForward(); + assert_equals(document.activeElement, document.getElementById("after2"), + "Tab skips focusgroup with only non-focusable elements"); + }, "Focusgroup with only non-focusable elements is skipped"); + + promise_test(async t => { + document.getElementById("before3").focus(); + await navigateFocusForward(); + assert_equals(document.activeElement, document.getElementById("after3"), + "Tab skips focusgroup with disabled elements"); + }, "Focusgroup with only disabled elements is skipped"); + + promise_test(async t => { + document.getElementById("before4").focus(); + await navigateFocusForward(); + assert_equals(document.activeElement, document.getElementById("container"), + "Tab focuses the focusgroup container itself when empty but focusable"); + }, "Focusable empty focusgroup container receives focus"); +</script> diff --git a/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/sequential-navigation/focusgroup-segments.html b/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/sequential-navigation/focusgroup-segments.html @@ -0,0 +1,147 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>HTML Test: focusgroup - Sequential navigation with focusgroup segments</title> +<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> +<script src="/shadow-dom/focus-navigation/resources/focus-utils.js"></script> +<script src="../resources/focusgroup-utils.js"></script> + +<!-- Opted-out elements create multiple focusgroup entry points --> +<div id=before tabindex=0>Before toolbar</div> + +<div id=toolbar focusgroup="toolbar wrap" aria-label="Text formatting"> + <button id=bold type="button" focusgroup-entry-priority>Bold (priority)</button> + <button id=italic type="button">Italic</button> + <span id=help-group focusgroup="none" aria-label="Help group"> + <button id=help type="button">Help</button> + <button id=shortcuts type="button">Shortcuts</button> + </span> + <button id=underline type="button" focusgroup-entry-priority>Underline (priority)</button> +</div> + +<div id=after tabindex=0>After toolbar</div> + +<!-- Test complex nested opt-out --> +<div id=before2 tabindex=0>Before complex</div> + +<div id=complex-focusgroup focusgroup="toolbar no-memory"> + <button id=item1 focusgroup-entry-priority>Item 1 (priority)</button> + <div id=nested-opt-out focusgroup="none"> + <button id=opted-out-1>Opted out 1</button> + <div id=deeply-nested focusgroup="none"> + <button id=deeply-opted-out>Deeply opted out</button> + </div> + <button id=opted-out-2>Opted out 2</button> + </div> + <button id=item2 focusgroup-entry-priority>Item 2 (priority)</button> + <button id=item3>Item 3</button> +</div> + +<div id=after2 tabindex=0>After complex</div> + +<script> + promise_test(async t => { + var before = document.getElementById("before"); + var bold = document.getElementById("bold"); + var help = document.getElementById("help"); + var shortcuts = document.getElementById("shortcuts"); + var underline = document.getElementById("underline"); + var after = document.getElementById("after"); + + before.focus(); + await navigateFocusForward(); + assert_equals(document.activeElement, bold, "Tab from before should focus Bold (first focusgroup item)"); + + await navigateFocusForward(); + assert_equals(document.activeElement, help, "Tab from Bold should focus Help (first opted-out element)"); + + await navigateFocusForward(); + assert_equals(document.activeElement, shortcuts, "Tab from Help should focus Shortcuts (next in opted-out subtree)"); + + await navigateFocusForward(); + assert_equals(document.activeElement, underline, "Tab from Shortcuts should focus Underline (re-enter focusgroup)"); + + await navigateFocusForward(); + assert_equals(document.activeElement, after, "Tab from Underline should exit focusgroup"); + }, "Tab navigation through focusgroup segments - forward direction"); + + promise_test(async t => { + var before = document.getElementById("before"); + var italic = document.getElementById("italic"); + var help = document.getElementById("help"); + var shortcuts = document.getElementById("shortcuts"); + var underline = document.getElementById("underline"); + var after = document.getElementById("after"); + + help.focus(); + await navigateFocusBackward(); + assert_equals(document.activeElement, bold, "Shift+Tab from Help should focus Bold"); + + await navigateFocusBackward(); + assert_equals(document.activeElement, before, "Shift+Tab from Bold should exit focusgroup"); + }, "Shift+Tab navigation through focusgroup segments - backward direction"); + + promise_test(async t => { + var bold = document.getElementById("bold"); + var italic = document.getElementById("italic"); + var underline = document.getElementById("underline"); + + bold.focus(); + + await focusAndKeyPress(bold, kArrowRight); + assert_equals(document.activeElement, italic, "Arrow right should move from Bold to Italic"); + + await focusAndKeyPress(italic, kArrowRight); + assert_equals(document.activeElement, underline, "Arrow right should skip opted-out span and move to Underline"); + + await focusAndKeyPress(underline, kArrowLeft); + assert_equals(document.activeElement, italic, "Arrow left should skip opted-out span and move to Italic"); + }, "Arrow key navigation treats opted-out elements as if they don't exist"); + + promise_test(async t => { + var before2 = document.getElementById("before2"); + var item1 = document.getElementById("item1"); + var optedOut1 = document.getElementById("opted-out-1"); + var deeplyOptedOut = document.getElementById("deeply-opted-out"); + var optedOut2 = document.getElementById("opted-out-2"); + var item2 = document.getElementById("item2"); + var after2 = document.getElementById("after2"); + + before2.focus(); + await navigateFocusForward(); + assert_equals(document.activeElement, item1, "Tab should enter at Item 1"); + + await navigateFocusForward(); + assert_equals(document.activeElement, optedOut1, "Tab should move to first opted-out element"); + + await navigateFocusForward(); + assert_equals(document.activeElement, deeplyOptedOut, "Tab should move to deeply nested opted-out element"); + + await navigateFocusForward(); + assert_equals(document.activeElement, optedOut2, "Tab should move to second opted-out element"); + + await navigateFocusForward(); + assert_equals(document.activeElement, item2, "Tab should re-enter focusgroup at Item 2"); + + await navigateFocusForward(); + assert_equals(document.activeElement, after2, "Tab should exit focusgroup"); + }, "Complex nested opt-out creates proper focusgroup segments"); + + promise_test(async t => { + var help = document.getElementById("help"); + var shortcuts = document.getElementById("shortcuts"); + + help.focus(); + + await focusAndKeyPress(help, kArrowRight); + assert_equals(document.activeElement, help, "Arrow keys should not work within opted-out section"); + + await focusAndKeyPress(help, kArrowDown); + assert_equals(document.activeElement, help, "Vertical arrow keys should not work within opted-out section"); + }, "Arrow keys do not work within opted-out focusgroup sections"); +</script> +\ No newline at end of file diff --git a/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/sequential-navigation/guaranteed-tab-stop-priority.html b/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/sequential-navigation/guaranteed-tab-stop-priority.html @@ -0,0 +1,91 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>HTML Test: focusgroup - Entry element priority with focusgroup-entry-priority attribute</title> +<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> +<script src="/shadow-dom/focus-navigation/resources/focus-utils.js"></script> +<script src="../resources/focusgroup-utils.js"></script> + +<!-- Priority Tier 1: focusgroup-entry-priority attribute --> +<div id=before1 tabindex=0>Before test 1</div> + +<div id=fg1 focusgroup="toolbar no-memory"> + <span id=item1 tabindex=0>Item 1 (no priority)</span> + <span id=item2 tabindex=0 focusgroup-entry-priority>Item 2 (has priority)</span> + <span id=item3 tabindex=0>Item 3 (no priority)</span> +</div> + +<div id=after1 tabindex=0>After test 1</div> + +<!-- Priority Tier 2: First element in tree order when no priority attribute --> +<div id=before2 tabindex=0>Before test 2</div> + +<div id=fg2 focusgroup="toolbar no-memory"> + <span id=first tabindex=0>First (no priority)</span> + <span id=second tabindex=0>Second (no priority)</span> +</div> + +<div id=after2 tabindex=0>After test 2</div> + +<!-- Priority Tier 3: First element with priority attribute when multiple have it --> +<div id=before3 tabindex=0>Before test 3</div> + +<div id=fg3 focusgroup="toolbar no-memory"> + <span id=early1 tabindex=0 focusgroup-entry-priority>Early (has priority)</span> + <span id=early2 tabindex=0 focusgroup-entry-priority>Later (has priority)</span> +</div> + +<div id=after3 tabindex=0>After test 3</div> + +<!-- Test with buttons (natively focusable) --> +<div id=before4 tabindex=0>Before test 4</div> + +<div id=fg4 focusgroup="toolbar no-memory"> + <button id=btn1>Button 1</button> + <button id=btn2 focusgroup-entry-priority>Button 2 (has priority)</button> + <button id=btn3>Button 3</button> +</div> + +<div id=after4 tabindex=0>After test 4</div> + +<script> + promise_test(async t => { + document.getElementById("before1").focus(); + await navigateFocusForward(); + assert_equals(document.activeElement, document.getElementById("item2"), + "Entry element is item with focusgroup-entry-priority"); + }, "Element with focusgroup-entry-priority is selected as entry element"); + + promise_test(async t => { + document.getElementById("before2").focus(); + await navigateFocusForward(); + assert_equals(document.activeElement, document.getElementById("first"), + "First item in tree order is selected when no priority attributes"); + }, "When no elements have focusgroup-entry-priority, first in tree order is selected"); + + promise_test(async t => { + document.getElementById("before3").focus(); + await navigateFocusForward(); + assert_equals(document.activeElement, document.getElementById("early1"), + "First element with priority attribute is selected"); + }, "When multiple items have focusgroup-entry-priority, earliest in tree order wins"); + + promise_test(async t => { + document.getElementById("before4").focus(); + await navigateFocusForward(); + assert_equals(document.activeElement, document.getElementById("btn2"), + "Priority attribute overrides tree order for native focusable elements"); + }, "focusgroup-entry-priority works with natively focusable elements"); + + promise_test(async t => { + document.getElementById("after1").focus(); + await navigateFocusBackward(); + assert_equals(document.activeElement, document.getElementById("item2"), + "Reverse navigation respects same priority algorithm"); + }, "Reverse navigation uses same priority algorithm"); +</script> diff --git a/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/sequential-navigation/memory-behavior.html b/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/sequential-navigation/memory-behavior.html @@ -0,0 +1,147 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>HTML Test: focusgroup - Memory behavior in sequential navigation</title> +<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> +<script src="/shadow-dom/focus-navigation/resources/focus-utils.js"></script> +<script src="../resources/focusgroup-utils.js"></script> + +<!-- Test focusgroup with memory --> +<div id=before-memory tabindex=0>Before memory focusgroup</div> + +<div id=memory-focusgroup focusgroup="toolbar"> + <button id=memory-item1>Item 1</button> + <button id=memory-item2 focusgroup-entry-priority>Item 2 (priority)</button> + <button id=memory-item3>Item 3</button> +</div> + +<div id=between tabindex=0>Between focusgroups</div> + +<!-- Test focusgroup with no-memory --> +<div id=no-memory-focusgroup focusgroup="toolbar no-memory"> + <button id=no-memory-item1>Item 1</button> + <button id=no-memory-item2 focusgroup-entry-priority>Item 2 (priority)</button> + <button id=no-memory-item3>Item 3</button> +</div> + +<div id=after-no-memory tabindex=0>After no-memory focusgroup</div> + +<!-- Test memory with segments --> +<div id=before-segments tabindex=0>Before segments</div> + +<div id=segments-focusgroup focusgroup="toolbar"> + <button id=seg-item1>Segment Item 1</button> + <button id=seg-item2 focusgroup-entry-priority>Segment Item 2 (priority)</button> + <div focusgroup="none"> + <button id=seg-opted-out>Opted out item</button> + </div> + <button id=seg-item3>Segment Item 3</button> +</div> + +<div id=after-segments tabindex=0>After segments</div> + +<script> + promise_test(async t => { + var beforeMemory = document.getElementById("before-memory"); + var memoryItem1 = document.getElementById("memory-item1"); + var memoryItem2 = document.getElementById("memory-item2"); + var memoryItem3 = document.getElementById("memory-item3"); + var between = document.getElementById("between"); + + beforeMemory.focus(); + await navigateFocusForward(); + assert_equals(document.activeElement, memoryItem2, "First tab entry should use focusgroup-entry-priority element"); + + await focusAndKeyPress(memoryItem2, kArrowRight); + assert_equals(document.activeElement, memoryItem3, "Arrow navigation should move to Item 3"); + + await navigateFocusForward(); + assert_equals(document.activeElement, between, "Tab should exit to between element"); + + await navigateFocusBackward(); + assert_equals(document.activeElement, memoryItem3, "Re-entering should focus last focused item (Item 3) due to memory"); + }, "Focusgroup with memory remembers last focused item on re-entry"); + + promise_test(async t => { + var between = document.getElementById("between"); + var noMemoryItem1 = document.getElementById("no-memory-item1"); + var noMemoryItem2 = document.getElementById("no-memory-item2"); + var noMemoryItem3 = document.getElementById("no-memory-item3"); + var afterNoMemory = document.getElementById("after-no-memory"); + + between.focus(); + await navigateFocusForward(); + assert_equals(document.activeElement, noMemoryItem2, "Entry should use focusgroup-entry-priority element"); + + await focusAndKeyPress(noMemoryItem2, kArrowRight); + assert_equals(document.activeElement, noMemoryItem3, "Arrow navigation should move to Item 3"); + + await navigateFocusForward(); + assert_equals(document.activeElement, afterNoMemory, "Tab should exit focusgroup"); + + await navigateFocusBackward(); + assert_equals(document.activeElement, noMemoryItem2, "Re-entering should use focusgroup-entry-priority, not remember last focus"); + }, "Focusgroup with no-memory does not remember last focused item"); + + promise_test(async t => { + var beforeSegments = document.getElementById("before-segments"); + var segItem1 = document.getElementById("seg-item1"); + var segItem2 = document.getElementById("seg-item2"); + var segOptedOut = document.getElementById("seg-opted-out"); + var segItem3 = document.getElementById("seg-item3"); + var afterSegments = document.getElementById("after-segments"); + + beforeSegments.focus(); + await navigateFocusForward(); + assert_equals(document.activeElement, segItem2, "Should enter at focusgroup-entry-priority element"); + + await navigateFocusForward(); + assert_equals(document.activeElement, segOptedOut, "Should move to opted-out element"); + + await navigateFocusForward(); + assert_equals(document.activeElement, segItem3, "Should enter second focusgroup segment"); + + await navigateFocusForward(); + assert_equals(document.activeElement, afterSegments, "Should exit focusgroup"); + + await navigateFocusBackward(); + assert_equals(document.activeElement, segItem3, "Should remember last focused item in the second segment"); + }, "Memory behavior with focusgroup segments remembers per-segment"); + + promise_test(async t => { + var beforeSegments = document.getElementById("before-segments"); + var segItem2 = document.getElementById("seg-item2"); + var segOptedOut = document.getElementById("seg-opted-out"); + + segOptedOut.focus(); + + await navigateFocusBackward(); + assert_equals(document.activeElement, segItem2, "Shift+Tab from opted-out should enter first segment at focusgroup-entry-priority element"); + + await navigateFocusBackward(); + assert_equals(document.activeElement, beforeSegments, "Should exit to before element"); + + await navigateFocusForward(); + assert_equals(document.activeElement, segItem2, "Re-entering first segment should remember last focused"); + }, "Memory works correctly for first focusgroup segment"); + + promise_test(async t => { + var memoryItem2 = document.getElementById("memory-item2"); + var memoryItem1 = document.getElementById("memory-item1"); + + memoryItem2.focus(); + + await focusAndKeyPress(memoryItem2, kArrowLeft); + assert_equals(document.activeElement, memoryItem1, "Arrow left should move to Item 1"); + + document.getElementById("between").focus(); + + await navigateFocusBackward(); + assert_equals(document.activeElement, memoryItem1, "Should remember Item 1 as last focused"); + }, "Last focused item is updated by arrow key navigation"); +</script> +\ No newline at end of file diff --git a/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/sequential-navigation/nested-focusgroups.html b/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/sequential-navigation/nested-focusgroups.html @@ -0,0 +1,66 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>HTML Test: focusgroup - Nested focusgroup navigation</title> +<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> +<script src="/shadow-dom/focus-navigation/resources/focus-utils.js"></script> +<script src="../resources/focusgroup-utils.js"></script> + +<!-- Test forward navigation through nested focusgroups --> +<div id=before1 tabindex=0>Before outer</div> + +<div id=outer focusgroup="toolbar"> + <span id=outer1 tabindex=0 focusgroup-entry-priority>Outer 1 (priority)</span> + <div id=inner focusgroup="toolbar no-memory"> + <span id=inner1 tabindex=0 focusgroup-entry-priority>Inner 1 (priority)</span> + <span id=inner2 tabindex=0>Inner 2</span> + </div> + <span id=outer2 tabindex=0>Outer 2</span> +</div> + +<div id=after1 tabindex=0>After outer</div> + +<script> + promise_test(async t => { + document.getElementById("before1").focus(); + await navigateFocusForward(); + assert_equals(document.activeElement, document.getElementById("outer1"), + "Tab enters outer focusgroup at focusgroup-entry-priority element"); + + await navigateFocusForward(); + assert_equals(document.activeElement, document.getElementById("inner1"), + "Tab enters nested inner focusgroup at focusgroup-entry-priority element"); + + await navigateFocusForward(); + assert_equals(document.activeElement, document.getElementById("outer2"), + "Tab moves to second segment of outer focusgroup"); + + await navigateFocusForward(); + assert_equals(document.activeElement, document.getElementById("after1"), + "Tab moves outside both focusgroups"); + }, "Forward Tab navigation through nested focusgroups"); + + promise_test(async t => { + document.getElementById("after1").focus(); + await navigateFocusBackward(); + assert_equals(document.activeElement, document.getElementById("outer2"), + "Shift+Tab enters second segment of outer focusgroup"); + + await navigateFocusBackward(); + assert_equals(document.activeElement, document.getElementById("inner1"), + "Shift+Tab enters nested focusgroup at focusgroup-entry-priority element"); + + await navigateFocusBackward(); + assert_equals(document.activeElement, document.getElementById("outer1"), + "Shift+Tab moves to outer focusgroup"); + + await navigateFocusBackward(); + assert_equals(document.activeElement, document.getElementById("before1"), + "Shift+Tab exits both focusgroups"); + }, "Reverse Shift+Tab navigation through nested focusgroups"); +</script>