summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/html/interaction
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /testing/web-platform/tests/html/interaction
parentInitial commit. (diff)
downloadfirefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz
firefox-26a029d407be480d791972afb5975cf62c9360a6.zip
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'testing/web-platform/tests/html/interaction')
-rw-r--r--testing/web-platform/tests/html/interaction/focus/chrome-object-tab-focus-bug.html43
-rw-r--r--testing/web-platform/tests/html/interaction/focus/composed.window.js16
-rw-r--r--testing/web-platform/tests/html/interaction/focus/document-level-focus-apis/document-has-system-focus.html44
-rw-r--r--testing/web-platform/tests/html/interaction/focus/document-level-focus-apis/document-level-apis.html34
-rw-r--r--testing/web-platform/tests/html/interaction/focus/document-level-focus-apis/support/popup.html18
-rw-r--r--testing/web-platform/tests/html/interaction/focus/document-level-focus-apis/support/test.html5
-rw-r--r--testing/web-platform/tests/html/interaction/focus/focus-01.html45
-rw-r--r--testing/web-platform/tests/html/interaction/focus/focus-02.html38
-rw-r--r--testing/web-platform/tests/html/interaction/focus/focus-file-input.html18
-rw-r--r--testing/web-platform/tests/html/interaction/focus/focus-input-type-switch.html26
-rw-r--r--testing/web-platform/tests/html/interaction/focus/focus-keyboard-js.html26
-rw-r--r--testing/web-platform/tests/html/interaction/focus/focus-management/focus-event-targets-simple.html33
-rw-r--r--testing/web-platform/tests/html/interaction/focus/focus-management/focus-events.html33
-rw-r--r--testing/web-platform/tests/html/interaction/focus/focused-element-move-documents-crash.html16
-rw-r--r--testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/backward-navigation/descends-into-extending-focusgroup.html37
-rw-r--r--testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/backward-navigation/does-not-move-when-on-focusgroup-root.html30
-rw-r--r--testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/backward-navigation/does-not-move-when-on-non-focusgroup-item.html33
-rw-r--r--testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/backward-navigation/does-not-move-when-only-one-item-and-wraps.html29
-rw-r--r--testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/backward-navigation/does-not-move-when-only-one-item.html29
-rw-r--r--testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/backward-navigation/does-not-move-when-outside-focusgroup.html31
-rw-r--r--testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/backward-navigation/does-not-wrap-when-not-supported.html30
-rw-r--r--testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/backward-navigation/horizontal/ascends-to-parent-focusgroup.html36
-rw-r--r--testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/backward-navigation/horizontal/does-not-ascend-out-of-non-extending-focusgroup.html30
-rw-r--r--testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/backward-navigation/horizontal/does-not-move-when-axis-not-supported.html27
-rw-r--r--testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/backward-navigation/horizontal/does-not-wrap-in-orthogonal-axis.html28
-rw-r--r--testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/backward-navigation/horizontal/moves-when-only-current-axis-supported.html28
-rw-r--r--testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/backward-navigation/horizontal/skips-focusgroup-that-extends-in-orthogonal-axis-complex-case.html38
-rw-r--r--testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/backward-navigation/horizontal/skips-focusgroup-that-extends-in-orthogonal-axis.html34
-rw-r--r--testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/backward-navigation/horizontal/wraps-in-axis.html29
-rw-r--r--testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/backward-navigation/moves-to-previous-item-and-skips-focusable-item.html32
-rw-r--r--testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/backward-navigation/moves-to-previous-item.html31
-rw-r--r--testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/backward-navigation/skips-empty-wrapping-focusgroup.html37
-rw-r--r--testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/backward-navigation/skips-non-focusgroup-subtree.html35
-rw-r--r--testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/backward-navigation/skips-root-focusgroup-complex-case.html41
-rw-r--r--testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/backward-navigation/skips-root-focusgroup.html37
-rw-r--r--testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/backward-navigation/vertical/ascends-to-parent-focusgroup.html36
-rw-r--r--testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/backward-navigation/vertical/does-not-ascend-out-of-non-extending-focusgroup.html30
-rw-r--r--testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/backward-navigation/vertical/does-not-move-when-axis-not-supported.html27
-rw-r--r--testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/backward-navigation/vertical/does-not-wrap-in-orthogonal-axis.html28
-rw-r--r--testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/backward-navigation/vertical/moves-when-only-current-axis-supported.html28
-rw-r--r--testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/backward-navigation/vertical/skips-focusgroup-that-extends-in-orthogonal-axis-complex-case.html38
-rw-r--r--testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/backward-navigation/vertical/skips-focusgroup-that-extends-in-orthogonal-axis.html34
-rw-r--r--testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/backward-navigation/vertical/wraps-in-axis.html29
-rw-r--r--testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/backward-navigation/wraps-in-extending-focusgroup.html43
-rw-r--r--testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/backward-navigation/wraps-successfully-complex-case.html40
-rw-r--r--testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/backward-navigation/wraps-successfully.html32
-rw-r--r--testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/forward-navigation/does-not-move-when-on-focusgroup-root.html30
-rw-r--r--testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/forward-navigation/does-not-move-when-on-non-item.html33
-rw-r--r--testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/forward-navigation/does-not-move-when-only-one-item-and-wraps.html29
-rw-r--r--testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/forward-navigation/does-not-move-when-only-one-item.html29
-rw-r--r--testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/forward-navigation/does-not-move-when-outside-focusgroup.html31
-rw-r--r--testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/forward-navigation/does-not-wrap-in-focusgroup-with-no-items.html35
-rw-r--r--testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/forward-navigation/does-not-wrap-when-not-supported.html30
-rw-r--r--testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/forward-navigation/horizontal/descends-in-horizontal-inner-focusgroup.html30
-rw-r--r--testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/forward-navigation/horizontal/does-not-move-when-axis-not-supported.html27
-rw-r--r--testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/forward-navigation/horizontal/does-not-wrap-even-when-other-axis-supported.html31
-rw-r--r--testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/forward-navigation/horizontal/moves-when-only-current-axis-supported.html28
-rw-r--r--testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/forward-navigation/horizontal/skips-focusgroup-that-extends-in-orthogonal-axis.html30
-rw-r--r--testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/forward-navigation/horizontal/wraps-and-skips-orthogonal-inner-focusgroup.html32
-rw-r--r--testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/forward-navigation/horizontal/wraps-in-appropriate-focusgroup.html35
-rw-r--r--testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/forward-navigation/horizontal/wraps-in-inner-focusgroup.html33
-rw-r--r--testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/forward-navigation/moves-inside-extending-focusgroup.html34
-rw-r--r--testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/forward-navigation/moves-to-next-item-and-skips-non-focusable.html32
-rw-r--r--testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/forward-navigation/moves-to-next-item-outside-extending-focusgroup.html34
-rw-r--r--testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/forward-navigation/moves-to-next-item-within-descendants.html37
-rw-r--r--testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/forward-navigation/moves-to-next-item.html31
-rw-r--r--testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/forward-navigation/vertical/descends-in-vertical-inner-focusgroup.html30
-rw-r--r--testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/forward-navigation/vertical/does-not-move-when-axis-not-supported.html27
-rw-r--r--testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/forward-navigation/vertical/does-not-wrap-even-when-other-axis-supported.html31
-rw-r--r--testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/forward-navigation/vertical/moves-when-only-current-axis-supported.html28
-rw-r--r--testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/forward-navigation/vertical/skips-focusgroup-that-extends-in-orthogonal-axis.html30
-rw-r--r--testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/forward-navigation/vertical/wraps-and-skips-orthogonal-inner-focusgroup.html32
-rw-r--r--testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/forward-navigation/vertical/wraps-in-appropriate-focusgroup.html35
-rw-r--r--testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/forward-navigation/vertical/wraps-in-inner-focusgroup.html33
-rw-r--r--testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/forward-navigation/wraps-and-goes-into-inner-focusgroup.html34
-rw-r--r--testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/forward-navigation/wraps-successfully.html31
-rw-r--r--testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/forward-navigation/wraps-to-parent-focusgroup.html34
-rw-r--r--testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/grid-navigation/col-flow-only.html58
-rw-r--r--testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/grid-navigation/col-wrap-and-row-flow.html60
-rw-r--r--testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/grid-navigation/col-wrap-only.html58
-rw-r--r--testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/grid-navigation/colspan.html93
-rw-r--r--testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/grid-navigation/css-table-not-focusgroup.html32
-rw-r--r--testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/grid-navigation/css-table.html44
-rw-r--r--testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/grid-navigation/does-not-wrap-or-flow.html56
-rw-r--r--testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/grid-navigation/empty-spaces.html144
-rw-r--r--testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/grid-navigation/flows-in-both-axes.html60
-rw-r--r--testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/grid-navigation/moves-across-table-sections.html97
-rw-r--r--testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/grid-navigation/non-table.html26
-rw-r--r--testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/grid-navigation/relayout-before-navigation.html39
-rw-r--r--testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/grid-navigation/row-flow-only.html58
-rw-r--r--testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/grid-navigation/row-wrap-and-col-flow.html60
-rw-r--r--testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/grid-navigation/row-wrap-only.html58
-rw-r--r--testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/grid-navigation/rowspan.html148
-rw-r--r--testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/grid-navigation/simple-case-with-non-focusable-cell-in-the-center.html65
-rw-r--r--testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/grid-navigation/simple-case.html60
-rw-r--r--testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/grid-navigation/wraps-in-both-axes.html60
-rw-r--r--testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/resources/focusgroup-utils.js15
-rw-r--r--testing/web-platform/tests/html/interaction/focus/processing-model/focus-fixup-rule-one-no-dialogs.html109
-rw-r--r--testing/web-platform/tests/html/interaction/focus/processing-model/focusVisible.html65
-rw-r--r--testing/web-platform/tests/html/interaction/focus/processing-model/legend-focusable.html17
-rw-r--r--testing/web-platform/tests/html/interaction/focus/processing-model/legend.html20
-rw-r--r--testing/web-platform/tests/html/interaction/focus/processing-model/preventScroll-nested-scroll-elements.html62
-rw-r--r--testing/web-platform/tests/html/interaction/focus/processing-model/preventScroll-textarea.html40
-rw-r--r--testing/web-platform/tests/html/interaction/focus/processing-model/preventScroll.html76
-rw-r--r--testing/web-platform/tests/html/interaction/focus/processing-model/support/preventScroll-helper.html6
-rw-r--r--testing/web-platform/tests/html/interaction/focus/processing-model/textarea-scroll-selection.html46
-rw-r--r--testing/web-platform/tests/html/interaction/focus/sequential-focus-navigation-and-the-tabindex-attribute/focus-tabindex-default-value.html21
-rw-r--r--testing/web-platform/tests/html/interaction/focus/sequential-focus-navigation-and-the-tabindex-attribute/focus-tabindex-negative.html45
-rw-r--r--testing/web-platform/tests/html/interaction/focus/sequential-focus-navigation-and-the-tabindex-attribute/focus-tabindex-order.html65
-rw-r--r--testing/web-platform/tests/html/interaction/focus/sequential-focus-navigation-and-the-tabindex-attribute/focus-tabindex-positive.html44
-rw-r--r--testing/web-platform/tests/html/interaction/focus/sequential-focus-navigation-and-the-tabindex-attribute/focus-tabindex-zero.html46
-rw-r--r--testing/web-platform/tests/html/interaction/focus/sequential-focus-navigation-and-the-tabindex-attribute/resources/frameset-using-page.html6
-rw-r--r--testing/web-platform/tests/html/interaction/focus/sequential-focus-navigation-and-the-tabindex-attribute/tabindex-getter-frame.html55
-rw-r--r--testing/web-platform/tests/html/interaction/focus/sequential-focus-navigation-and-the-tabindex-attribute/tabindex-getter.html95
-rw-r--r--testing/web-platform/tests/html/interaction/focus/setSequentialFocusStartingPoint.tentative.html27
-rw-r--r--testing/web-platform/tests/html/interaction/focus/tabindex-focus-flag.html134
-rw-r--r--testing/web-platform/tests/html/interaction/focus/the-autofocus-attribute/autofocus-area.html22
-rw-r--r--testing/web-platform/tests/html/interaction/focus/the-autofocus-attribute/autofocus-dialog.html29
-rw-r--r--testing/web-platform/tests/html/interaction/focus/the-autofocus-attribute/autofocus-in-not-fully-active-document.html16
-rw-r--r--testing/web-platform/tests/html/interaction/focus/the-autofocus-attribute/autofocus-on-stable-document.html21
-rw-r--r--testing/web-platform/tests/html/interaction/focus/the-autofocus-attribute/document-with-fragment-empty.html31
-rw-r--r--testing/web-platform/tests/html/interaction/focus/the-autofocus-attribute/document-with-fragment-nonexistent.html31
-rw-r--r--testing/web-platform/tests/html/interaction/focus/the-autofocus-attribute/document-with-fragment-top.html34
-rw-r--r--testing/web-platform/tests/html/interaction/focus/the-autofocus-attribute/document-with-fragment-valid.html57
-rw-r--r--testing/web-platform/tests/html/interaction/focus/the-autofocus-attribute/first-reconnected.html22
-rw-r--r--testing/web-platform/tests/html/interaction/focus/the-autofocus-attribute/first-when-later-but-before.html26
-rw-r--r--testing/web-platform/tests/html/interaction/focus/the-autofocus-attribute/first-when-later.html26
-rw-r--r--testing/web-platform/tests/html/interaction/focus/the-autofocus-attribute/first.html24
-rw-r--r--testing/web-platform/tests/html/interaction/focus/the-autofocus-attribute/focusable-area-in-top-document.html24
-rw-r--r--testing/web-platform/tests/html/interaction/focus/the-autofocus-attribute/no-autofocus-on-changing-input-type.html27
-rw-r--r--testing/web-platform/tests/html/interaction/focus/the-autofocus-attribute/no-cross-origin-autofocus.html44
-rw-r--r--testing/web-platform/tests/html/interaction/focus/the-autofocus-attribute/no-sandboxed-automatic-features.html17
-rw-r--r--testing/web-platform/tests/html/interaction/focus/the-autofocus-attribute/not-on-first-task.html22
-rw-r--r--testing/web-platform/tests/html/interaction/focus/the-autofocus-attribute/queue-non-focusable.html20
-rw-r--r--testing/web-platform/tests/html/interaction/focus/the-autofocus-attribute/resources/child-autofocus.html20
-rw-r--r--testing/web-platform/tests/html/interaction/focus/the-autofocus-attribute/resources/child-iframe.html18
-rw-r--r--testing/web-platform/tests/html/interaction/focus/the-autofocus-attribute/resources/erase-first.css3
-rw-r--r--testing/web-platform/tests/html/interaction/focus/the-autofocus-attribute/resources/frame-with-a.html4
-rw-r--r--testing/web-platform/tests/html/interaction/focus/the-autofocus-attribute/resources/frame-with-anchor.html4
-rw-r--r--testing/web-platform/tests/html/interaction/focus/the-autofocus-attribute/resources/frame-with-autofocus-element.html5
-rw-r--r--testing/web-platform/tests/html/interaction/focus/the-autofocus-attribute/resources/grand-child-autofocus.html18
-rw-r--r--testing/web-platform/tests/html/interaction/focus/the-autofocus-attribute/resources/imagemap.html5
-rw-r--r--testing/web-platform/tests/html/interaction/focus/the-autofocus-attribute/resources/moving-autofocus-to-parent.html10
-rw-r--r--testing/web-platform/tests/html/interaction/focus/the-autofocus-attribute/resources/utils.js51
-rw-r--r--testing/web-platform/tests/html/interaction/focus/the-autofocus-attribute/same-origin-autofocus.html48
-rw-r--r--testing/web-platform/tests/html/interaction/focus/the-autofocus-attribute/skip-another-top-level-browsing-context.html17
-rw-r--r--testing/web-platform/tests/html/interaction/focus/the-autofocus-attribute/skip-non-focusable.html19
-rw-r--r--testing/web-platform/tests/html/interaction/focus/the-autofocus-attribute/skip-not-fully-active.html18
-rw-r--r--testing/web-platform/tests/html/interaction/focus/the-autofocus-attribute/spin-by-blocking-style-sheet.html19
-rw-r--r--testing/web-platform/tests/html/interaction/focus/the-autofocus-attribute/supported-elements.html83
-rw-r--r--testing/web-platform/tests/html/interaction/focus/the-autofocus-attribute/update-the-rendering.html40
151 files changed, 5563 insertions, 0 deletions
diff --git a/testing/web-platform/tests/html/interaction/focus/chrome-object-tab-focus-bug.html b/testing/web-platform/tests/html/interaction/focus/chrome-object-tab-focus-bug.html
new file mode 100644
index 0000000000..5127ee1cb6
--- /dev/null
+++ b/testing/web-platform/tests/html/interaction/focus/chrome-object-tab-focus-bug.html
@@ -0,0 +1,43 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Tabbing through object tag</title>
+<link rel="author" title="atotic@chromium.org">
+<link rel="help" href="https://crbug.com/1132895">
+<meta assert="assert" content="Tabbed focus works through OBJECT tags">
+<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>
+
+<p>Pressing TAB twice should focus/highlight end checkbox</p>
+
+<input type="checkbox" id="start">start</a>
+<object type='text/html' data='data:text/html,' width='16' height='16'></object>
+<input type="checkbox" id="end" >end</a>
+
+<script>
+
+let t = async_test("focus advances with tab key thorough object element");
+
+let start = document.querySelector("#start");
+let object = document.querySelector("object");
+let end = document.querySelector("#end");
+let tab = "\uE004";
+
+t.step( _ => {
+ document.querySelector("#start").focus();
+ assert_equals(document.activeElement, start, "start got focus");
+ test_driver.send_keys(document.activeElement, tab).then( _ => {
+ t.step( _ => {
+ assert_equals(document.activeElement, object, "object got focus");
+ test_driver.send_keys(document.activeElement, tab).then( _ => {
+ t.step( _ => {
+ assert_equals(document.activeElement, end, "end got focus");
+ t.done();
+ });
+ });
+ });
+ });
+});
+
+</script>
diff --git a/testing/web-platform/tests/html/interaction/focus/composed.window.js b/testing/web-platform/tests/html/interaction/focus/composed.window.js
new file mode 100644
index 0000000000..8951afc4e0
--- /dev/null
+++ b/testing/web-platform/tests/html/interaction/focus/composed.window.js
@@ -0,0 +1,16 @@
+async_test(t => {
+ const input = document.body.appendChild(document.createElement("input"));
+ let happened = false;
+ input.onfocus = t.step_func(e => {
+ happened = true;
+ assert_equals(e.type, "focus");
+ assert_true(e.composed);
+ });
+ input.focus();
+ input.onblur = t.step_func_done(e => {
+ assert_true(happened);
+ assert_equals(e.type, "blur");
+ assert_true(e.composed);
+ });
+ input.blur();
+}, "Focus events are composed");
diff --git a/testing/web-platform/tests/html/interaction/focus/document-level-focus-apis/document-has-system-focus.html b/testing/web-platform/tests/html/interaction/focus/document-level-focus-apis/document-has-system-focus.html
new file mode 100644
index 0000000000..26f0bbbd09
--- /dev/null
+++ b/testing/web-platform/tests/html/interaction/focus/document-level-focus-apis/document-has-system-focus.html
@@ -0,0 +1,44 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>HTML Test: focus - document has system focus</title>
+<link rel="help" href="https://html.spec.whatwg.org/#has-focus-steps">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<input id="input">
+<script>
+
+promise_test(async t => {
+ await new Promise(r => window.onload = r);
+ // This test requires the document to have focus as a starting condition.
+ // Whether a newly loaded page receives focus or not, seems to be somewhat
+ // browser-dependent and situation-dependent. For instance, Firefox appears to
+ // focus the page immediately if the page was loaded with the refresh button,
+ // but not if it was loaded from pressing ENTER in the URL bar. To ensure a
+ // reliable starting condition for this test, we give an extra push for focus.
+ if (!document.hasFocus()) {
+ const input = document.getElementById("input");
+ input.focus();
+ await new Promise(r => input.onfocus = r);
+ }
+ assert_true(document.hasFocus(), "Document has focus as starting condition.");
+
+ let gotBlur = false;
+ window.onblur = () => gotBlur = true;
+ const popup = window.open("support/popup.html", "otherwindow", "resizable");
+ assert_not_equals(popup, null, "Test requires popup be opened");
+ t.add_cleanup(() => popup.close());
+ const msg = await new Promise(r => window.onmessage = ({data}) => r(data));
+ assert_equals(msg, "focus = true",
+ "Test requires popups be focused (may require harness flags)");
+ assert_true(gotBlur, "Document received blur event when popup opened");
+ assert_false(document.hasFocus(), "Document lost focus when popup opened");
+
+ const p = new Promise(r => window.onfocus = r);
+ popup.close();
+ await p;
+ assert_true(true, "Document received focus event when popup closed");
+ assert_true(document.hasFocus(), "Document regained focus when popup closed");
+}, "Top-level document receives blur/focus events and loses system focus " +
+ "during opening/closing of a popup");
+
+</script>
diff --git a/testing/web-platform/tests/html/interaction/focus/document-level-focus-apis/document-level-apis.html b/testing/web-platform/tests/html/interaction/focus/document-level-focus-apis/document-level-apis.html
new file mode 100644
index 0000000000..4a8a9a291a
--- /dev/null
+++ b/testing/web-platform/tests/html/interaction/focus/document-level-focus-apis/document-level-apis.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>HTML Test: focus - document-level APIs</title>
+<link rel="author" title="Intel" href="http://www.intel.com/">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#document-level-focus-apis">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<input id="test">
+<script>
+
+test(function () {
+ assert_equals(document.activeElement, document.body, "The active element should be the body element.");
+}, "The body element must be the active element if no element is focused");
+
+test(function () {
+ document.getElementById("test").focus();
+ assert_equals(document.activeElement, document.getElementById("test"), "The active element should be the input element.");
+}, "The element must be the active element if it is focused");
+
+function frame_load () {
+ test(function () {
+ document.getElementById("fr").contentDocument.getElementById("ipt").focus();
+ assert_equals(document.activeElement, document.getElementById("fr"), "The active element should be the iframe element.");
+ }, "When a child browsing context is focused, its browsing context container is also focused");
+}
+
+test(function () {
+ var doc = document.implementation.createHTMLDocument("test");
+ assert_false(doc.hasFocus(), "The hasFocus() method should return false.");
+}, "The hasFocus() method must return false if the Document has no browsing context");
+
+</script>
+<iframe id="fr" src="support/test.html" onload="frame_load()"></iframe>
diff --git a/testing/web-platform/tests/html/interaction/focus/document-level-focus-apis/support/popup.html b/testing/web-platform/tests/html/interaction/focus/document-level-focus-apis/support/popup.html
new file mode 100644
index 0000000000..3a4419105a
--- /dev/null
+++ b/testing/web-platform/tests/html/interaction/focus/document-level-focus-apis/support/popup.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+Popup <input id="input">
+<script>
+window.onload = async () => {
+ try {
+ if (!document.hasFocus()) {
+ const input = document.getElementById("input");
+ input.focus();
+ await new Promise(r => input.onfocus = r);
+ }
+ opener.postMessage(`focus = ${document.hasFocus()}`, "*");
+ } catch(e) {
+ opener.postMessage(`${e.name}: $(e.message)`, "*");
+ }
+};
+</script>
+</html>
diff --git a/testing/web-platform/tests/html/interaction/focus/document-level-focus-apis/support/test.html b/testing/web-platform/tests/html/interaction/focus/document-level-focus-apis/support/test.html
new file mode 100644
index 0000000000..90d63e51e9
--- /dev/null
+++ b/testing/web-platform/tests/html/interaction/focus/document-level-focus-apis/support/test.html
@@ -0,0 +1,5 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>HTML Test: focus - document-level APIs</title>
+<link rel="author" title="Intel" href="http://www.intel.com/">
+<input id="ipt">
diff --git a/testing/web-platform/tests/html/interaction/focus/focus-01.html b/testing/web-platform/tests/html/interaction/focus/focus-01.html
new file mode 100644
index 0000000000..b8faca54d8
--- /dev/null
+++ b/testing/web-platform/tests/html/interaction/focus/focus-01.html
@@ -0,0 +1,45 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>HTML Test: focus - key events</title>
+<meta name="timeout" content="long">
+<link rel="author" title="Intel" href="http://www.intel.com/">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#focus">
+<meta assert="flag" content="interact">
+<meta assert="assert" content="Check if the key events received by document are targeted at the element when it is focused">
+<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>
+<div id="log"></div>
+<input id="test">
+<script>
+
+var t1 = async_test("The keydown event must be targeted at the input element"),
+ t2 = async_test("The keypress event must be targeted at the input element"),
+ t3 = async_test("The keyup event must be targeted at the input element"),
+ testEle;
+
+setup(function () {
+ testEle = document.getElementById("test");
+ testEle.focus();
+});
+
+document.onkeydown = t1.step_func_done(function(evt){
+ assert_equals(evt.target, testEle, "The keydown events must be targeted at the input element.");
+});
+
+document.onkeypress = t2.step_func_done(function(evt){
+ assert_equals(evt.target, testEle, "The keypress events must be targeted at the input element.");
+});
+
+document.onkeyup = t3.step_func_done(function(evt){
+ assert_equals(evt.target, testEle, "The keyup events must be targeted at the input element.");
+});
+
+var input_element = document.getElementById("test");
+
+t1.step(function() {
+ test_driver.send_keys(input_element, "a");
+});
+
+</script>
diff --git a/testing/web-platform/tests/html/interaction/focus/focus-02.html b/testing/web-platform/tests/html/interaction/focus/focus-02.html
new file mode 100644
index 0000000000..2e765ca152
--- /dev/null
+++ b/testing/web-platform/tests/html/interaction/focus/focus-02.html
@@ -0,0 +1,38 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>HTML Test: focus - key events</title>
+<meta name="timeout" content="long">
+<link rel="author" title="Intel" href="http://www.intel.com/">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#focus">
+<meta assert="flag" content="interact">
+<meta assert="assert" content="Check if the key events received by document are targeted at the element when no element is focused">
+<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>
+<div id="log"></div>
+<script>
+
+var t1 = async_test("The keydown event must be targeted at the body element"),
+ t2 = async_test("The keypress event must be targeted at the body element"),
+ t3 = async_test("The keyup event must be targeted at the body element");
+
+setup({timeout: 10000});
+
+document.onkeydown = t1.step_func_done(function(evt){
+ assert_equals(evt.target, document.body, "The keydown events must be targeted at the document's body.");
+});
+
+document.onkeypress = t2.step_func_done(function(evt){
+ assert_equals(evt.target, document.body, "The keypress events must be targeted at the document's body.");
+});
+
+document.onkeyup = t3.step_func_done(function(evt){
+ assert_equals(evt.target, document.body, "The keyup events must be targeted at the document's body.");
+});
+
+t1.step(function() {
+ test_driver.send_keys(document.body, "a");
+});
+
+</script>
diff --git a/testing/web-platform/tests/html/interaction/focus/focus-file-input.html b/testing/web-platform/tests/html/interaction/focus/focus-file-input.html
new file mode 100644
index 0000000000..d3007112ff
--- /dev/null
+++ b/testing/web-platform/tests/html/interaction/focus/focus-file-input.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>HTML Test: file input can be programatically focused before layout</title>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#focus">
+<link rel="help" href="https://bugzilla.mozilla.org/show_bug.cgi?id=505355">
+<link rel="author" title="Mozilla" href="https://mozilla.org/">
+<link rel="author" title="Emilio Cobos Álvarez" href="mailto:emilio@crisal.io">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<input type="file">
+<script>
+test(function() {
+ let input = document.querySelector("input");
+ assert_not_equals(document.activeElement, input);
+ input.focus();
+ assert_equals(document.activeElement, input);
+});
+</script>
diff --git a/testing/web-platform/tests/html/interaction/focus/focus-input-type-switch.html b/testing/web-platform/tests/html/interaction/focus/focus-input-type-switch.html
new file mode 100644
index 0000000000..eeebe488bf
--- /dev/null
+++ b/testing/web-platform/tests/html/interaction/focus/focus-input-type-switch.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Inputs remain focusable upon changing type</title>
+<link rel="help" href="https://wicg.github.io/auxclick">
+<link rel="help" href="https://bugzilla.mozilla.org/show_bug.cgi?id=981248">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-actions.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<h1>Can still focus on inputs that change types</h1>
+<input type="text" value="123" onfocus="javascript:event.target.type='number'"
+ onblur="javascript:event.target.type='text'">
+<script>
+promise_test(() => {
+ // Click the input to attempt to focus on it
+ const target = document.querySelector("input");
+ const actions = new test_driver.Actions();
+ return actions.pointerMove(0, 0, {origin: target})
+ .pointerDown({button: actions.ButtonType.LEFT})
+ .pointerUp({button: actions.ButtonType.LEFT})
+ .send()
+ .then(() => assert_equals(document.activeElement, target,
+ "The element was correctly focused"));
+}, "Can change an input's type during focus handler without breaking focus");
+</script>
diff --git a/testing/web-platform/tests/html/interaction/focus/focus-keyboard-js.html b/testing/web-platform/tests/html/interaction/focus/focus-keyboard-js.html
new file mode 100644
index 0000000000..8a7242f37e
--- /dev/null
+++ b/testing/web-platform/tests/html/interaction/focus/focus-keyboard-js.html
@@ -0,0 +1,26 @@
+<!doctype html>
+<link rel="author" title="Emilio Cobos Álvarez" href="mailto:emilio@crisal.io">
+<link rel="author" title="Mozilla" href="https://mozilla.org">
+<link rel="help" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1712724">
+<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>
+<input type=text id=text value=abc>
+<script>
+let text = document.getElementById("text");
+
+document.addEventListener("keyup", function(e) {
+ text.focus();
+});
+
+promise_test(async t => {
+ await test_driver.send_keys(document.body, " ");
+
+ assert_equals(document.activeElement, text, "#text should be focused by our event listener");
+ assert_true(text.matches(":focus"), "#text should match :focus");
+ assert_true(text.matches(":focus-visible"), "#text should match :focus-visible");
+ assert_equals(text.selectionStart, text.selectionEnd, "#text should not be selected");
+ assert_equals(text.value, "abc", "#text should not have changed value");
+});
+</script>
diff --git a/testing/web-platform/tests/html/interaction/focus/focus-management/focus-event-targets-simple.html b/testing/web-platform/tests/html/interaction/focus/focus-management/focus-event-targets-simple.html
new file mode 100644
index 0000000000..ab7bcfe6d0
--- /dev/null
+++ b/testing/web-platform/tests/html/interaction/focus/focus-management/focus-event-targets-simple.html
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Focus events fire at correct targets in correct order in simple case</title>
+ <link rel="author" title="Chris Rebert" href="http://chrisrebert.com">
+ <link rel="help" href="https://html.spec.whatwg.org/#focus-update-steps">
+ <link rel="help" href="https://html.spec.whatwg.org/#focus-chain">
+ <meta name="flags" content="dom">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+ <input type="text" id="a">
+ <script>
+// Record all the focus event targets in an array.
+// Modulo special cases in the "focus update steps" algorithm,
+// this should be the same as the new focus chain, except in reverse order.
+var newFocusChainReversedNotQuite = [];
+var pushTarget = function (e) {
+ newFocusChainReversedNotQuite.push(e.target);
+};
+// Window is the root node for event dispatch per https://html.spec.whatwg.org/multipage/webappapis.html#events-and-the-window-object
+window.addEventListener('focus', pushTarget, true);// Use event capturing since focus event doesn't bubble
+var input = document.getElementById('a');
+input.focus();
+window.removeEventListener('focus', pushTarget, true);
+test(function() {
+ assert_array_equals(newFocusChainReversedNotQuite, [input], "Exactly 1 focus event should fire and its target should be the input");
+}, "Focus events fire at correct targets in correct order in simple case");
+ </script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/interaction/focus/focus-management/focus-events.html b/testing/web-platform/tests/html/interaction/focus/focus-management/focus-events.html
new file mode 100644
index 0000000000..5f8ca20a13
--- /dev/null
+++ b/testing/web-platform/tests/html/interaction/focus/focus-management/focus-events.html
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Focus management</title>
+<link rel="author" title="Denis Ah-Kang" href="mailto:denis@w3.org">
+<link rel=help href="https://html.spec.whatwg.org/multipage/#focus-management">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<input type=text id=i1>
+<input type=text id=i2>
+<script>
+ var i1 = document.getElementById('i1'),
+ i2 = document.getElementById('i2'),
+ t1 = async_test("focusing on a focusable element fires a focus event at the element"),
+ t2 = async_test("focusing on a focusable element fires a blur event at the previous focussed element");
+
+ i2.onfocus = t1.step_func_done(function(e){
+ assert_true(e.isTrusted, "focus event is trusted");
+ assert_false(e.bubbles, "focus event doesn't bubble");
+ assert_false(e.cancelable, "focus event is not cancelable");
+ assert_equals(document.activeElement, i2);
+ });
+
+ i1.onblur = t2.step_func_done(function(e){
+ assert_true(e.isTrusted, "blur event is trusted");
+ assert_false(e.bubbles, "blur event doesn't bubble");
+ assert_false(e.cancelable, "blur event is not cancelable");
+ assert_equals(document.activeElement, document.body);
+ });
+
+ i1.focus();
+ i2.focus();
+</script>
diff --git a/testing/web-platform/tests/html/interaction/focus/focused-element-move-documents-crash.html b/testing/web-platform/tests/html/interaction/focus/focused-element-move-documents-crash.html
new file mode 100644
index 0000000000..ca9ab26edd
--- /dev/null
+++ b/testing/web-platform/tests/html/interaction/focus/focused-element-move-documents-crash.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<link rel="author" title="Joey Arhar" href="mailto:jarhar@chromium.org">
+<link rel="help" href="https://bugs.chromium.org/p/chromium/issues/detail?id=1172828">
+
+<!-- No crash should occur if a focused element is moved to another document. -->
+
+<div id=editable contenteditable=>
+
+<script>
+editable.addEventListener('blur', function() {
+ document.execCommand('InsertText', false, '\t');
+});
+editable.focus();
+const secondDoc = document.implementation.createDocument('', null);
+secondDoc.appendChild(editable);
+</script>
diff --git a/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/backward-navigation/descends-into-extending-focusgroup.html b/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/backward-navigation/descends-into-extending-focusgroup.html
new file mode 100644
index 0000000000..2590bc3250
--- /dev/null
+++ b/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/backward-navigation/descends-into-extending-focusgroup.html
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>HTML Test: focusgroup - Focus moves to previous item within extending focusgroup.</title>
+<link rel="author" title="Microsoft" href="http://www.microsoft.com/">
+<link rel="help" href="https://github.com/MicrosoftEdge/MSEdgeExplainers/blob/main/Focusgroup/explainer.md">
+<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="../resources/focusgroup-utils.js"></script>
+
+<div focusgroup=wrap>
+ <span id=item1 tabindex=0>item1</span>
+ <div>
+ <div focusgroup=extend>
+ <span id=item2 tabindex=-1>item2</span>
+ <span id=item3 tabindex=-1>item3</span>
+ </div>
+ </div>
+ <span id=item4 tabindex=-1>item4</span>
+</div>
+
+<script>
+
+ promise_test(async t => {
+ var item3 = document.getElementById("item3");
+ var item4 = document.getElementById("item4");
+
+ await focusAndKeyPress(item4, kArrowUp);
+ assert_equals(document.activeElement, item3);
+
+ await focusAndKeyPress(item4, kArrowLeft);
+ assert_equals(document.activeElement, item3);
+ }, "When the focus is set on the last item of a focusgroup and the previous item is a descendant of a subtree, a backward arrow key press should move the focus to that previous item within the subtree.");
+
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/backward-navigation/does-not-move-when-on-focusgroup-root.html b/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/backward-navigation/does-not-move-when-on-focusgroup-root.html
new file mode 100644
index 0000000000..3dd9cf2da5
--- /dev/null
+++ b/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/backward-navigation/does-not-move-when-on-focusgroup-root.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>HTML Test: focusgroup - Focus does not move when initially set on the focusgroup root.</title>
+<link rel="author" title="Microsoft" href="http://www.microsoft.com/">
+<link rel="help" href="https://github.com/MicrosoftEdge/MSEdgeExplainers/blob/main/Focusgroup/explainer.md">
+<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="../resources/focusgroup-utils.js"></script>
+
+<div id=root tabindex=-1 focusgroup>
+ <span id=item1 tabindex=0>item1</span>
+ <span id=item2 tabindex=-1>item2</span>
+</div>
+
+<script>
+
+ promise_test(async t => {
+ var root = document.getElementById("root");
+
+ await focusAndKeyPress(root, kArrowUp);
+ assert_equals(document.activeElement, root);
+
+ await focusAndKeyPress(root, kArrowLeft);
+ assert_equals(document.activeElement, root);
+ }, "When the focus is set on the root of a focusgroup element, an arrow key press shouldn't move the focus at all.");
+
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/backward-navigation/does-not-move-when-on-non-focusgroup-item.html b/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/backward-navigation/does-not-move-when-on-non-focusgroup-item.html
new file mode 100644
index 0000000000..0ec7827664
--- /dev/null
+++ b/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/backward-navigation/does-not-move-when-on-non-focusgroup-item.html
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>HTML Test: focusgroup - Focus does not move when initially set a focusable element that isn't a focusgroup item.</title>
+<link rel="author" title="Microsoft" href="http://www.microsoft.com/">
+<link rel="help" href="https://github.com/MicrosoftEdge/MSEdgeExplainers/blob/main/Focusgroup/explainer.md">
+<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="../resources/focusgroup-utils.js"></script>
+
+<div focusgroup>
+ <span id=item1 tabindex=0>item1</span>
+ <span id=item2 tabindex=-1>item2</span>
+ <div>
+ <span id=nonitem1 tabindex=0>nonitem1</span>
+ </div>
+</div>
+
+<script>
+
+ promise_test(async t => {
+ var nonitem1 = document.getElementById("nonitem1");
+
+ await focusAndKeyPress(nonitem1, kArrowUp);
+ assert_equals(document.activeElement, nonitem1);
+
+ await focusAndKeyPress(nonitem1, kArrowLeft);
+ assert_equals(document.activeElement, nonitem1);
+ }, "When the focus is set on a focusable element that isn't a focusgroup item, an arrow key press shouldn't move the focus at all.");
+
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/backward-navigation/does-not-move-when-only-one-item-and-wraps.html b/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/backward-navigation/does-not-move-when-only-one-item-and-wraps.html
new file mode 100644
index 0000000000..475acdc7f1
--- /dev/null
+++ b/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/backward-navigation/does-not-move-when-only-one-item-and-wraps.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>HTML Test: focusgroup - Focus does not move when there is only one item, even though it wraps.</title>
+<link rel="author" title="Microsoft" href="http://www.microsoft.com/">
+<link rel="help" href="https://github.com/MicrosoftEdge/MSEdgeExplainers/blob/main/Focusgroup/explainer.md">
+<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="../resources/focusgroup-utils.js"></script>
+
+<div focusgroup=wrap>
+ <span id=item1 tabindex=0>item1</span>
+</div>
+
+<script>
+
+ promise_test(async t => {
+ var item1 = document.getElementById("item1");
+
+ await focusAndKeyPress(item1, kArrowUp);
+ assert_equals(document.activeElement, item1);
+
+ await focusAndKeyPress(item1, kArrowLeft);
+ assert_equals(document.activeElement, item1);
+ }, "When the focus is set on the only focusgroup item and the focusgroup wraps in the axis of the arrow key pressed, the focus shouldn't move and we shouldn't get stuck in an infinite loop.");
+
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/backward-navigation/does-not-move-when-only-one-item.html b/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/backward-navigation/does-not-move-when-only-one-item.html
new file mode 100644
index 0000000000..f046769459
--- /dev/null
+++ b/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/backward-navigation/does-not-move-when-only-one-item.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>HTML Test: focusgroup - Focus does not move when there is only one item.</title>
+<link rel="author" title="Microsoft" href="http://www.microsoft.com/">
+<link rel="help" href="https://github.com/MicrosoftEdge/MSEdgeExplainers/blob/main/Focusgroup/explainer.md">
+<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="../resources/focusgroup-utils.js"></script>
+
+<div focusgroup>
+ <span id=item1 tabindex=0>item1</span>
+</div>
+
+<script>
+
+ promise_test(async t => {
+ var item1 = document.getElementById("item1");
+
+ await focusAndKeyPress(item1, kArrowUp);
+ assert_equals(document.activeElement, item1);
+
+ await focusAndKeyPress(item1, kArrowLeft);
+ assert_equals(document.activeElement, item1);
+ }, "When the focus is set on the only focusgroup item, the focus shouldn't move and we shouldn't get stuck in an infinite loop.");
+
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/backward-navigation/does-not-move-when-outside-focusgroup.html b/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/backward-navigation/does-not-move-when-outside-focusgroup.html
new file mode 100644
index 0000000000..78ee0dadeb
--- /dev/null
+++ b/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/backward-navigation/does-not-move-when-outside-focusgroup.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>HTML Test: focusgroup - Focus does not move when initially set on an element not included in the focusgroup.</title>
+<link rel="author" title="Microsoft" href="http://www.microsoft.com/">
+<link rel="help" href="https://github.com/MicrosoftEdge/MSEdgeExplainers/blob/main/Focusgroup/explainer.md">
+<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="../resources/focusgroup-utils.js"></script>
+
+<div focusgroup>
+ <span id=item1 tabindex=0>item1</span>
+ <span id=item2 tabindex=-1>item2</span>
+</div>
+<span id=out tabindex=-1>out</span>
+
+<script>
+
+ promise_test(async t => {
+ var out = document.getElementById("out");
+
+ await focusAndKeyPress(out, kArrowUp);
+ assert_equals(document.activeElement, out);
+
+ await focusAndKeyPress(out, kArrowLeft);
+ assert_equals(document.activeElement, out);
+ }, "When the focus is set on an element outside of the focusgroup, an arrow keypress shouldn't move the focus at all.");
+
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/backward-navigation/does-not-wrap-when-not-supported.html b/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/backward-navigation/does-not-wrap-when-not-supported.html
new file mode 100644
index 0000000000..742daedfef
--- /dev/null
+++ b/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/backward-navigation/does-not-wrap-when-not-supported.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>HTML Test: focusgroup - Focus does not wrap when 'wrap' not specified.</title>
+<link rel="author" title="Microsoft" href="http://www.microsoft.com/">
+<link rel="help" href="https://github.com/MicrosoftEdge/MSEdgeExplainers/blob/main/Focusgroup/explainer.md">
+<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="../resources/focusgroup-utils.js"></script>
+
+<div focusgroup>
+ <span id=item1 tabindex=0>item1</span>
+ <span id=item2 tabindex=-1>item2</span>
+</div>
+
+<script>
+
+ promise_test(async t => {
+ var item1 = document.getElementById("item1");
+
+ await focusAndKeyPress(item1, kArrowUp);
+ assert_equals(document.activeElement, item1);
+
+ await focusAndKeyPress(item1, kArrowLeft);
+ assert_equals(document.activeElement, item1);
+ }, "When the focus is set on the first item of a focusgroup, a backward arrow key press shouldn't move the focus since there aren't any previous item.");
+
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/backward-navigation/horizontal/ascends-to-parent-focusgroup.html b/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/backward-navigation/horizontal/ascends-to-parent-focusgroup.html
new file mode 100644
index 0000000000..00ef3db90c
--- /dev/null
+++ b/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/backward-navigation/horizontal/ascends-to-parent-focusgroup.html
@@ -0,0 +1,36 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>HTML Test: focusgroup - Focus ascends to parent focusgroup successfully.</title>
+<link rel="author" title="Microsoft" href="http://www.microsoft.com/">
+<link rel="help" href="https://github.com/MicrosoftEdge/MSEdgeExplainers/blob/main/Focusgroup/explainer.md">
+<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="../../resources/focusgroup-utils.js"></script>
+
+<div focusgroup=horizontal>
+ <span id=item1 tabindex=0>item1</span>
+ <div id=item2 tabindex=-1>
+ <div>
+ <div focusgroup="extend vertical">
+ <span id=item3 tabindex=-1>item3</span>
+ <span id=item4 tabindex=-1>item4</span>
+ </div>
+ </div>
+ </div>
+ <span id=item5 tabindex=-1>item5</span>
+</div>
+
+<script>
+
+ promise_test(async t => {
+ var item2 = document.getElementById("item2");
+ var item3 = document.getElementById("item3");
+
+ await focusAndKeyPress(item3, kArrowLeft);
+ assert_equals(document.activeElement, item2);
+ }, "When the focus is set on the first item of an extending focusgroup that doesn't support the axis of the arrow key pressed but the parent focusgroup does, ascend to that focusgroup. This should work whether the extending focusgroup is the child of the other focusgroup or a distant descendant.");
+
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/backward-navigation/horizontal/does-not-ascend-out-of-non-extending-focusgroup.html b/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/backward-navigation/horizontal/does-not-ascend-out-of-non-extending-focusgroup.html
new file mode 100644
index 0000000000..b00958719d
--- /dev/null
+++ b/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/backward-navigation/horizontal/does-not-ascend-out-of-non-extending-focusgroup.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>HTML Test: focusgroup - Focus does not ascend out of current focusgroup if it does not extend the parent focusgroup.</title>
+<link rel="author" title="Microsoft" href="http://www.microsoft.com/">
+<link rel="help" href="https://github.com/MicrosoftEdge/MSEdgeExplainers/blob/main/Focusgroup/explainer.md">
+<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="../../resources/focusgroup-utils.js"></script>
+
+<ul focusgroup=horizontal>
+ <li id=item1 tabindex="-1">
+ <ul focusgroup=vertical>
+ <li id=item2 tabindex="-1">item2</li>
+ </ul>
+ </li>
+</ul>
+
+<script>
+
+ promise_test(async t => {
+ var item2 = document.getElementById("item2");
+
+ await focusAndKeyPress(item2, kArrowLeft);
+ assert_equals(document.activeElement, item2);
+ }, "When the focus is set on the first element of a non-extending focusgroup located inside another focusgroup, we should be able to ascend to that other focusgroup.");
+
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/backward-navigation/horizontal/does-not-move-when-axis-not-supported.html b/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/backward-navigation/horizontal/does-not-move-when-axis-not-supported.html
new file mode 100644
index 0000000000..e671a25844
--- /dev/null
+++ b/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/backward-navigation/horizontal/does-not-move-when-axis-not-supported.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>HTML Test: focusgroup - Focus does not move to previous focusgroup item when the axis of the arrow key pressed isn't supported.</title>
+<link rel="author" title="Microsoft" href="http://www.microsoft.com/">
+<link rel="help" href="https://github.com/MicrosoftEdge/MSEdgeExplainers/blob/main/Focusgroup/explainer.md">
+<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="../../resources/focusgroup-utils.js"></script>
+
+<div id=root focusgroup=vertical>
+ <span id=item1 tabindex=0>item1</span>
+ <span id=item2 tabindex=-1>item2</span>
+</div>
+
+<script>
+
+ promise_test(async t => {
+ var item2 = document.getElementById("item2");
+
+ await focusAndKeyPress(item2, kArrowLeft);
+ assert_equals(document.activeElement, item2);
+ }, "When the focus is set on the last element of a focusgroup that only supports the orthogonal axis of the arrow key pressed, the focus shouldn't move.");
+
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/backward-navigation/horizontal/does-not-wrap-in-orthogonal-axis.html b/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/backward-navigation/horizontal/does-not-wrap-in-orthogonal-axis.html
new file mode 100644
index 0000000000..de09fa813c
--- /dev/null
+++ b/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/backward-navigation/horizontal/does-not-wrap-in-orthogonal-axis.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>HTML Test: focusgroup - Focus does not wrap in the arrow key pressed orthogonal axis.</title>
+<link rel="author" title="Microsoft" href="http://www.microsoft.com/">
+<link rel="help" href="https://github.com/MicrosoftEdge/MSEdgeExplainers/blob/main/Focusgroup/explainer.md">
+<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="../../resources/focusgroup-utils.js"></script>
+
+<div focusgroup="vertical wrap">
+ <span id=item1 tabindex=0>item1</span>
+ <span id=item2 tabindex=-1>item2</span>
+ <span id=item3 tabindex=-1>item3</span>
+</div>
+
+<script>
+
+ promise_test(async t => {
+ var item1 = document.getElementById("item1");
+
+ await focusAndKeyPress(item1, kArrowLeft);
+ assert_equals(document.activeElement, item1);
+ }, "When the focus is set on the first item of a focusgroup that wraps and supports only the orthogonal axis of the pressed arrow key, a backward arrow key press shouldn't move the focus.");
+
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/backward-navigation/horizontal/moves-when-only-current-axis-supported.html b/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/backward-navigation/horizontal/moves-when-only-current-axis-supported.html
new file mode 100644
index 0000000000..12f7934866
--- /dev/null
+++ b/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/backward-navigation/horizontal/moves-when-only-current-axis-supported.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>HTML Test: focusgroup - Focus moves to previous focusgroup item when only the axis of the arrow key pressed is supported.</title>
+<link rel="author" title="Microsoft" href="http://www.microsoft.com/">
+<link rel="help" href="https://github.com/MicrosoftEdge/MSEdgeExplainers/blob/main/Focusgroup/explainer.md">
+<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="../../resources/focusgroup-utils.js"></script>
+
+<div id=root focusgroup=horizontal>
+ <span id=item1 tabindex=0>item1</span>
+ <span id=item2 tabindex=-1>item2</span>
+</div>
+
+<script>
+
+ promise_test(async t => {
+ var item1 = document.getElementById("item1");
+ var item2 = document.getElementById("item2");
+
+ await focusAndKeyPress(item2, kArrowLeft);
+ assert_equals(document.activeElement, item1);
+ }, "When the focus is set on an item of a focusgroup that only supports the axis of the arrow key pressed the focus should move.");
+
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/backward-navigation/horizontal/skips-focusgroup-that-extends-in-orthogonal-axis-complex-case.html b/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/backward-navigation/horizontal/skips-focusgroup-that-extends-in-orthogonal-axis-complex-case.html
new file mode 100644
index 0000000000..2fadddac27
--- /dev/null
+++ b/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/backward-navigation/horizontal/skips-focusgroup-that-extends-in-orthogonal-axis-complex-case.html
@@ -0,0 +1,38 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>HTML Test: focusgroup - Focus moves to previous item, skipping the focusgroup that extends in the orthogonal axis (complex case).</title>
+<link rel="author" title="Microsoft" href="http://www.microsoft.com/">
+<link rel="help" href="https://github.com/MicrosoftEdge/MSEdgeExplainers/blob/main/Focusgroup/explainer.md">
+<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="../../resources/focusgroup-utils.js"></script>
+
+<div focusgroup=wrap>
+ <span id=item1 tabindex=0>item1</span>
+ <div>
+ <div focusgroup="extend vertical">
+ <div id=item2 tabindex=-1>
+ <div focusgroup=extend>
+ <span id=item3 tabindex=-1>item3</span>
+ <span id=item4 tabindex=-1>item4</span>
+ </div>
+ </div>
+ </div>
+ </div>
+ <span id=item5 tabindex=-1>item5</span>
+</div>
+
+<script>
+
+ promise_test(async t => {
+ var item1 = document.getElementById("item1");
+ var item5 = document.getElementById("item5");
+
+ await focusAndKeyPress(item5, kArrowLeft);
+ assert_equals(document.activeElement, item1);
+ }, "When the focus is set on the last item of a focusgroup and the previous item is located past an extending focusgroup that only supports the orthogonal axis, a backward arrow key press should move the focus to that previous item without getting stuck in the inner focusgroup that doesn't support the axis. The same should still be true when inside a focusgroup that extends another extending focusgroup that supports only the orthogonal axis within the original focusgroup.");
+
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/backward-navigation/horizontal/skips-focusgroup-that-extends-in-orthogonal-axis.html b/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/backward-navigation/horizontal/skips-focusgroup-that-extends-in-orthogonal-axis.html
new file mode 100644
index 0000000000..44dace8461
--- /dev/null
+++ b/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/backward-navigation/horizontal/skips-focusgroup-that-extends-in-orthogonal-axis.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>HTML Test: focusgroup - Focus moves to previous item, skipping the focusgroup that extends in the orthogonal axis.</title>
+<link rel="author" title="Microsoft" href="http://www.microsoft.com/">
+<link rel="help" href="https://github.com/MicrosoftEdge/MSEdgeExplainers/blob/main/Focusgroup/explainer.md">
+<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="../../resources/focusgroup-utils.js"></script>
+
+<div focusgroup=wrap>
+ <span id=item1 tabindex=0>item1</span>
+ <div>
+ <div focusgroup="extending vertical">
+ <span id=item2 tabindex=-1>item2</span>
+ <span id=item3 tabindex=-1>item3</span>
+ </div>
+ </div>
+ <span id=item4 tabindex=-1>item4</span>
+</div>
+
+<script>
+
+ promise_test(async t => {
+ var item1 = document.getElementById("item1");
+ var item4 = document.getElementById("item4");
+
+ await focusAndKeyPress(item4, kArrowLeft);
+ assert_equals(document.activeElement, item1);
+ }, "When the focus is set on the last item of a focusgroup and the previous item is a descendant of a subtree, a backward arrow key press should move the focus to that previous item within the subtree. However, if that subtree is an extending focusgroup that supports only the orthogonal axis, it should be skipped.");
+
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/backward-navigation/horizontal/wraps-in-axis.html b/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/backward-navigation/horizontal/wraps-in-axis.html
new file mode 100644
index 0000000000..956d3f6406
--- /dev/null
+++ b/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/backward-navigation/horizontal/wraps-in-axis.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>HTML Test: focusgroup - Focus wraps in the arrow key pressed axis.</title>
+<link rel="author" title="Microsoft" href="http://www.microsoft.com/">
+<link rel="help" href="https://github.com/MicrosoftEdge/MSEdgeExplainers/blob/main/Focusgroup/explainer.md">
+<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="../../resources/focusgroup-utils.js"></script>
+
+<div focusgroup="horizontal wrap">
+ <span id=item1 tabindex=0>item1</span>
+ <span id=item2 tabindex=-1>item2</span>
+ <span id=item3 tabindex=-1>item3</span>
+</div>
+
+<script>
+
+ promise_test(async t => {
+ var item1 = document.getElementById("item1");
+ var item3 = document.getElementById("item3");
+
+ await focusAndKeyPress(item1, kArrowLeft);
+ assert_equals(document.activeElement, item3);
+ }, "When the focus is set on the first item of a focusgroup that wraps and supports only the axis of the pressed arrow key, a backward arrow key press should move the focus to the last item within the focusgroup.");
+
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/backward-navigation/moves-to-previous-item-and-skips-focusable-item.html b/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/backward-navigation/moves-to-previous-item-and-skips-focusable-item.html
new file mode 100644
index 0000000000..9bab38a24d
--- /dev/null
+++ b/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/backward-navigation/moves-to-previous-item-and-skips-focusable-item.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>HTML Test: focusgroup - Focus moves to previous item and skips non-focusable elements.</title>
+<link rel="author" title="Microsoft" href="http://www.microsoft.com/">
+<link rel="help" href="https://github.com/MicrosoftEdge/MSEdgeExplainers/blob/main/Focusgroup/explainer.md">
+<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="../resources/focusgroup-utils.js"></script>
+
+<div focusgroup>
+ <span id=item1 tabindex=0>item1</span>
+ <span id=item2>item2</span> <!--NOT FOCUSABLE-->
+ <span id=item3 tabindex=-1>item3</span>
+</div>
+
+<script>
+
+ promise_test(async t => {
+ var item1 = document.getElementById("item1");
+ var item3 = document.getElementById("item3");
+
+ await focusAndKeyPress(item3, kArrowUp);
+ assert_equals(document.activeElement, item1);
+
+ await focusAndKeyPress(item3, kArrowLeft);
+ assert_equals(document.activeElement, item1);
+ }, "When the focus is set on a focusgroup item, an arrow key press should move the focus to the previous item and skip non-focusable items.");
+
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/backward-navigation/moves-to-previous-item.html b/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/backward-navigation/moves-to-previous-item.html
new file mode 100644
index 0000000000..228a4aad63
--- /dev/null
+++ b/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/backward-navigation/moves-to-previous-item.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>HTML Test: focusgroup - Focus moves to previous focusgroup item.</title>
+<link rel="author" title="Microsoft" href="http://www.microsoft.com/">
+<link rel="help" href="https://github.com/MicrosoftEdge/MSEdgeExplainers/blob/main/Focusgroup/explainer.md">
+<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="../resources/focusgroup-utils.js"></script>
+
+<div focusgroup>
+ <span id=item1 tabindex=0>item1</span>
+ <span id=item2 tabindex=-1>item2</span>
+</div>
+
+<script>
+
+ promise_test(async t => {
+ var item1 = document.getElementById("item1");
+ var item2 = document.getElementById("item2");
+
+ await focusAndKeyPress(item2, kArrowUp);
+ assert_equals(document.activeElement, item1);
+
+ await focusAndKeyPress(item2, kArrowLeft);
+ assert_equals(document.activeElement, item1);
+ }, "When the focus is set on a focusgroup item, an arrow key press should move the focus to the previous item.");
+
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/backward-navigation/skips-empty-wrapping-focusgroup.html b/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/backward-navigation/skips-empty-wrapping-focusgroup.html
new file mode 100644
index 0000000000..fb0b66e30e
--- /dev/null
+++ b/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/backward-navigation/skips-empty-wrapping-focusgroup.html
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>HTML Test: focusgroup - Focus moves to previous item, skipping over an empty extending focusgroup that wraps on itself.</title>
+<link rel="author" title="Microsoft" href="http://www.microsoft.com/">
+<link rel="help" href="https://github.com/MicrosoftEdge/MSEdgeExplainers/blob/main/Focusgroup/explainer.md">
+<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="../resources/focusgroup-utils.js"></script>
+
+<div focusgroup=wrap>
+ <span id=item1 tabindex=0>item1</span>
+ <div>
+ <div focusgroup="extend wrap">
+ <span id=item2>item2</span> <!-- Not focusable -->
+ <span id=item3>item3</span> <!-- Not focusable -->
+ </div>
+ </div>
+ <span id=item4 tabindex=-1>item4</span>
+</div>
+
+<script>
+
+ promise_test(async t => {
+ var item1 = document.getElementById("item1");
+ var item4 = document.getElementById("item4");
+
+ await focusAndKeyPress(item4, kArrowUp);
+ assert_equals(document.activeElement, item1);
+
+ await focusAndKeyPress(item4, kArrowLeft);
+ assert_equals(document.activeElement, item1);
+ }, "When the focus is set on the last item of a focusgroup and the previous item is located past an extending focusgroup that wraps but has no item in it, a backward arrow key press should move the focus to that previous item without getting stuck in the inner focusgroup.");
+
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/backward-navigation/skips-non-focusgroup-subtree.html b/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/backward-navigation/skips-non-focusgroup-subtree.html
new file mode 100644
index 0000000000..140950202b
--- /dev/null
+++ b/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/backward-navigation/skips-non-focusgroup-subtree.html
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>HTML Test: focusgroup - Focus moves to previous item, skipping over a subtree that isn't an extending focusgroup.</title>
+<link rel="author" title="Microsoft" href="http://www.microsoft.com/">
+<link rel="help" href="https://github.com/MicrosoftEdge/MSEdgeExplainers/blob/main/Focusgroup/explainer.md">
+<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="../resources/focusgroup-utils.js"></script>
+
+<div focusgroup=wrap>
+ <span id=item1 tabindex=0>item1</span>
+ <div>
+ <span id=item2 tabindex=-1>item2</span>
+ <span id=item3 tabindex=-1>item3</span>
+ </div>
+ <span id=item4 tabindex=-1>item4</span>
+</div>
+
+<script>
+
+ promise_test(async t => {
+ var item1 = document.getElementById("item1");
+ var item4 = document.getElementById("item4");
+
+ await focusAndKeyPress(item4, kArrowUp);
+ assert_equals(document.activeElement, item1);
+
+ await focusAndKeyPress(item4, kArrowLeft);
+ assert_equals(document.activeElement, item1);
+ }, "When the focus is set on the last item of a focusgroup and the previous item is located past a non-focusgroup subtree, a backward arrow key press should move the focus to that previous item without getting stuck in the subtree.");
+
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/backward-navigation/skips-root-focusgroup-complex-case.html b/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/backward-navigation/skips-root-focusgroup-complex-case.html
new file mode 100644
index 0000000000..79731e7b2a
--- /dev/null
+++ b/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/backward-navigation/skips-root-focusgroup-complex-case.html
@@ -0,0 +1,41 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>HTML Test: focusgroup - Focus moves to previous item and skips focusgroup root subtree (complex case).</title>
+<link rel="author" title="Microsoft" href="http://www.microsoft.com/">
+<link rel="help" href="https://github.com/MicrosoftEdge/MSEdgeExplainers/blob/main/Focusgroup/explainer.md">
+<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="../resources/focusgroup-utils.js"></script>
+
+<div focusgroup=wrap>
+ <span id=item1 tabindex=0>item1</span>
+ <div>
+ <div focusgroup>
+ <div id=item2 tabindex=-1>
+ <div focusgroup=extend>
+ <span id=item3 tabindex=-1>item3</span>
+ <span id=item4 tabindex=-1>item4</span>
+ </div>
+ </div>
+ </div>
+ </div>
+ <span id=item5 tabindex=-1>item5</span>
+</div>
+
+<script>
+
+ promise_test(async t => {
+ var item1 = document.getElementById("item1");
+ var item5 = document.getElementById("item5");
+
+ await focusAndKeyPress(item5, kArrowUp);
+ assert_equals(document.activeElement, item1);
+
+ await focusAndKeyPress(item5, kArrowLeft);
+ assert_equals(document.activeElement, item1);
+ }, "When the focus is set on the last item of a focusgroup and the previous item is located past an other (non-extending) focusgroup subtree, a backward arrow key press should move the focus to that previous item without getting stuck in the other focusgroup. The same should still be true when inside a focusgroup that extends a root focusgroup within the original focusgroup.");
+
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/backward-navigation/skips-root-focusgroup.html b/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/backward-navigation/skips-root-focusgroup.html
new file mode 100644
index 0000000000..c40ce02b5d
--- /dev/null
+++ b/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/backward-navigation/skips-root-focusgroup.html
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>HTML Test: focusgroup - Focus moves to previous item, skipping over a subtree that is a root focusgroup (unrelated to the one we're in).</title>
+<link rel="author" title="Microsoft" href="http://www.microsoft.com/">
+<link rel="help" href="https://github.com/MicrosoftEdge/MSEdgeExplainers/blob/main/Focusgroup/explainer.md">
+<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="../resources/focusgroup-utils.js"></script>
+
+<div focusgroup=wrap>
+ <span id=item1 tabindex=0>item1</span>
+ <div>
+ <div focusgroup>
+ <span id=item2 tabindex=-1>item2</span>
+ <span id=item3 tabindex=-1>item3</span>
+ </div>
+ </div>
+ <span id=item4 tabindex=-1>item4</span>
+</div>
+
+<script>
+
+ promise_test(async t => {
+ var item1 = document.getElementById("item1");
+ var item4 = document.getElementById("item4");
+
+ await focusAndKeyPress(item4, kArrowUp);
+ assert_equals(document.activeElement, item1);
+
+ await focusAndKeyPress(item4, kArrowLeft);
+ assert_equals(document.activeElement, item1);
+ }, "When the focus is set on the last item of a focusgroup and the previous item is located past an other (non-extending) focusgroup subtree, a backward arrow key press should move the focus to that previous item without getting stuck in the other focusgroup.");
+
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/backward-navigation/vertical/ascends-to-parent-focusgroup.html b/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/backward-navigation/vertical/ascends-to-parent-focusgroup.html
new file mode 100644
index 0000000000..b95f2c527b
--- /dev/null
+++ b/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/backward-navigation/vertical/ascends-to-parent-focusgroup.html
@@ -0,0 +1,36 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>HTML Test: focusgroup - Focus ascends to parent focusgroup successfully.</title>
+<link rel="author" title="Microsoft" href="http://www.microsoft.com/">
+<link rel="help" href="https://github.com/MicrosoftEdge/MSEdgeExplainers/blob/main/Focusgroup/explainer.md">
+<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="../../resources/focusgroup-utils.js"></script>
+
+<div focusgroup=vertical>
+ <span id=item1 tabindex=0>item1</span>
+ <div id=item2 tabindex=-1>
+ <div>
+ <div focusgroup="extend horizontal">
+ <span id=item3 tabindex=-1>item3</span>
+ <span id=item4 tabindex=-1>item4</span>
+ </div>
+ </div>
+ </div>
+ <span id=item5 tabindex=-1>item5</span>
+</div>
+
+<script>
+
+ promise_test(async t => {
+ var item2 = document.getElementById("item2");
+ var item3 = document.getElementById("item3");
+
+ await focusAndKeyPress(item3, kArrowUp);
+ assert_equals(document.activeElement, item2);
+ }, "When the focus is set on the first item of an extending focusgroup that doesn't support the axis of the arrow key pressed but the parent focusgroup does, ascend to that focusgroup. This should work whether the extending focusgroup is the child of the other focusgroup or a distant descendant.");
+
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/backward-navigation/vertical/does-not-ascend-out-of-non-extending-focusgroup.html b/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/backward-navigation/vertical/does-not-ascend-out-of-non-extending-focusgroup.html
new file mode 100644
index 0000000000..08dc466603
--- /dev/null
+++ b/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/backward-navigation/vertical/does-not-ascend-out-of-non-extending-focusgroup.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>HTML Test: focusgroup - Focus does not ascend out of current focusgroup if it does not extend the parent focusgroup.</title>
+<link rel="author" title="Microsoft" href="http://www.microsoft.com/">
+<link rel="help" href="https://github.com/MicrosoftEdge/MSEdgeExplainers/blob/main/Focusgroup/explainer.md">
+<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="../../resources/focusgroup-utils.js"></script>
+
+<ul focusgroup=vertical>
+ <li id=item1 tabindex="-1">
+ <ul focusgroup=horizontal>
+ <li id=item2 tabindex="-1">item2</li>
+ </ul>
+ </li>
+</ul>
+
+<script>
+
+ promise_test(async t => {
+ var item2 = document.getElementById("item2");
+
+ await focusAndKeyPress(item2, kArrowUp);
+ assert_equals(document.activeElement, item2);
+ }, "When the focus is set on the first element of a non-extending focusgroup located inside another focusgroup, we should be able to ascend to that other focusgroup.");
+
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/backward-navigation/vertical/does-not-move-when-axis-not-supported.html b/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/backward-navigation/vertical/does-not-move-when-axis-not-supported.html
new file mode 100644
index 0000000000..0cf6ad3be2
--- /dev/null
+++ b/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/backward-navigation/vertical/does-not-move-when-axis-not-supported.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>HTML Test: focusgroup - Focus does not move to previous focusgroup item when the axis of the arrow key pressed isn't supported.</title>
+<link rel="author" title="Microsoft" href="http://www.microsoft.com/">
+<link rel="help" href="https://github.com/MicrosoftEdge/MSEdgeExplainers/blob/main/Focusgroup/explainer.md">
+<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="../../resources/focusgroup-utils.js"></script>
+
+<div id=root focusgroup=horizontal>
+ <span id=item1 tabindex=0>item1</span>
+ <span id=item2 tabindex=-1>item2</span>
+</div>
+
+<script>
+
+ promise_test(async t => {
+ var item2 = document.getElementById("item2");
+
+ await focusAndKeyPress(item2, kArrowUp);
+ assert_equals(document.activeElement, item2);
+ }, "When the focus is set on the last element of a focusgroup that only supports the orthogonal axis of the arrow key pressed, the focus shouldn't move.");
+
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/backward-navigation/vertical/does-not-wrap-in-orthogonal-axis.html b/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/backward-navigation/vertical/does-not-wrap-in-orthogonal-axis.html
new file mode 100644
index 0000000000..0ec4f02247
--- /dev/null
+++ b/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/backward-navigation/vertical/does-not-wrap-in-orthogonal-axis.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>HTML Test: focusgroup - Focus does not wrap in the arrow key pressed orthogonal axis.</title>
+<link rel="author" title="Microsoft" href="http://www.microsoft.com/">
+<link rel="help" href="https://github.com/MicrosoftEdge/MSEdgeExplainers/blob/main/Focusgroup/explainer.md">
+<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="../../resources/focusgroup-utils.js"></script>
+
+<div focusgroup="horizontal wrap">
+ <span id=item1 tabindex=0>item1</span>
+ <span id=item2 tabindex=-1>item2</span>
+ <span id=item3 tabindex=-1>item3</span>
+</div>
+
+<script>
+
+ promise_test(async t => {
+ var item1 = document.getElementById("item1");
+
+ await focusAndKeyPress(item1, kArrowUp);
+ assert_equals(document.activeElement, item1);
+ }, "When the focus is set on the first item of a focusgroup that wraps and supports only the orthogonal axis of the pressed arrow key, a backward arrow key press shouldn't move the focus.");
+
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/backward-navigation/vertical/moves-when-only-current-axis-supported.html b/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/backward-navigation/vertical/moves-when-only-current-axis-supported.html
new file mode 100644
index 0000000000..ad46be76b2
--- /dev/null
+++ b/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/backward-navigation/vertical/moves-when-only-current-axis-supported.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>HTML Test: focusgroup - Focus moves to previous focusgroup item when only the axis of the arrow key pressed is supported.</title>
+<link rel="author" title="Microsoft" href="http://www.microsoft.com/">
+<link rel="help" href="https://github.com/MicrosoftEdge/MSEdgeExplainers/blob/main/Focusgroup/explainer.md">
+<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="../../resources/focusgroup-utils.js"></script>
+
+<div id=root focusgroup=vertical>
+ <span id=item1 tabindex=0>item1</span>
+ <span id=item2 tabindex=-1>item2</span>
+</div>
+
+<script>
+
+ promise_test(async t => {
+ var item1 = document.getElementById("item1");
+ var item2 = document.getElementById("item2");
+
+ await focusAndKeyPress(item2, kArrowUp);
+ assert_equals(document.activeElement, item1);
+ }, "When the focus is set on an item of a focusgroup that only supports the axis of the arrow key pressed the focus should move.");
+
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/backward-navigation/vertical/skips-focusgroup-that-extends-in-orthogonal-axis-complex-case.html b/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/backward-navigation/vertical/skips-focusgroup-that-extends-in-orthogonal-axis-complex-case.html
new file mode 100644
index 0000000000..86ea79122c
--- /dev/null
+++ b/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/backward-navigation/vertical/skips-focusgroup-that-extends-in-orthogonal-axis-complex-case.html
@@ -0,0 +1,38 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>HTML Test: focusgroup - Focus moves to previous item, skipping the focusgroup that extends in the orthogonal axis (complex case).</title>
+<link rel="author" title="Microsoft" href="http://www.microsoft.com/">
+<link rel="help" href="https://github.com/MicrosoftEdge/MSEdgeExplainers/blob/main/Focusgroup/explainer.md">
+<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="../../resources/focusgroup-utils.js"></script>
+
+<div focusgroup=wrap>
+ <span id=item1 tabindex=0>item1</span>
+ <div>
+ <div focusgroup="extend horizontal">
+ <div id=item2 tabindex=-1>
+ <div focusgroup=extend>
+ <span id=item3 tabindex=-1>item3</span>
+ <span id=item4 tabindex=-1>item4</span>
+ </div>
+ </div>
+ </div>
+ </div>
+ <span id=item5 tabindex=-1>item5</span>
+</div>
+
+<script>
+
+ promise_test(async t => {
+ var item1 = document.getElementById("item1");
+ var item5 = document.getElementById("item5");
+
+ await focusAndKeyPress(item5, kArrowUp);
+ assert_equals(document.activeElement, item1);
+ }, "When the focus is set on the last item of a focusgroup and the previous item is located past an extending focusgroup that only supports the orthogonal axis, a backward arrow key press should move the focus to that previous item without getting stuck in the inner focusgroup that doesn't support the axis. The same should still be true when inside a focusgroup that extends another extending focusgroup that supports only the orthogonal axis within the original focusgroup.");
+
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/backward-navigation/vertical/skips-focusgroup-that-extends-in-orthogonal-axis.html b/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/backward-navigation/vertical/skips-focusgroup-that-extends-in-orthogonal-axis.html
new file mode 100644
index 0000000000..960b8604b0
--- /dev/null
+++ b/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/backward-navigation/vertical/skips-focusgroup-that-extends-in-orthogonal-axis.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>HTML Test: focusgroup - Focus moves to previous item, skipping the focusgroup that extends in the orthogonal axis.</title>
+<link rel="author" title="Microsoft" href="http://www.microsoft.com/">
+<link rel="help" href="https://github.com/MicrosoftEdge/MSEdgeExplainers/blob/main/Focusgroup/explainer.md">
+<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="../../resources/focusgroup-utils.js"></script>
+
+<div focusgroup=wrap>
+ <span id=item1 tabindex=0>item1</span>
+ <div>
+ <div focusgroup="extending horizontal">
+ <span id=item2 tabindex=-1>item2</span>
+ <span id=item3 tabindex=-1>item3</span>
+ </div>
+ </div>
+ <span id=item4 tabindex=-1>item4</span>
+</div>
+
+<script>
+
+ promise_test(async t => {
+ var item1 = document.getElementById("item1");
+ var item4 = document.getElementById("item4");
+
+ await focusAndKeyPress(item4, kArrowUp);
+ assert_equals(document.activeElement, item1);
+ }, "When the focus is set on the last item of a focusgroup and the previous item is a descendant of a subtree, a backward arrow key press should move the focus to that previous item within the subtree. However, if that subtree is an extending focusgroup that supports only the orthogonal axis, it should be skipped.");
+
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/backward-navigation/vertical/wraps-in-axis.html b/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/backward-navigation/vertical/wraps-in-axis.html
new file mode 100644
index 0000000000..fdccee9742
--- /dev/null
+++ b/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/backward-navigation/vertical/wraps-in-axis.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>HTML Test: focusgroup - Focus wraps in the arrow key pressed axis.</title>
+<link rel="author" title="Microsoft" href="http://www.microsoft.com/">
+<link rel="help" href="https://github.com/MicrosoftEdge/MSEdgeExplainers/blob/main/Focusgroup/explainer.md">
+<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="../../resources/focusgroup-utils.js"></script>
+
+<div focusgroup="vertical wrap">
+ <span id=item1 tabindex=0>item1</span>
+ <span id=item2 tabindex=-1>item2</span>
+ <span id=item3 tabindex=-1>item3</span>
+</div>
+
+<script>
+
+ promise_test(async t => {
+ var item1 = document.getElementById("item1");
+ var item3 = document.getElementById("item3");
+
+ await focusAndKeyPress(item1, kArrowUp);
+ assert_equals(document.activeElement, item3);
+ }, "When the focus is set on the first item of a focusgroup that wraps and supports only the axis of the pressed arrow key, a backward arrow key press should move the focus to the last item within the focusgroup.");
+
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/backward-navigation/wraps-in-extending-focusgroup.html b/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/backward-navigation/wraps-in-extending-focusgroup.html
new file mode 100644
index 0000000000..6727d93f75
--- /dev/null
+++ b/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/backward-navigation/wraps-in-extending-focusgroup.html
@@ -0,0 +1,43 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>HTML Test: focusgroup - Focus wraps from first to last element when 'wrap' is specified (jumping into extending focusgroup).</title>
+<link rel="author" title="Microsoft" href="http://www.microsoft.com/">
+<link rel="help" href="https://github.com/MicrosoftEdge/MSEdgeExplainers/blob/main/Focusgroup/explainer.md">
+<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="../resources/focusgroup-utils.js"></script>
+
+<div focusgroup=wrap>
+ <div focusgroup=extend>
+ <span id=item1 tabindex=0>item1</span>
+ <div focusgroup=extend>
+ <span id=item2 tabindex=-1>item2</span>
+ <span id=item3 tabindex=-1>item3</span>
+ </div>
+ </div>
+ <span id=item4 tabindex=-1>item4</span>
+</div>
+
+<script>
+
+ promise_test(async t => {
+ var item1 = document.getElementById("item1");
+ var item3 = document.getElementById("item3");
+
+ await focusAndKeyPress(item1, kArrowUp);
+ assert_equals(document.activeElement, item4);
+
+ await focusAndKeyPress(item2, kArrowUp);
+ assert_equals(document.activeElement, item1);
+
+ await focusAndKeyPress(item1, kArrowLeft);
+ assert_equals(document.activeElement, item4);
+
+ await focusAndKeyPress(item2, kArrowLeft);
+ assert_equals(document.activeElement, item1);
+ }, "When the focus is set on the first item of an extending focusgroup that inherited its wrapping behavior, it should only wrap if the focused item is also the first item of that parent focusgroup. If it is, then it should wrap within the parent focusgroup, not within the extending focusgroup.");
+
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/backward-navigation/wraps-successfully-complex-case.html b/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/backward-navigation/wraps-successfully-complex-case.html
new file mode 100644
index 0000000000..11ebea349d
--- /dev/null
+++ b/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/backward-navigation/wraps-successfully-complex-case.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>HTML Test: focusgroup - Focus wraps from first to last focusgroup item, even though there are non items in the way.</title>
+<link rel="author" title="Microsoft" href="http://www.microsoft.com/">
+<link rel="help" href="https://github.com/MicrosoftEdge/MSEdgeExplainers/blob/main/Focusgroup/explainer.md">
+<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="../resources/focusgroup-utils.js"></script>
+
+<div focusgroup=wrap>
+ <div>
+ <span id=nonitem1>nonitem1</span>
+ <span id=nonitem2>nonitem2</span>
+ </div>
+ <span id=item1 tabindex=0>item1</span>
+ <span id=item2 tabindex=-1>item2</span>
+ <span id=item3 tabindex=-1>item3</span>
+ <div>
+ <span id=nonitem3>nonitem3</span>
+ <span id=nonitem4>nonitem4</span>
+ </div>
+</div>
+
+<script>
+
+ promise_test(async t => {
+ var item1 = document.getElementById("item1");
+ var item3 = document.getElementById("item3");
+
+ await focusAndKeyPress(item1, kArrowUp);
+ assert_equals(document.activeElement, item3);
+
+ await focusAndKeyPress(item1, kArrowUp);
+ assert_equals(document.activeElement, item3);
+ }, "When the focus is set on the first item of an extending focusgroup while there are other non-item elements before, we should still be able to wrap to the last item. Also, if the last item has other non-item elements after itself, skipping these non-item elements shouldn't be an issue.");
+
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/backward-navigation/wraps-successfully.html b/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/backward-navigation/wraps-successfully.html
new file mode 100644
index 0000000000..19df794e19
--- /dev/null
+++ b/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/backward-navigation/wraps-successfully.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>HTML Test: focusgroup - Focus wraps from first to last element when 'wrap' is specified.</title>
+<link rel="author" title="Microsoft" href="http://www.microsoft.com/">
+<link rel="help" href="https://github.com/MicrosoftEdge/MSEdgeExplainers/blob/main/Focusgroup/explainer.md">
+<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="../resources/focusgroup-utils.js"></script>
+
+<div focusgroup=wrap>
+ <span id=item1 tabindex=0>item1</span>
+ <span id=item2 tabindex=-1>item2</span>
+ <span id=item3 tabindex=-1>item3</span>
+</div>
+
+<script>
+
+ promise_test(async t => {
+ var item1 = document.getElementById("item1");
+ var item3 = document.getElementById("item3");
+
+ await focusAndKeyPress(item1, kArrowUp);
+ assert_equals(document.activeElement, item3);
+
+ await focusAndKeyPress(item1, kArrowLeft);
+ assert_equals(document.activeElement, item3);
+ }, "When the focus is set on the first item of a focusgroup that wraps, a backward arrow key press should move the focus to the last item within the focusgroup.");
+
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/forward-navigation/does-not-move-when-on-focusgroup-root.html b/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/forward-navigation/does-not-move-when-on-focusgroup-root.html
new file mode 100644
index 0000000000..30c70ad3b3
--- /dev/null
+++ b/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/forward-navigation/does-not-move-when-on-focusgroup-root.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>HTML Test: focusgroup - Focus does not move when initially set on the focusgroup root.</title>
+<link rel="author" title="Microsoft" href="http://www.microsoft.com/">
+<link rel="help" href="https://github.com/MicrosoftEdge/MSEdgeExplainers/blob/main/Focusgroup/explainer.md">
+<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="../resources/focusgroup-utils.js"></script>
+
+<div id=root tabindex=-1 focusgroup>
+ <span id=item1 tabindex=0>item1</span>
+ <span id=item2 tabindex=-1>item2</span>
+</div>
+
+<script>
+
+ promise_test(async t => {
+ var root = document.getElementById("root");
+
+ await focusAndKeyPress(root, kArrowDown);
+ assert_equals(document.activeElement, root);
+
+ await focusAndKeyPress(root, kArrowRight);
+ assert_equals(document.activeElement, root);
+ }, "When the focus is set on the root of a focusgroup element, an arrow key press shouldn't move the focus at all.");
+
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/forward-navigation/does-not-move-when-on-non-item.html b/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/forward-navigation/does-not-move-when-on-non-item.html
new file mode 100644
index 0000000000..3afcd184c1
--- /dev/null
+++ b/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/forward-navigation/does-not-move-when-on-non-item.html
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>HTML Test: focusgroup - Focus does not move when initially set a focusable element that isn't a focusgroup item.</title>
+<link rel="author" title="Microsoft" href="http://www.microsoft.com/">
+<link rel="help" href="https://github.com/MicrosoftEdge/MSEdgeExplainers/blob/main/Focusgroup/explainer.md">
+<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="../resources/focusgroup-utils.js"></script>
+
+<div tabindex=-1 focusgroup>
+ <div>
+ <span id=nonitem1 tabindex=0>nonitem1</span>
+ </div>
+ <span id=item1 tabindex=0>item1</span>
+ <span id=item2 tabindex=-1>item2</span>
+</div>
+
+<script>
+
+ promise_test(async t => {
+ var nonitem1 = document.getElementById("nonitem1");
+
+ await focusAndKeyPress(nonitem1, kArrowDown);
+ assert_equals(document.activeElement, nonitem1);
+
+ await focusAndKeyPress(nonitem1, kArrowRight);
+ assert_equals(document.activeElement, nonitem1);
+ }, "When the focus is set on a focusable element that isn't a focusgroup item, an arrow key press shouldn't move the focus at all.");
+
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/forward-navigation/does-not-move-when-only-one-item-and-wraps.html b/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/forward-navigation/does-not-move-when-only-one-item-and-wraps.html
new file mode 100644
index 0000000000..20cc5eb57d
--- /dev/null
+++ b/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/forward-navigation/does-not-move-when-only-one-item-and-wraps.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>HTML Test: focusgroup - Focus does not move when there is only one item, even though it wraps.</title>
+<link rel="author" title="Microsoft" href="http://www.microsoft.com/">
+<link rel="help" href="https://github.com/MicrosoftEdge/MSEdgeExplainers/blob/main/Focusgroup/explainer.md">
+<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="../resources/focusgroup-utils.js"></script>
+
+<div focusgroup=wrap>
+ <span id=item1 tabindex=0>item1</span>
+</div>
+
+<script>
+
+ promise_test(async t => {
+ var item1 = document.getElementById("item1");
+
+ await focusAndKeyPress(item1, kArrowDown);
+ assert_equals(document.activeElement, item1);
+
+ await focusAndKeyPress(item1, kArrowRight);
+ assert_equals(document.activeElement, item1);
+ }, "When the focus is set on the only focusgroup item and the focusgroup wraps in the axis of the arrow key pressed, the focus shouldn't move and we shouldn't get stuck in an infinite loop.");
+
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/forward-navigation/does-not-move-when-only-one-item.html b/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/forward-navigation/does-not-move-when-only-one-item.html
new file mode 100644
index 0000000000..0178494d87
--- /dev/null
+++ b/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/forward-navigation/does-not-move-when-only-one-item.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>HTML Test: focusgroup - Focus does not move when there is only one item.</title>
+<link rel="author" title="Microsoft" href="http://www.microsoft.com/">
+<link rel="help" href="https://github.com/MicrosoftEdge/MSEdgeExplainers/blob/main/Focusgroup/explainer.md">
+<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="../resources/focusgroup-utils.js"></script>
+
+<div focusgroup>
+ <span id=item1 tabindex=0>item1</span>
+</div>
+
+<script>
+
+ promise_test(async t => {
+ var item1 = document.getElementById("item1");
+
+ await focusAndKeyPress(item1, kArrowDown);
+ assert_equals(document.activeElement, item1);
+
+ await focusAndKeyPress(item1, kArrowRight);
+ assert_equals(document.activeElement, item1);
+ }, "When the focus is set on the only focusgroup item, the focus shouldn't move and we shouldn't get stuck in an infinite loop.");
+
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/forward-navigation/does-not-move-when-outside-focusgroup.html b/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/forward-navigation/does-not-move-when-outside-focusgroup.html
new file mode 100644
index 0000000000..1c35805f80
--- /dev/null
+++ b/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/forward-navigation/does-not-move-when-outside-focusgroup.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>HTML Test: focusgroup - Focus does not move when initially set on an element not included in the focusgroup.</title>
+<link rel="author" title="Microsoft" href="http://www.microsoft.com/">
+<link rel="help" href="https://github.com/MicrosoftEdge/MSEdgeExplainers/blob/main/Focusgroup/explainer.md">
+<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="../resources/focusgroup-utils.js"></script>
+
+<span id=out tabindex=-1>out</span>
+<div focusgroup>
+ <span id=item1 tabindex=0>item1</span>
+ <span id=item2 tabindex=-1>item2</span>
+</div>
+
+<script>
+
+ promise_test(async t => {
+ var out = document.getElementById("out");
+
+ await focusAndKeyPress(out, kArrowDown);
+ assert_equals(document.activeElement, out);
+
+ await focusAndKeyPress(out, kArrowRight);
+ assert_equals(document.activeElement, out);
+ }, "When the focus is set on an element outside of the focusgroup, an arrow keypress shouldn't move the focus at all.");
+
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/forward-navigation/does-not-wrap-in-focusgroup-with-no-items.html b/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/forward-navigation/does-not-wrap-in-focusgroup-with-no-items.html
new file mode 100644
index 0000000000..602feed001
--- /dev/null
+++ b/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/forward-navigation/does-not-wrap-in-focusgroup-with-no-items.html
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>HTML Test: focusgroup - Focus does not wrap in wrapping extending focusgroup that doesn't have focusgroup items.</title>
+<link rel="author" title="Microsoft" href="http://www.microsoft.com/">
+<link rel="help" href="https://github.com/MicrosoftEdge/MSEdgeExplainers/blob/main/Focusgroup/explainer.md">
+<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="../resources/focusgroup-utils.js"></script>
+
+<div id=root focusgroup>
+ <span id=item1 tabindex=0>item1</span>
+ <div focusgroup="extend wrap">
+ <span id=item2>item2</span> <!--NOT FOCUSABLE-->
+ <span id=item3>item3</span> <!--NOT FOCUSABLE-->
+ </div>
+ <span id=item4 tabindex=-1>item4</span>
+</div>
+
+<script>
+
+ promise_test(async t => {
+ var item1 = document.getElementById("item1");
+ var item4 = document.getElementById("item4");
+
+ await focusAndKeyPress(item1, kArrowDown);
+ assert_equals(document.activeElement, item4);
+
+ await focusAndKeyPress(item1, kArrowRight);
+ assert_equals(document.activeElement, item4);
+ }, "This test validates that we don't get stuck in an infinite loop searching for a focusable element in the extending focusgroup that wraps that doesn't contain one. Wrapping should only be allowed in the focusgroup that contains the focusable element we started on or in one of its ancestors.");
+
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/forward-navigation/does-not-wrap-when-not-supported.html b/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/forward-navigation/does-not-wrap-when-not-supported.html
new file mode 100644
index 0000000000..550a4ba8a1
--- /dev/null
+++ b/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/forward-navigation/does-not-wrap-when-not-supported.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>HTML Test: focusgroup - Focus does not wrap when not supported.</title>
+<link rel="author" title="Microsoft" href="http://www.microsoft.com/">
+<link rel="help" href="https://github.com/MicrosoftEdge/MSEdgeExplainers/blob/main/Focusgroup/explainer.md">
+<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="../resources/focusgroup-utils.js"></script>
+
+<div id=root focusgroup>
+ <span id=item1 tabindex=0>item1</span>
+ <span id=item2 tabindex=-1>item2</span>
+</div>
+
+<script>
+
+ promise_test(async t => {
+ var item2 = document.getElementById("item2");
+
+ await focusAndKeyPress(item2, kArrowDown);
+ assert_equals(document.activeElement, item2);
+
+ await focusAndKeyPress(item2, kArrowRight);
+ assert_equals(document.activeElement, item2);
+ }, "When the focus is set on the last item of a focusgroup that doesn't support wrapping in the axis of the arrow key pressed, the focus shouldn't move.");
+
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/forward-navigation/horizontal/descends-in-horizontal-inner-focusgroup.html b/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/forward-navigation/horizontal/descends-in-horizontal-inner-focusgroup.html
new file mode 100644
index 0000000000..3d0d7f0fa5
--- /dev/null
+++ b/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/forward-navigation/horizontal/descends-in-horizontal-inner-focusgroup.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>HTML Test: focusgroup - Focus descends from vertical focusgroup into horizontal focusgroup.</title>
+<link rel="author" title="Microsoft" href="http://www.microsoft.com/">
+<link rel="help" href="https://github.com/MicrosoftEdge/MSEdgeExplainers/blob/main/Focusgroup/explainer.md">
+<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="../../resources/focusgroup-utils.js"></script>
+
+<div id=fg1 focusgroup=vertical>
+ <span id=item1 tabindex=0>item1</span>
+ <div id=fg2 tabindex=-1 focusgroup="extend horizontal">
+ <span id=item2 tabindex=-1>item2</span>
+ </div>
+</div>
+
+<script>
+
+ promise_test(async t => {
+ var fg2 = document.getElementById("fg2");
+ var item2 = document.getElementById("item2");
+
+ await focusAndKeyPress(fg2, kArrowRight);
+ assert_equals(document.activeElement, item2);
+ }, "When the focus is set on the root of an extending focusgroup that supports an orthogonal axis to the outer focusgroup, an arrow key press aligned with the inner focusgroup axis should move the focus within that inner focusgroup.");
+
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/forward-navigation/horizontal/does-not-move-when-axis-not-supported.html b/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/forward-navigation/horizontal/does-not-move-when-axis-not-supported.html
new file mode 100644
index 0000000000..ebf7238c54
--- /dev/null
+++ b/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/forward-navigation/horizontal/does-not-move-when-axis-not-supported.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>HTML Test: focusgroup - Focus does not move to next focusgroup item when the axis of the arrow key pressed isn't supported.</title>
+<link rel="author" title="Microsoft" href="http://www.microsoft.com/">
+<link rel="help" href="https://github.com/MicrosoftEdge/MSEdgeExplainers/blob/main/Focusgroup/explainer.md">
+<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="../../resources/focusgroup-utils.js"></script>
+
+<div id=root focusgroup=vertical>
+ <span id=item1 tabindex=0>item1</span>
+ <span id=item2 tabindex=-1>item2</span>
+</div>
+
+<script>
+
+ promise_test(async t => {
+ var item1 = document.getElementById("item1");
+
+ await focusAndKeyPress(item1, kArrowRight);
+ assert_equals(document.activeElement, item1);
+ }, "When the focus is set on an item of a focusgroup that only supports the orthogonal axis to the arrow key pressed, the arrow pressed shouldn't move the focus.");
+
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/forward-navigation/horizontal/does-not-wrap-even-when-other-axis-supported.html b/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/forward-navigation/horizontal/does-not-wrap-even-when-other-axis-supported.html
new file mode 100644
index 0000000000..19535e1df9
--- /dev/null
+++ b/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/forward-navigation/horizontal/does-not-wrap-even-when-other-axis-supported.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>HTML Test: focusgroup - Does not wrap when the arrow keypress is supported but the focusgroup only wraps in the other axis.</title>
+<link rel="author" title="Microsoft" href="http://www.microsoft.com/">
+<link rel="help" href="https://github.com/MicrosoftEdge/MSEdgeExplainers/blob/main/Focusgroup/explainer.md">
+<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="../../resources/focusgroup-utils.js"></script>
+
+<div id=root focusgroup="vertical wrap">
+ <span id=item1 tabindex=0>item1</span>
+ <div id=item2 tabindex=-1 focusgroup=extend>
+ <!--This focusgroup supports both axes, but only wraps in the vertical one.-->
+ <span id=item3 tabindex=-1>item3</span>
+ <span id=item4 tabindex=-1>item4</span>
+ </div>
+</div>
+
+<script>
+
+ promise_test(async t => {
+ var item4 = document.getElementById("item4");
+
+ await focusAndKeyPress(item4, kArrowRight);
+ assert_equals(document.activeElement, item4);
+ }, "When the focus is set on the last item of a focusgroup that doesn't support wrapping in the axis of the arrow key pressed but supports wrapping in the orthogonal axis, the focus shouldn't move.");
+
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/forward-navigation/horizontal/moves-when-only-current-axis-supported.html b/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/forward-navigation/horizontal/moves-when-only-current-axis-supported.html
new file mode 100644
index 0000000000..9ae9892c1d
--- /dev/null
+++ b/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/forward-navigation/horizontal/moves-when-only-current-axis-supported.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>HTML Test: focusgroup - Focus moves to next focusgroup item when only the axis of the arrow key pressed is supported.</title>
+<link rel="author" title="Microsoft" href="http://www.microsoft.com/">
+<link rel="help" href="https://github.com/MicrosoftEdge/MSEdgeExplainers/blob/main/Focusgroup/explainer.md">
+<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="../../resources/focusgroup-utils.js"></script>
+
+<div id=root focusgroup=horizontal>
+ <span id=item1 tabindex=0>item1</span>
+ <span id=item2 tabindex=-1>item2</span>
+</div>
+
+<script>
+
+ promise_test(async t => {
+ var item1 = document.getElementById("item1");
+ var item2 = document.getElementById("item2");
+
+ await focusAndKeyPress(item1, kArrowRight);
+ assert_equals(document.activeElement, item2);
+ }, "When the focus is set on an item of a focusgroup that only supports the axis of the arrow key pressed the focus should move.");
+
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/forward-navigation/horizontal/skips-focusgroup-that-extends-in-orthogonal-axis.html b/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/forward-navigation/horizontal/skips-focusgroup-that-extends-in-orthogonal-axis.html
new file mode 100644
index 0000000000..05f8268895
--- /dev/null
+++ b/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/forward-navigation/horizontal/skips-focusgroup-that-extends-in-orthogonal-axis.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>HTML Test: focusgroup - Focus moves to next item, skipping the focusgroup that extends in the orthogonal axis.</title>
+<link rel="author" title="Microsoft" href="http://www.microsoft.com/">
+<link rel="help" href="https://github.com/MicrosoftEdge/MSEdgeExplainers/blob/main/Focusgroup/explainer.md">
+<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="../../resources/focusgroup-utils.js"></script>
+
+<div id=root focusgroup>
+ <div id=item1 tabindex=0 focusgroup="extend vertical">
+ <span id=item2 tabindex=-1>item2</span>
+ </div>
+ <span id=item3 tabindex=-1>item3</span>
+</div>
+
+<script>
+
+ promise_test(async t => {
+ var item1 = document.getElementById("item1");
+ var item3 = document.getElementById("item3");
+
+ await focusAndKeyPress(item1, kArrowRight);
+ assert_equals(document.activeElement, item3);
+ }, "When the focus is set on an extending focusgroup element but that focusgroup doesn't support the axis of the arrow key pressed, skip that subtree altogether.");
+
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/forward-navigation/horizontal/wraps-and-skips-orthogonal-inner-focusgroup.html b/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/forward-navigation/horizontal/wraps-and-skips-orthogonal-inner-focusgroup.html
new file mode 100644
index 0000000000..af79ca24c6
--- /dev/null
+++ b/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/forward-navigation/horizontal/wraps-and-skips-orthogonal-inner-focusgroup.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>HTML Test: focusgroup - Focus wraps and skips the orthogonal inner focusgroup.</title>
+<link rel="author" title="Microsoft" href="http://www.microsoft.com/">
+<link rel="help" href="https://github.com/MicrosoftEdge/MSEdgeExplainers/blob/main/Focusgroup/explainer.md">
+<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="../../resources/focusgroup-utils.js"></script>
+
+<div id=root focusgroup=wrap>
+ <div focusgroup="extend vertical">
+ <span id=item1 tabindex=-1>item1</span>
+ <span id=item2 tabindex=-1>item2</span>
+ </div>
+ <span id=item3 tabindex=-1>item3</span>
+ <span id=item4 tabindex=0>item4</span>
+</div>
+
+<script>
+
+ promise_test(async t => {
+ var item3 = document.getElementById("item3");
+ var item4 = document.getElementById("item4");
+
+ await focusAndKeyPress(item4, kArrowRight);
+ assert_equals(document.activeElement, item3);
+ }, "When the focus is set on the last item of a focusgroup that supports wrapping in the axis of the arrow key pressed and the first item is in an inner focusgroup that doesn't support wrapping in the same axis, the focus moves to the next item out of that inner focusgroup.");
+
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/forward-navigation/horizontal/wraps-in-appropriate-focusgroup.html b/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/forward-navigation/horizontal/wraps-in-appropriate-focusgroup.html
new file mode 100644
index 0000000000..80290c9918
--- /dev/null
+++ b/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/forward-navigation/horizontal/wraps-in-appropriate-focusgroup.html
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>HTML Test: focusgroup - Focus wraps in the appropriate focusgroup.</title>
+<link rel="author" title="Microsoft" href="http://www.microsoft.com/">
+<link rel="help" href="https://github.com/MicrosoftEdge/MSEdgeExplainers/blob/main/Focusgroup/explainer.md">
+<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="../../resources/focusgroup-utils.js"></script>
+
+<div focusgroup=wrap> <!--Supports horizontal wrapping-->
+ <div focusgroup="extend vertical"> <!--Doesn't support horizontal wrapping-->
+ <span id=item1 tabindex=0>item1</span>
+ <div id=item2 tabindex=-1 focusgroup="extend wrap">
+ <!--Supports wrapping in both axis, but only extend the
+ wrapping behavior of its ancestors in the vertical axis. -->
+ <span id=item3 tabindex=-1>item3</span>
+ <span id=item4 tabindex=-1>item4</span>
+ </div>
+ </div>
+</div>
+
+<script>
+
+ promise_test(async t => {
+ var item3 = document.getElementById("item3");
+ var item4 = document.getElementById("item4");
+
+ await focusAndKeyPress(item4, kArrowRight);
+ assert_equals(document.activeElement, item3);
+ }, "When the focus is set on the last item of an inner focusgroup that supports wrapping while its parent focusgroup doesn't (in the axis of the arrow key pressed), the focus should move to the first item of the inner focusgroup even if there's another focusgroup supporting wrapping in the same axis as the arrow key pressed in the hierarchy.");
+
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/forward-navigation/horizontal/wraps-in-inner-focusgroup.html b/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/forward-navigation/horizontal/wraps-in-inner-focusgroup.html
new file mode 100644
index 0000000000..ab33842340
--- /dev/null
+++ b/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/forward-navigation/horizontal/wraps-in-inner-focusgroup.html
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>HTML Test: focusgroup - Focus wraps in the inner focusgroup only since the outer focusgroup only wraps in the other axis.</title>
+<link rel="author" title="Microsoft" href="http://www.microsoft.com/">
+<link rel="help" href="https://github.com/MicrosoftEdge/MSEdgeExplainers/blob/main/Focusgroup/explainer.md">
+<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="../../resources/focusgroup-utils.js"></script>
+
+<div id=root focusgroup="vertical wrap">
+ <span id=item1 tabindex=0>item1</span>
+ <div id=item2 tabindex=-1 focusgroup="extend wrap">
+ <!--This focusgroup supports wrapping in both axis, but only extend the wrapping
+ behavior of its parent in the vertical axis. -->
+ <span id=item3 tabindex=-1>item3</span>
+ <span id=item4 tabindex=-1>item4</span>
+ </div>
+</div>
+
+<script>
+
+ promise_test(async t => {
+ var item3 = document.getElementById("item3");
+ var item4 = document.getElementById("item4");
+
+ await focusAndKeyPress(item4, kArrowRight);
+ assert_equals(document.activeElement, item3);
+ }, "When the focus is set on the last item of an inner focusgroup that supports wrapping while its parent focusgroup doesn't (in the axis of the arrow key pressed), the focus should move to the first item of the inner focusgroup.");
+
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/forward-navigation/moves-inside-extending-focusgroup.html b/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/forward-navigation/moves-inside-extending-focusgroup.html
new file mode 100644
index 0000000000..bea7b959c5
--- /dev/null
+++ b/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/forward-navigation/moves-inside-extending-focusgroup.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>HTML Test: focusgroup - Focus moves to next item inside an extending focusgroup.</title>
+<link rel="author" title="Microsoft" href="http://www.microsoft.com/">
+<link rel="help" href="https://github.com/MicrosoftEdge/MSEdgeExplainers/blob/main/Focusgroup/explainer.md">
+<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="../resources/focusgroup-utils.js"></script>
+
+<div id=root focusgroup>
+ <div id=item1 tabindex=0 focusgroup=extend>
+ <span id=item2>item2</span> <!--NOT FOCUSABLE-->
+ <span id=item3 tabindex=-1>item3</span>
+ </div>
+ <span id=item4 tabindex=-1>item4</span>
+</div>
+
+<script>
+
+ promise_test(async t => {
+ var item1 = document.getElementById("item1");
+ var item3 = document.getElementById("item3");
+
+ await focusAndKeyPress(item1, kArrowDown);
+ assert_equals(document.activeElement, item3);
+
+ await focusAndKeyPress(item1, kArrowRight);
+ assert_equals(document.activeElement, item3);
+ }, "When the focus is set on a focusgroup item which happens to also be an extending focusgroup, an arrow key press should move the focus to the next item within the extending focusgroup and skip non-focusable items.");
+
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/forward-navigation/moves-to-next-item-and-skips-non-focusable.html b/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/forward-navigation/moves-to-next-item-and-skips-non-focusable.html
new file mode 100644
index 0000000000..a5d6e306ed
--- /dev/null
+++ b/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/forward-navigation/moves-to-next-item-and-skips-non-focusable.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>HTML Test: focusgroup - Focus moves to next item and skips non-focusable elements.</title>
+<link rel="author" title="Microsoft" href="http://www.microsoft.com/">
+<link rel="help" href="https://github.com/MicrosoftEdge/MSEdgeExplainers/blob/main/Focusgroup/explainer.md">
+<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="../resources/focusgroup-utils.js"></script>
+
+<div focusgroup>
+ <span id=item1 tabindex=0>item1</span>
+ <span id=item2>item2</span> <!--NOT FOCUSABLE-->
+ <span id=item3 tabindex=-1>item3</span>
+</div>
+
+<script>
+
+ promise_test(async t => {
+ var item1 = document.getElementById("item1");
+ var item3 = document.getElementById("item3");
+
+ await focusAndKeyPress(item1, kArrowDown);
+ assert_equals(document.activeElement, item3);
+
+ await focusAndKeyPress(item1, kArrowRight);
+ assert_equals(document.activeElement, item3);
+ }, "When the focus is set on a focusgroup item, an arrow key press should move the focus to the next item and skip non-focusable items.");
+
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/forward-navigation/moves-to-next-item-outside-extending-focusgroup.html b/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/forward-navigation/moves-to-next-item-outside-extending-focusgroup.html
new file mode 100644
index 0000000000..f5a74ced5f
--- /dev/null
+++ b/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/forward-navigation/moves-to-next-item-outside-extending-focusgroup.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>HTML Test: focusgroup - Focus moves to next item outside the extending focusgroup.</title>
+<link rel="author" title="Microsoft" href="http://www.microsoft.com/">
+<link rel="help" href="https://github.com/MicrosoftEdge/MSEdgeExplainers/blob/main/Focusgroup/explainer.md">
+<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="../resources/focusgroup-utils.js"></script>
+
+<div id=root focusgroup>
+ <div id=item1 tabindex=0 focusgroup=extend>
+ <span id=item2>item2</span> <!--NOT FOCUSABLE-->
+ <span id=item3>item3</span> <!--NOT FOCUSABLE-->
+ </div>
+ <span id=item4 tabindex=-1>item4</span>
+</div>
+
+<script>
+
+ promise_test(async t => {
+ var item1 = document.getElementById("item1");
+ var item4 = document.getElementById("item4");
+
+ await focusAndKeyPress(item1, kArrowDown);
+ assert_equals(document.activeElement, item4);
+
+ await focusAndKeyPress(item1, kArrowRight);
+ assert_equals(document.activeElement, item4);
+ }, "When the focus is set on a focusgroup item which happens to also be an extending focusgroup, an arrow key press should move the focus to the next item within the extending focusgroup and skip non-focusable items. If no valid candidate is found within that extending focusgroup, the next element (in pre-order traversal) should be considered. In this case, |item4| is the valid next candidate.");
+
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/forward-navigation/moves-to-next-item-within-descendants.html b/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/forward-navigation/moves-to-next-item-within-descendants.html
new file mode 100644
index 0000000000..97355ec2da
--- /dev/null
+++ b/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/forward-navigation/moves-to-next-item-within-descendants.html
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>HTML Test: focusgroup - Focus moves to next item within its descendants.</title>
+<link rel="author" title="Microsoft" href="http://www.microsoft.com/">
+<link rel="help" href="https://github.com/MicrosoftEdge/MSEdgeExplainers/blob/main/Focusgroup/explainer.md">
+<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="../resources/focusgroup-utils.js"></script>
+
+<div id=root focusgroup>
+ <div id=item1 tabindex=0>
+ <div>
+ <div focusgroup=extend>
+ <span id=item2 tabindex=-1>item2<span>
+ </div>
+ </div>
+ </div>
+ <span id=item4 tabindex=-1>item4</span>
+</div>
+
+<script>
+
+ promise_test(async t => {
+ var item1 = document.getElementById("item1");
+ var item2 = document.getElementById("item2");
+
+ await focusAndKeyPress(item1, kArrowDown);
+ assert_equals(document.activeElement, item2);
+
+ await focusAndKeyPress(item1, kArrowRight);
+ assert_equals(document.activeElement, item2);
+ }, "When the focus is set on a focusgroup item that is an ancestor to an extending focusgroup, the focus should move to the next element inside that extending focusgroup even if it's not a direct child.");
+
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/forward-navigation/moves-to-next-item.html b/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/forward-navigation/moves-to-next-item.html
new file mode 100644
index 0000000000..747805d7b5
--- /dev/null
+++ b/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/forward-navigation/moves-to-next-item.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>HTML Test: focusgroup - Focus moves to next focusgroup item.</title>
+<link rel="author" title="Microsoft" href="http://www.microsoft.com/">
+<link rel="help" href="https://github.com/MicrosoftEdge/MSEdgeExplainers/blob/main/Focusgroup/explainer.md">
+<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="../resources/focusgroup-utils.js"></script>
+
+<div focusgroup>
+ <span id=item1 tabindex=0>item1</span>
+ <span id=item2 tabindex=-1>item2</span>
+</div>
+
+<script>
+
+ promise_test(async t => {
+ var item1 = document.getElementById("item1");
+ var item2 = document.getElementById("item2");
+
+ await focusAndKeyPress(item1, kArrowDown);
+ assert_equals(document.activeElement, item2);
+
+ await focusAndKeyPress(item1, kArrowRight);
+ assert_equals(document.activeElement, item2);
+ }, "When the focus is set on a focusgroup item, an arrow key press should move the focus to the next item.");
+
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/forward-navigation/vertical/descends-in-vertical-inner-focusgroup.html b/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/forward-navigation/vertical/descends-in-vertical-inner-focusgroup.html
new file mode 100644
index 0000000000..4f6f8e9459
--- /dev/null
+++ b/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/forward-navigation/vertical/descends-in-vertical-inner-focusgroup.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>HTML Test: focusgroup - Focus descends from horizontal focusgroup into vertical focusgroup.</title>
+<link rel="author" title="Microsoft" href="http://www.microsoft.com/">
+<link rel="help" href="https://github.com/MicrosoftEdge/MSEdgeExplainers/blob/main/Focusgroup/explainer.md">
+<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="../../resources/focusgroup-utils.js"></script>
+
+<div id=fg1 focusgroup=horizontal>
+ <span id=item1 tabindex=0>item1</span>
+ <div id=fg2 tabindex=-1 focusgroup="extend vertical">
+ <span id=item2 tabindex=-1>item2</span>
+ </div>
+</div>
+
+<script>
+
+ promise_test(async t => {
+ var fg2 = document.getElementById("fg2");
+ var item2 = document.getElementById("item2");
+
+ await focusAndKeyPress(fg2, kArrowDown);
+ assert_equals(document.activeElement, item2);
+ }, "When the focus is set on the root of an extending focusgroup that supports an orthogonal axis to the outer focusgroup, an arrow key press aligned with the inner focusgroup axis should move the focus within that inner focusgroup.");
+
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/forward-navigation/vertical/does-not-move-when-axis-not-supported.html b/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/forward-navigation/vertical/does-not-move-when-axis-not-supported.html
new file mode 100644
index 0000000000..794f079418
--- /dev/null
+++ b/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/forward-navigation/vertical/does-not-move-when-axis-not-supported.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>HTML Test: focusgroup - Focus does not move to next focusgroup item when the axis of the arrow key pressed isn't supported.</title>
+<link rel="author" title="Microsoft" href="http://www.microsoft.com/">
+<link rel="help" href="https://github.com/MicrosoftEdge/MSEdgeExplainers/blob/main/Focusgroup/explainer.md">
+<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="../../resources/focusgroup-utils.js"></script>
+
+<div id=root focusgroup=horizontal>
+ <span id=item1 tabindex=0>item1</span>
+ <span id=item2 tabindex=-1>item2</span>
+</div>
+
+<script>
+
+ promise_test(async t => {
+ var item1 = document.getElementById("item1");
+
+ await focusAndKeyPress(item1, kArrowDown);
+ assert_equals(document.activeElement, item1);
+ }, "When the focus is set on an item of a focusgroup that only supports the orthogonal axis to the arrow key pressed, the arrow pressed shouldn't move the focus.");
+
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/forward-navigation/vertical/does-not-wrap-even-when-other-axis-supported.html b/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/forward-navigation/vertical/does-not-wrap-even-when-other-axis-supported.html
new file mode 100644
index 0000000000..f7b697e5c0
--- /dev/null
+++ b/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/forward-navigation/vertical/does-not-wrap-even-when-other-axis-supported.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>HTML Test: focusgroup - Does not wrap when the arrow keypress is supported but the focusgroup only wraps in the other axis.</title>
+<link rel="author" title="Microsoft" href="http://www.microsoft.com/">
+<link rel="help" href="https://github.com/MicrosoftEdge/MSEdgeExplainers/blob/main/Focusgroup/explainer.md">
+<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="../../resources/focusgroup-utils.js"></script>
+
+<div id=root focusgroup="horizontal wrap">
+ <span id=item1 tabindex=0>item1</span>
+ <div id=item2 tabindex=-1 focusgroup=extend>
+ <!--This focusgroup supports both axes, but only wraps in the horizontal one.-->
+ <span id=item3 tabindex=-1>item3</span>
+ <span id=item4 tabindex=-1>item4</span>
+ </div>
+</div>
+
+<script>
+
+ promise_test(async t => {
+ var item4 = document.getElementById("item4");
+
+ await focusAndKeyPress(item4, kArrowDown);
+ assert_equals(document.activeElement, item4);
+ }, "When the focus is set on the last item of a focusgroup that doesn't support wrapping in the axis of the arrow key pressed but supports wrapping in the orthogonal axis, the focus shouldn't move.");
+
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/forward-navigation/vertical/moves-when-only-current-axis-supported.html b/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/forward-navigation/vertical/moves-when-only-current-axis-supported.html
new file mode 100644
index 0000000000..b5b97cca67
--- /dev/null
+++ b/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/forward-navigation/vertical/moves-when-only-current-axis-supported.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>HTML Test: focusgroup - Focus moves to next focusgroup item when only the axis of the arrow key pressed is supported.</title>
+<link rel="author" title="Microsoft" href="http://www.microsoft.com/">
+<link rel="help" href="https://github.com/MicrosoftEdge/MSEdgeExplainers/blob/main/Focusgroup/explainer.md">
+<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="../../resources/focusgroup-utils.js"></script>
+
+<div id=root focusgroup=vertical>
+ <span id=item1 tabindex=0>item1</span>
+ <span id=item2 tabindex=-1>item2</span>
+</div>
+
+<script>
+
+ promise_test(async t => {
+ var item1 = document.getElementById("item1");
+ var item2 = document.getElementById("item2");
+
+ await focusAndKeyPress(item1, kArrowDown);
+ assert_equals(document.activeElement, item2);
+ }, "When the focus is set on an item of a focusgroup that only supports the axis of the arrow key pressed the focus should move.");
+
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/forward-navigation/vertical/skips-focusgroup-that-extends-in-orthogonal-axis.html b/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/forward-navigation/vertical/skips-focusgroup-that-extends-in-orthogonal-axis.html
new file mode 100644
index 0000000000..4fe753126b
--- /dev/null
+++ b/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/forward-navigation/vertical/skips-focusgroup-that-extends-in-orthogonal-axis.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>HTML Test: focusgroup - Focus moves to next item, skipping the focusgroup that extends in the orthogonal axis.</title>
+<link rel="author" title="Microsoft" href="http://www.microsoft.com/">
+<link rel="help" href="https://github.com/MicrosoftEdge/MSEdgeExplainers/blob/main/Focusgroup/explainer.md">
+<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="../../resources/focusgroup-utils.js"></script>
+
+<div id=root focusgroup>
+ <div id=item1 tabindex=0 focusgroup="extend horizontal">
+ <span id=item2 tabindex=-1>item2</span>
+ </div>
+ <span id=item3 tabindex=-1>item3</span>
+</div>
+
+<script>
+
+ promise_test(async t => {
+ var item1 = document.getElementById("item1");
+ var item3 = document.getElementById("item3");
+
+ await focusAndKeyPress(item1, kArrowDown);
+ assert_equals(document.activeElement, item3);
+ }, "When the focus is set on an extending focusgroup element but that focusgroup doesn't support the axis of the arrow key pressed, skip that subtree altogether.");
+
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/forward-navigation/vertical/wraps-and-skips-orthogonal-inner-focusgroup.html b/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/forward-navigation/vertical/wraps-and-skips-orthogonal-inner-focusgroup.html
new file mode 100644
index 0000000000..64a4b76ebf
--- /dev/null
+++ b/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/forward-navigation/vertical/wraps-and-skips-orthogonal-inner-focusgroup.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>HTML Test: focusgroup - Focus wraps and skips the orthogonal inner focusgroup.</title>
+<link rel="author" title="Microsoft" href="http://www.microsoft.com/">
+<link rel="help" href="https://github.com/MicrosoftEdge/MSEdgeExplainers/blob/main/Focusgroup/explainer.md">
+<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="../../resources/focusgroup-utils.js"></script>
+
+<div id=root focusgroup=wrap>
+ <div focusgroup="extend horizontal">
+ <span id=item1 tabindex=-1>item1</span>
+ <span id=item2 tabindex=-1>item2</span>
+ </div>
+ <span id=item3 tabindex=-1>item3</span>
+ <span id=item4 tabindex=0>item4</span>
+</div>
+
+<script>
+
+ promise_test(async t => {
+ var item3 = document.getElementById("item3");
+ var item4 = document.getElementById("item4");
+
+ await focusAndKeyPress(item4, kArrowDown);
+ assert_equals(document.activeElement, item3);
+ }, "When the focus is set on the last item of a focusgroup that supports wrapping in the axis of the arrow key pressed and the first item is in an inner focusgroup that doesn't support wrapping in the same axis, the focus moves to the next item out of that inner focusgroup.");
+
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/forward-navigation/vertical/wraps-in-appropriate-focusgroup.html b/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/forward-navigation/vertical/wraps-in-appropriate-focusgroup.html
new file mode 100644
index 0000000000..fa9c72b0bd
--- /dev/null
+++ b/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/forward-navigation/vertical/wraps-in-appropriate-focusgroup.html
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>HTML Test: focusgroup - Focus wraps in the appropriate focusgroup.</title>
+<link rel="author" title="Microsoft" href="http://www.microsoft.com/">
+<link rel="help" href="https://github.com/MicrosoftEdge/MSEdgeExplainers/blob/main/Focusgroup/explainer.md">
+<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="../../resources/focusgroup-utils.js"></script>
+
+<div focusgroup=wrap> <!--Supports vertical wrapping-->
+ <div focusgroup="extend horizontal"> <!--Doesn't support vertical wrapping-->
+ <span id=item1 tabindex=0>item1</span>
+ <div id=item2 tabindex=-1 focusgroup="extend wrap">
+ <!--Supports wrapping in both axis, but only extend the
+ wrapping behavior of its ancestors in the horizontal axis. -->
+ <span id=item3 tabindex=-1>item3</span>
+ <span id=item4 tabindex=-1>item4</span>
+ </div>
+ </div>
+</div>
+
+<script>
+
+ promise_test(async t => {
+ var item3 = document.getElementById("item3");
+ var item4 = document.getElementById("item4");
+
+ await focusAndKeyPress(item4, kArrowDown);
+ assert_equals(document.activeElement, item3);
+ }, "When the focus is set on the last item of an inner focusgroup that supports wrapping while its parent focusgroup doesn't (in the axis of the arrow key pressed), the focus should move to the first item of the inner focusgroup even if there's another focusgroup supporting wrapping in the same axis as the arrow key pressed in the hierarchy.");
+
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/forward-navigation/vertical/wraps-in-inner-focusgroup.html b/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/forward-navigation/vertical/wraps-in-inner-focusgroup.html
new file mode 100644
index 0000000000..8fd44ba0f1
--- /dev/null
+++ b/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/forward-navigation/vertical/wraps-in-inner-focusgroup.html
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>HTML Test: focusgroup - Focus wraps in the inner focusgroup only since the outer focusgroup only wraps in the other axis.</title>
+<link rel="author" title="Microsoft" href="http://www.microsoft.com/">
+<link rel="help" href="https://github.com/MicrosoftEdge/MSEdgeExplainers/blob/main/Focusgroup/explainer.md">
+<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="../../resources/focusgroup-utils.js"></script>
+
+<div id=root focusgroup="horizontal wrap">
+ <span id=item1 tabindex=0>item1</span>
+ <div id=item2 tabindex=-1 focusgroup="extend wrap">
+ <!--This focusgroup supports wrapping in both axis, but only extend the wrapping
+ behavior of its parent in the horizontal axis. -->
+ <span id=item3 tabindex=-1>item3</span>
+ <span id=item4 tabindex=-1>item4</span>
+ </div>
+</div>
+
+<script>
+
+ promise_test(async t => {
+ var item3 = document.getElementById("item3");
+ var item4 = document.getElementById("item4");
+
+ await focusAndKeyPress(item4, kArrowDown);
+ assert_equals(document.activeElement, item3);
+ }, "When the focus is set on the last item of an inner focusgroup that supports wrapping while its parent focusgroup doesn't (in the axis of the arrow key pressed), the focus should move to the first item of the inner focusgroup.");
+
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/forward-navigation/wraps-and-goes-into-inner-focusgroup.html b/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/forward-navigation/wraps-and-goes-into-inner-focusgroup.html
new file mode 100644
index 0000000000..ea9b1cbec5
--- /dev/null
+++ b/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/forward-navigation/wraps-and-goes-into-inner-focusgroup.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>HTML Test: focusgroup - Wraps and goes into inner focusgroup.</title>
+<link rel="author" title="Microsoft" href="http://www.microsoft.com/">
+<link rel="help" href="https://github.com/MicrosoftEdge/MSEdgeExplainers/blob/main/Focusgroup/explainer.md">
+<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="../resources/focusgroup-utils.js"></script>
+
+<div id=root focusgroup=wrap>
+ <div focusgroup=extend>
+ <span id=item1 tabindex=-1>item1</span>
+ <span id=item2 tabindex=-1>item2</span>
+ </div>
+ <span id=item3 tabindex=0>item3</span>
+</div>
+
+<script>
+
+ promise_test(async t => {
+ var item1 = document.getElementById("item1");
+ var item3 = document.getElementById("item3");
+
+ await focusAndKeyPress(item3, kArrowDown);
+ assert_equals(document.activeElement, item1);
+
+ await focusAndKeyPress(item3, kArrowRight);
+ assert_equals(document.activeElement, item1);
+ }, "When the focus is set on the last item of a focusgroup that supports wrapping in the axis of the arrow key pressed and the first item is in an inner focusgroup that supports it too, the focus moves to that item in the inner focusgroup.");
+
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/forward-navigation/wraps-successfully.html b/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/forward-navigation/wraps-successfully.html
new file mode 100644
index 0000000000..5ad1cd8fe9
--- /dev/null
+++ b/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/forward-navigation/wraps-successfully.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>HTML Test: focusgroup - Focus wraps successfully from the last item to the first one.</title>
+<link rel="author" title="Microsoft" href="http://www.microsoft.com/">
+<link rel="help" href="https://github.com/MicrosoftEdge/MSEdgeExplainers/blob/main/Focusgroup/explainer.md">
+<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="../resources/focusgroup-utils.js"></script>
+
+<div id=root focusgroup=wrap>
+ <span id=item1 tabindex=0>item1</span>
+ <span id=item2 tabindex=-1>item2</span>
+</div>
+
+<script>
+
+ promise_test(async t => {
+ var item1 = document.getElementById("item1");
+ var item2 = document.getElementById("item2");
+
+ await focusAndKeyPress(item2, kArrowDown);
+ assert_equals(document.activeElement, item1);
+
+ await focusAndKeyPress(item2, kArrowRight);
+ assert_equals(document.activeElement, item1);
+ }, "When the focus is set on the last item of a focusgroup that supports wrapping in the axis of the arrow key pressed, the focus should move back to the first item.");
+
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/forward-navigation/wraps-to-parent-focusgroup.html b/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/forward-navigation/wraps-to-parent-focusgroup.html
new file mode 100644
index 0000000000..afb9744b71
--- /dev/null
+++ b/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/forward-navigation/wraps-to-parent-focusgroup.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>HTML Test: focusgroup - Focus wraps successfully from the last item inside an extending focusgroup to the first item of the parent focusgroup.</title>
+<link rel="author" title="Microsoft" href="http://www.microsoft.com/">
+<link rel="help" href="https://github.com/MicrosoftEdge/MSEdgeExplainers/blob/main/Focusgroup/explainer.md">
+<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="../resources/focusgroup-utils.js"></script>
+
+<div id=root focusgroup=wrap>
+ <span id=item1 tabindex=0>item1</span>
+ <div id=item2 tabindex=-1 focusgroup=extend>
+ <span id=item3 tabindex=-1>item3</span>
+ <span id=item4 tabindex=-1>item4</span>
+ </div>
+</div>
+
+<script>
+
+ promise_test(async t => {
+ var item1 = document.getElementById("item1");
+ var item4 = document.getElementById("item4");
+
+ await focusAndKeyPress(item4, kArrowDown);
+ assert_equals(document.activeElement, item1);
+
+ await focusAndKeyPress(item4, kArrowRight);
+ assert_equals(document.activeElement, item1);
+ }, "When the focus is set on the last item of an inner focusgroup that supports wrapping while its parent focusgroup also does, the focus should move to the first item of the parent focusgroup.");
+
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/grid-navigation/col-flow-only.html b/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/grid-navigation/col-flow-only.html
new file mode 100644
index 0000000000..a634412d53
--- /dev/null
+++ b/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/grid-navigation/col-flow-only.html
@@ -0,0 +1,58 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>HTML Test: focusgroup - Validate that col-flow allows only vertical flowing.</title>
+<link rel="author" title="Microsoft" href="http://www.microsoft.com/">
+<link rel="help" href="https://github.com/MicrosoftEdge/MSEdgeExplainers/blob/main/Focusgroup/explainer.md">
+<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="../resources/focusgroup-utils.js"></script>
+
+<table focusgroup="grid col-flow">
+ <tr>
+ <td id=r1c1 tabindex=0>r1c1</td>
+ <td id=r1c2 tabindex=-1>r1c2</td>
+ <td id=r1c3 tabindex=-1>r1c3</td>
+ </tr>
+ <tr>
+ <td id=r2c1 tabindex=-1>r2c1</td>
+ <td id=r2c2 tabindex=-1>r2c2</td>
+ <td id=r2c3 tabindex=-1>r2c3</td>
+ </tr>
+</table>
+
+<script>
+
+ promise_test(async t => {
+ var r1c3 = document.getElementById("r1c3");
+
+ await focusAndKeyPress(r1c3, kArrowRight);
+ assert_equals(document.activeElement, r1c3);
+ }, "A right arrow press should not flow the focus horziontally when not supported by the focusgroup.");
+
+ promise_test(async t => {
+ var r1c2 = document.getElementById("r1c2");
+ var r2c1 = document.getElementById("r2c1");
+
+ await focusAndKeyPress(r2c1, kArrowDown);
+ assert_equals(document.activeElement, r1c2);
+ }, "When on the last row, a down arrow press should move the focus to first row and next column if the focusgroup flows horizontally.");
+
+ promise_test(async t => {
+ var r2c1 = document.getElementById("r2c1");
+
+ await focusAndKeyPress(r2c1, kArrowLeft);
+ assert_equals(document.activeElement, r2c1);
+ }, "A left arrow press should not flow the focus horziontally when not supported by the focusgroup.");
+
+ promise_test(async t => {
+ var r1c1 = document.getElementById("r1c1");
+ var r2c3 = document.getElementById("r2c3");
+
+ await focusAndKeyPress(r1c1, kArrowUp);
+ assert_equals(document.activeElement, r2c3);
+ }, "When on the first row, an up arrow press should move the focus to last row and previous column if the focusgroup flows horizontally.");
+
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/grid-navigation/col-wrap-and-row-flow.html b/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/grid-navigation/col-wrap-and-row-flow.html
new file mode 100644
index 0000000000..b5f678a706
--- /dev/null
+++ b/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/grid-navigation/col-wrap-and-row-flow.html
@@ -0,0 +1,60 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>HTML Test: focusgroup - Validate that we can col-wrap AND row-flow.</title>
+<link rel="author" title="Microsoft" href="http://www.microsoft.com/">
+<link rel="help" href="https://github.com/MicrosoftEdge/MSEdgeExplainers/blob/main/Focusgroup/explainer.md">
+<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="../resources/focusgroup-utils.js"></script>
+
+<table focusgroup="grid col-wrap row-flow">
+ <tr>
+ <td id=r1c1 tabindex=0>r1c1</td>
+ <td id=r1c2 tabindex=-1>r1c2</td>
+ <td id=r1c3 tabindex=-1>r1c3</td>
+ </tr>
+ <tr>
+ <td id=r2c1 tabindex=-1>r2c1</td>
+ <td id=r2c2 tabindex=-1>r2c2</td>
+ <td id=r2c3 tabindex=-1>r2c3</td>
+ </tr>
+</table>
+
+<script>
+
+ promise_test(async t => {
+ var r1c3 = document.getElementById("r1c3");
+ var r2c1 = document.getElementById("r2c1");
+
+ await focusAndKeyPress(r1c3, kArrowRight);
+ assert_equals(document.activeElement, r2c1);
+ }, "When on the last column, a right arrow press should move the focus to first column and next row if the focusgroup flows horizontally.");
+
+ promise_test(async t => {
+ var r1c1 = document.getElementById("r1c1");
+ var r2c1 = document.getElementById("r2c1");
+
+ await focusAndKeyPress(r2c1, kArrowDown);
+ assert_equals(document.activeElement, r1c1);
+ }, "When on the last row, a down arrow press should move the focus to first row if the focusgroup wraps vertically.");
+
+ promise_test(async t => {
+ var r1c3 = document.getElementById("r1c3");
+ var r2c1 = document.getElementById("r2c1");
+
+ await focusAndKeyPress(r2c1, kArrowLeft);
+ assert_equals(document.activeElement, r1c3);
+ }, "When on the first column, a left arrow press should move the focus to last column and previous row if the focusgroup flows horizontally.");
+
+ promise_test(async t => {
+ var r1c1 = document.getElementById("r1c1");
+ var r2c1 = document.getElementById("r2c1");
+
+ await focusAndKeyPress(r1c1, kArrowUp);
+ assert_equals(document.activeElement, r2c1);
+ }, "When on the first row, an up arrow press should move the focus to last row if the focusgroup wraps vertically.");
+
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/grid-navigation/col-wrap-only.html b/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/grid-navigation/col-wrap-only.html
new file mode 100644
index 0000000000..ff19b8778e
--- /dev/null
+++ b/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/grid-navigation/col-wrap-only.html
@@ -0,0 +1,58 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>HTML Test: focusgroup - Validate that col-wrap allows only vertical wrapping.</title>
+<link rel="author" title="Microsoft" href="http://www.microsoft.com/">
+<link rel="help" href="https://github.com/MicrosoftEdge/MSEdgeExplainers/blob/main/Focusgroup/explainer.md">
+<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="../resources/focusgroup-utils.js"></script>
+
+<table focusgroup="grid col-wrap">
+ <tr>
+ <td id=r1c1 tabindex=0>r1c1</td>
+ <td id=r1c2 tabindex=-1>r1c2</td>
+ <td id=r1c3 tabindex=-1>r1c3</td>
+ </tr>
+ <tr>
+ <td id=r2c1 tabindex=-1>r2c1</td>
+ <td id=r2c2 tabindex=-1>r2c2</td>
+ <td id=r2c3 tabindex=-1>r2c3</td>
+ </tr>
+</table>
+
+<script>
+
+ promise_test(async t => {
+ var r1c3 = document.getElementById("r1c3");
+
+ await focusAndKeyPress(r1c3, kArrowRight);
+ assert_equals(document.activeElement, r1c3);
+ }, "A right arrow press should not wrap the focus to the first column when only vertical wrap is supported by the focusgroup.");
+
+ promise_test(async t => {
+ var r1c1 = document.getElementById("r1c1");
+ var r2c1 = document.getElementById("r2c1");
+
+ await focusAndKeyPress(r2c1, kArrowDown);
+ assert_equals(document.activeElement, r1c1);
+ }, "When on the last row, a down arrow press should move the focus to first row if the focusgroup wraps vertically.");
+
+ promise_test(async t => {
+ var r1c1 = document.getElementById("r1c1");
+
+ await focusAndKeyPress(r1c1, kArrowLeft);
+ assert_equals(document.activeElement, r1c1);
+ }, "A left arrow press should not wrap the focus to the last column when only vertical wrap is supported by the focusgroup.");
+
+ promise_test(async t => {
+ var r1c1 = document.getElementById("r1c1");
+ var r2c1 = document.getElementById("r2c1");
+
+ await focusAndKeyPress(r1c1, kArrowUp);
+ assert_equals(document.activeElement, r2c1);
+ }, "When on the first row, an up arrow press should move the focus to last row if the focusgroup wraps vertically.");
+
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/grid-navigation/colspan.html b/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/grid-navigation/colspan.html
new file mode 100644
index 0000000000..c9daeffaeb
--- /dev/null
+++ b/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/grid-navigation/colspan.html
@@ -0,0 +1,93 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>HTML Test: focusgroup - Validate that we deal correctly with colspans.</title>
+<link rel="author" title="Microsoft" href="http://www.microsoft.com/">
+<link rel="help" href="https://github.com/MicrosoftEdge/MSEdgeExplainers/blob/main/Focusgroup/explainer.md">
+<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="../resources/focusgroup-utils.js"></script>
+
+<table focusgroup="grid">
+ <tr>
+ <td id=r1c1 tabindex=0>r1c1</td>
+ <td id=r1c2 tabindex=-1 colspan=2>r1c2</td>
+ <td id=r1c4 tabindex=-1>r1c4</td>
+ </tr>
+ <tr>
+ <td id=r2c1 tabindex=-1>r2c1</td>
+ <td id=r2c2 tabindex=-1>r2c2</td>
+ <td id=r2c3 tabindex=-1>r2c3</td>
+ <td id=r2c4 tabindex=-1>r2c4</td>
+ </tr>
+</table>
+
+<script>
+
+ promise_test(async t => {
+ var r1c1 = document.getElementById("r1c1");
+ var r1c2 = document.getElementById("r1c2");
+ var r1c4 = document.getElementById("r1c4");
+
+ await focusAndKeyPress(r1c1, kArrowRight);
+ assert_equals(document.activeElement, r1c2);
+
+ await focusAndKeyPress(r1c2, kArrowRight);
+ assert_equals(document.activeElement, r1c4);
+ }, "A right arrow press should move the focus to the next column, dealing correctly with colspans.");
+
+ promise_test(async t => {
+ var r1c1 = document.getElementById("r1c1");
+ var r1c2 = document.getElementById("r1c2");
+ var r1c4 = document.getElementById("r1c4");
+ var r2c1 = document.getElementById("r2c1");
+ var r2c2 = document.getElementById("r2c2");
+ var r2c4 = document.getElementById("r2c4");
+
+ await focusAndKeyPress(r1c1, kArrowDown);
+ assert_equals(document.activeElement, r2c1);
+
+ await focusAndKeyPress(r1c2, kArrowDown);
+ assert_equals(document.activeElement, r2c2);
+
+ await focusAndKeyPress(r1c4, kArrowDown);
+ assert_equals(document.activeElement, r2c4);
+ }, "A down arrow press should move the focus to the right cell on the next row, dealing correctly with colspans.");
+
+ promise_test(async t => {
+ var r1c1 = document.getElementById("r1c1");
+ var r1c2 = document.getElementById("r1c2");
+ var r1c4 = document.getElementById("r1c4");
+
+ await focusAndKeyPress(r1c4, kArrowLeft);
+ assert_equals(document.activeElement, r1c2);
+
+ await focusAndKeyPress(r1c2, kArrowLeft);
+ assert_equals(document.activeElement, r1c1);
+ }, "A left arrow press should move to the previous column, dealing correctly with the colspans.");
+
+ promise_test(async t => {
+ var r1c1 = document.getElementById("r1c1");
+ var r1c2 = document.getElementById("r1c2");
+ var r1c4 = document.getElementById("r1c4");
+ var r2c1 = document.getElementById("r2c1");
+ var r2c2 = document.getElementById("r2c2");
+ var r2c3 = document.getElementById("r2c3");
+ var r2c4 = document.getElementById("r2c4");
+
+ await focusAndKeyPress(r2c1, kArrowUp);
+ assert_equals(document.activeElement, r1c1);
+
+ await focusAndKeyPress(r2c2, kArrowUp);
+ assert_equals(document.activeElement, r1c2);
+
+ await focusAndKeyPress(r2c3, kArrowUp);
+ assert_equals(document.activeElement, r1c2);
+
+ await focusAndKeyPress(r2c4, kArrowUp);
+ assert_equals(document.activeElement, r1c4);
+ }, "An up arrow press should move the focus to the right cell on the previous row, dealing correctly with colspans.");
+
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/grid-navigation/css-table-not-focusgroup.html b/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/grid-navigation/css-table-not-focusgroup.html
new file mode 100644
index 0000000000..c0aea689ae
--- /dev/null
+++ b/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/grid-navigation/css-table-not-focusgroup.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>HTML Test: focusgroup - Validate that Focusgroup doesn't work on CSS table when the focusgroup attribute is not present.</title>
+<link rel="author" title="Microsoft" href="http://www.microsoft.com/">
+<link rel="help" href="https://github.com/MicrosoftEdge/MSEdgeExplainers/blob/main/Focusgroup/explainer.md">
+<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="../resources/focusgroup-utils.js"></script>
+
+<div style="display:table">
+ <div style="display:table-row">
+ <div id=r1c1 style="display:table-cell" tabindex=0>r1c1</div>
+ <div id=r1c2 style="display:table-cell" tabindex=-1>r1c2</div>
+ </div>
+ <div style="display:table-row">
+ <div id=r2c1 style="display:table-cell" tabindex=-1>r2c1</div>
+ <div id=r2c2 style="display:table-cell" tabindex=-1>r2c2</div>
+ </div>
+</div>
+
+<script>
+ promise_test(async t => {
+ var r1c1 = document.getElementById("r1c1");
+
+ await focusAndKeyPress(r1c1, kArrowRight);
+ assert_equals(document.activeElement, r1c1);
+ }, "Validates that a CSS table that doesn't have the focusgroup=grid attribute set doesn't allow arrow-keys navigation");
+
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/grid-navigation/css-table.html b/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/grid-navigation/css-table.html
new file mode 100644
index 0000000000..45e1e2c472
--- /dev/null
+++ b/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/grid-navigation/css-table.html
@@ -0,0 +1,44 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>HTML Test: focusgroup - Validate that Focusgroup works with CSS tables too.</title>
+<link rel="author" title="Microsoft" href="http://www.microsoft.com/">
+<link rel="help" href="https://github.com/MicrosoftEdge/MSEdgeExplainers/blob/main/Focusgroup/explainer.md">
+<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="../resources/focusgroup-utils.js"></script>
+
+<div style="display:table" focusgroup=grid>
+ <div style="display:table-row">
+ <div id=r1c1 style="display:table-cell" tabindex=0>r1c1</div>
+ <div id=r1c2 style="display:table-cell" tabindex=-1>r1c2</div>
+ </div>
+ <div style="display:table-row">
+ <div id=r2c1 style="display:table-cell" tabindex=-1>r2c1</div>
+ <div id=r2c2 style="display:table-cell" tabindex=-1>r2c2</div>
+ </div>
+</div>
+
+<script>
+ promise_test(async t => {
+ var r1c1 = document.getElementById("r1c1");
+ var r1c2 = document.getElementById("r1c2");
+ var r2c1 = document.getElementById("r2c1");
+ var r2c2 = document.getElementById("r2c2");
+
+ await focusAndKeyPress(r1c1, kArrowRight);
+ assert_equals(document.activeElement, r1c2);
+
+ await focusAndKeyPress(r1c2, kArrowDown);
+ assert_equals(document.activeElement, r2c2);
+
+ await focusAndKeyPress(r2c2, kArrowLeft);
+ assert_equals(document.activeElement, r2c1);
+
+ await focusAndKeyPress(r2c1, kArrowUp);
+ assert_equals(document.activeElement, r1c1);
+ }, "Tests that grid focusgroups also work on CSS tables (i.e.: 'display: table'). The implementation relies on the layout objects, so the other tests that covers HTML tables don't need to be duplicated to test the same cases with CSS tables.");
+
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/grid-navigation/does-not-wrap-or-flow.html b/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/grid-navigation/does-not-wrap-or-flow.html
new file mode 100644
index 0000000000..050dec3622
--- /dev/null
+++ b/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/grid-navigation/does-not-wrap-or-flow.html
@@ -0,0 +1,56 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>HTML Test: focusgroup - Simple case that validated we don't wrap or flow when these values aren't provided.</title>
+<link rel="author" title="Microsoft" href="http://www.microsoft.com/">
+<link rel="help" href="https://github.com/MicrosoftEdge/MSEdgeExplainers/blob/main/Focusgroup/explainer.md">
+<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="../resources/focusgroup-utils.js"></script>
+
+<table focusgroup=grid>
+ <tr>
+ <td id=r1c1 tabindex=0>r1c1</td>
+ <td id=r1c2 tabindex=-1>r1c2</td>
+ <td id=r1c3 tabindex=-1>r1c3</td>
+ </tr>
+ <tr>
+ <td id=r2c1 tabindex=-1>r2c1</td>
+ <td id=r2c2 tabindex=-1>r2c2</td>
+ <td id=r2c3 tabindex=-1>r2c3</td>
+ </tr>
+</table>
+
+<script>
+
+ promise_test(async t => {
+ var r1c3 = document.getElementById("r1c3");
+
+ await focusAndKeyPress(r1c3, kArrowRight);
+ assert_equals(document.activeElement, r1c3);
+ }, "A right arrow press should not move the focus when it is at the last column and the focusgroup doesn't wrap or flow.");
+
+ promise_test(async t => {
+ var r2c1 = document.getElementById("r2c1");
+
+ await focusAndKeyPress(r2c1, kArrowDown);
+ assert_equals(document.activeElement, r2c1);
+ }, "A down arrow press should not move the focus when it is at the last row and the focusgroup doesn't wrap or flow.");
+
+ promise_test(async t => {
+ var r1c1 = document.getElementById("r1c1");
+
+ await focusAndKeyPress(r1c1, kArrowLeft);
+ assert_equals(document.activeElement, r1c1);
+ }, "A left arrow press should not move the focus when it is at the first column and the focusgroup doesn't wrap or flow.");
+
+ promise_test(async t => {
+ var r1c1 = document.getElementById("r1c1");
+
+ await focusAndKeyPress(r1c1, kArrowUp);
+ assert_equals(document.activeElement, r1c1);
+ }, "An up arrow press should not move the focus when it is at the first row and the focusgroup doesn't wrap or flow.");
+
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/grid-navigation/empty-spaces.html b/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/grid-navigation/empty-spaces.html
new file mode 100644
index 0000000000..90a09e1a94
--- /dev/null
+++ b/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/grid-navigation/empty-spaces.html
@@ -0,0 +1,144 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>HTML Test: focusgroup - Validate that empty spaces are not troubling our expectations.</title>
+<link rel="author" title="Microsoft" href="http://www.microsoft.com/">
+<link rel="help" href="https://github.com/MicrosoftEdge/MSEdgeExplainers/blob/main/Focusgroup/explainer.md">
+<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="../resources/focusgroup-utils.js"></script>
+
+<table focusgroup="grid flow">
+ <tr>
+ <td id=r1c1 tabindex=-1 rowspan=2 colspan=2>r1c1</td>
+ <td id=r1c3 tabindex=-1>r1c3</td>
+ <td id=r1c4 tabindex=-1 rowspan=3>r1c4</td>
+ <td id=r1c5 tabindex=-1>r1c5</td>
+ <td id=r1c6 tabindex=-1>r1c6</td>
+ <td id=r1c7 tabindex=-1>r1c7</td>
+ </tr>
+ <tr>
+ <!-- r2c1 and r2c2 starts in the previous row and spans to here. -->
+ <td id=r2c3 tabindex=-1>r2c3</td>
+ <!-- Leave the rest of the row empty -->
+ </tr>
+ <tr>
+ <td id=r3c1 tabindex=-1>r3c1</td>
+ <td id=r3c2 tabindex=-1>r3c2</td>
+ <!-- There will be a cell at r3c4, but it starts in row 1. -->
+ </tr>
+ <tr>
+ <td id=r4c1 tabindex=-1 colspan=5>r4c1</td>
+ <td id=r4c6 tabindex=-1>r4c6</td>
+ <!-- No last cell - leave it empty for the test -->
+ </tr>
+</table>
+
+<script>
+ // The following tests are very corner-case.
+ //
+ // We are creating empty spaces that don't have cells through a weird table
+ // structure. The spaces at the following locations don't have cells (assuming
+ // that the first row/column starts at index 1): r2c5, r2c6, r2c7, r3c3,
+ // r3c5, r3c6, r3c7 and r4c7.
+
+ promise_test(async t => {
+ var r1c1 = document.getElementById("r1c1");
+ var r1c4 = document.getElementById("r1c4");
+ var r1c5 = document.getElementById("r1c5");
+ var r2c3 = document.getElementById("r2c3");
+ var r3c2 = document.getElementById("r3c2");
+ var r4c6 = document.getElementById("r4c6");
+
+ await focusAndKeyPress(r3c2, kArrowRight);
+ assert_equals(document.activeElement, r1c4);
+
+ await focusAndKeyPress(r1c4, kArrowRight);
+ assert_equals(document.activeElement, r1c5);
+
+ await focusAndKeyPress(r4c6, kArrowRight);
+ assert_equals(document.activeElement, r1c1);
+
+ await focusAndKeyPress(r2c3, kArrowRight);
+ assert_equals(document.activeElement, r1c4);
+ }, "A right arrow press should move the focus to the next column, dealing correctly with the empty spaces.");
+
+ promise_test(async t => {
+ var r1c1 = document.getElementById("r1c1");
+ var r1c4 = document.getElementById("r1c4");
+ var r1c5 = document.getElementById("r1c5");
+ var r1c6 = document.getElementById("r1c6");
+ var r1c7 = document.getElementById("r1c7");
+ var r2c3 = document.getElementById("r2c3");
+ var r4c1 = document.getElementById("r4c1");
+ var r4c6 = document.getElementById("r4c6");
+
+ await focusAndKeyPress(r2c3, kArrowDown);
+ assert_equals(document.activeElement, r4c1);
+
+ await focusAndKeyPress(r1c5, kArrowDown);
+ assert_equals(document.activeElement, r4c1);
+
+ await focusAndKeyPress(r1c6, kArrowDown);
+ assert_equals(document.activeElement, r4c6);
+
+ // Goes to r1c1 because it flows to the first cell of the first column when
+ // on the last cell of the last column.
+ await focusAndKeyPress(r1c7, kArrowDown);
+ assert_equals(document.activeElement, r1c1);
+
+ await focusAndKeyPress(r4c6, kArrowDown);
+ assert_equals(document.activeElement, r1c7);
+
+ await focusAndKeyPress(r1c4, kArrowDown);
+ assert_equals(document.activeElement, r4c1);
+ }, "A down arrow press should move the focus to the right cell on the next row, dealing correctly with empty spaces.");
+
+ promise_test(async t => {
+ var r1c1 = document.getElementById("r1c1");
+ var r1c3 = document.getElementById("r1c3");
+ var r1c4 = document.getElementById("r1c4");
+ var r3c1 = document.getElementById("r3c1");
+ var r4c1 = document.getElementById("r4c1");
+ var r4c6 = document.getElementById("r4c6");
+
+ await focusAndKeyPress(r1c1, kArrowLeft);
+ assert_equals(document.activeElement, r4c6);
+
+ await focusAndKeyPress(r3c1, kArrowLeft);
+ assert_equals(document.activeElement, r1c4);
+
+ await focusAndKeyPress(r1c4, kArrowLeft);
+ assert_equals(document.activeElement, r1c3);
+
+ await focusAndKeyPress(r4c1, kArrowLeft);
+ assert_equals(document.activeElement, r1c4);
+ }, "A left arrow press should move to the previous column, dealing correctly with the empty spaces.");
+
+ promise_test(async t => {
+ var r1c1 = document.getElementById("r1c1");
+ var r1c6 = document.getElementById("r1c6");
+ var r1c7 = document.getElementById("r1c7");
+ var r3c1 = document.getElementById("r3c1");
+ var r4c1 = document.getElementById("r4c1");
+ var r4c6 = document.getElementById("r4c6");
+
+ await focusAndKeyPress(r1c7, kArrowUp);
+ assert_equals(document.activeElement, r4c6);
+
+ await focusAndKeyPress(r4c6, kArrowUp);
+ assert_equals(document.activeElement, r1c6);
+
+ await focusAndKeyPress(r1c6, kArrowUp);
+ assert_equals(document.activeElement, r4c1);
+
+ await focusAndKeyPress(r4c1, kArrowUp);
+ assert_equals(document.activeElement, r3c1);
+
+ await focusAndKeyPress(r1c1, kArrowUp);
+ assert_equals(document.activeElement, r1c7);
+ }, "An up arrow press should move the focus to the right cell on the previous row, dealing correctly with empty spaces.");
+
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/grid-navigation/flows-in-both-axes.html b/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/grid-navigation/flows-in-both-axes.html
new file mode 100644
index 0000000000..86fc25456b
--- /dev/null
+++ b/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/grid-navigation/flows-in-both-axes.html
@@ -0,0 +1,60 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>HTML Test: focusgroup - Validate that flow allows both horizontal and vertical flowing.</title>
+<link rel="author" title="Microsoft" href="http://www.microsoft.com/">
+<link rel="help" href="https://github.com/MicrosoftEdge/MSEdgeExplainers/blob/main/Focusgroup/explainer.md">
+<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="../resources/focusgroup-utils.js"></script>
+
+<table focusgroup="grid flow">
+ <tr>
+ <td id=r1c1 tabindex=0>r1c1</td>
+ <td id=r1c2 tabindex=-1>r1c2</td>
+ <td id=r1c3 tabindex=-1>r1c3</td>
+ </tr>
+ <tr>
+ <td id=r2c1 tabindex=-1>r2c1</td>
+ <td id=r2c2 tabindex=-1>r2c2</td>
+ <td id=r2c3 tabindex=-1>r2c3</td>
+ </tr>
+</table>
+
+<script>
+
+ promise_test(async t => {
+ var r1c3 = document.getElementById("r1c3");
+ var r2c1 = document.getElementById("r2c1");
+
+ await focusAndKeyPress(r1c3, kArrowRight);
+ assert_equals(document.activeElement, r2c1);
+ }, "When on the last column, a right arrow press should move the focus to first column and next row if the focusgroup flows horizontally.");
+
+ promise_test(async t => {
+ var r1c2 = document.getElementById("r1c2");
+ var r2c1 = document.getElementById("r2c1");
+
+ await focusAndKeyPress(r2c1, kArrowDown);
+ assert_equals(document.activeElement, r1c2);
+ }, "When on the last row, a down arrow press should move the focus to first row and next column if the focusgroup flows horizontally.");
+
+ promise_test(async t => {
+ var r1c3 = document.getElementById("r1c3");
+ var r2c1 = document.getElementById("r2c1");
+
+ await focusAndKeyPress(r2c1, kArrowLeft);
+ assert_equals(document.activeElement, r1c3);
+ }, "When on the first column, a left arrow press should move the focus to last column and previous row if the focusgroup flows horizontally.");
+
+ promise_test(async t => {
+ var r1c1 = document.getElementById("r1c1");
+ var r2c3 = document.getElementById("r2c3");
+
+ await focusAndKeyPress(r1c1, kArrowUp);
+ assert_equals(document.activeElement, r2c3);
+ }, "When on the first row, an up arrow press should move the focus to last row and previous column if the focusgroup flows horizontally.");
+
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/grid-navigation/moves-across-table-sections.html b/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/grid-navigation/moves-across-table-sections.html
new file mode 100644
index 0000000000..d7d1b55f45
--- /dev/null
+++ b/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/grid-navigation/moves-across-table-sections.html
@@ -0,0 +1,97 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>HTML Test: focusgroup - Table with one or less row per section (head, 2 bodies and foot).</title>
+<link rel="author" title="Microsoft" href="http://www.microsoft.com/">
+<link rel="help" href="https://github.com/MicrosoftEdge/MSEdgeExplainers/blob/main/Focusgroup/explainer.md">
+<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="../resources/focusgroup-utils.js"></script>
+
+<table focusgroup="grid wrap">
+ <thead>
+ <tr>
+ <td id=r1c1 tabindex=0>r1c1</td>
+ </tr>
+ <tr>
+ <td id=r2c1 tabindex=-1>r2c1</td>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <td id=r3c1 tabindex=-1>r3c1</td>
+ </tr>
+ <tr>
+ <td id=r4c1 tabindex=-1>r4c1</td>
+ </tr>
+ </tbody>
+ <tbody></tbody>
+ <tfoot>
+ <tr>
+ <td id=r5c1 tabindex=-1>r5c1</td>
+ </tr>
+ <tr>
+ <td id=r6c1 tabindex=-1>r6c1</td>
+ </tr>
+ </foot>
+</table>
+
+<script>
+
+ promise_test(async t => {
+ var r1c1 = document.getElementById("r1c1");
+ var r1c2 = document.getElementById("r2c1");
+ var r3c2 = document.getElementById("r3c1");
+ var r4c2 = document.getElementById("r4c1");
+ var r5c2 = document.getElementById("r5c1");
+ var r6c2 = document.getElementById("r6c1");
+
+ await focusAndKeyPress(r1c1, kArrowDown);
+ assert_equals(document.activeElement, r2c1);
+
+ await focusAndKeyPress(r2c1, kArrowDown);
+ assert_equals(document.activeElement, r3c1);
+
+ await focusAndKeyPress(r3c1, kArrowDown);
+ assert_equals(document.activeElement, r4c1);
+
+ await focusAndKeyPress(r4c1, kArrowDown);
+ assert_equals(document.activeElement, r5c1);
+
+ await focusAndKeyPress(r5c1, kArrowDown);
+ assert_equals(document.activeElement, r6c1);
+
+ await focusAndKeyPress(r6c1, kArrowDown);
+ assert_equals(document.activeElement, r1c1);
+ }, "A down arrow press should move the focus to the next row even when that row is in another section.");
+
+ promise_test(async t => {
+ var r1c1 = document.getElementById("r1c1");
+ var r1c2 = document.getElementById("r2c1");
+ var r3c2 = document.getElementById("r3c1");
+ var r4c2 = document.getElementById("r4c1");
+ var r5c2 = document.getElementById("r5c1");
+ var r6c2 = document.getElementById("r6c1");
+
+ await focusAndKeyPress(r6c1, kArrowUp);
+ assert_equals(document.activeElement, r5c1);
+
+ await focusAndKeyPress(r5c1, kArrowUp);
+ assert_equals(document.activeElement, r4c1);
+
+ await focusAndKeyPress(r4c1, kArrowUp);
+ assert_equals(document.activeElement, r3c1);
+
+ await focusAndKeyPress(r3c1, kArrowUp);
+ assert_equals(document.activeElement, r2c1);
+
+ await focusAndKeyPress(r2c1, kArrowUp);
+ assert_equals(document.activeElement, r1c1);
+
+ await focusAndKeyPress(r1c1, kArrowUp);
+ assert_equals(document.activeElement, r6c1);
+ }, "An up arrow press should move the focus to the previous row even when that row is in another section.");
+
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/grid-navigation/non-table.html b/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/grid-navigation/non-table.html
new file mode 100644
index 0000000000..c02de186e6
--- /dev/null
+++ b/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/grid-navigation/non-table.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>HTML Test: focusgroup - Validate that Focusgroup doesn't work when not set on a table element</title>
+<link rel="author" title="Microsoft" href="http://www.microsoft.com/">
+<link rel="help" href="https://github.com/MicrosoftEdge/MSEdgeExplainers/blob/main/Focusgroup/explainer.md">
+<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="../resources/focusgroup-utils.js"></script>
+
+<div focusgroup=grid>
+ <span id=nonitem1 tabindex=0>nonitem1</span>
+ <span id=nonitem2 tabindex=-1>nonitem2</span>
+</div>
+
+<script>
+ promise_test(async t => {
+ var nonitem1 = document.getElementById("nonitem1");
+
+ await focusAndKeyPress(nonitem1, kArrowRight);
+ assert_equals(document.activeElement, nonitem1);
+ }, "Validates that focusgroup=grid set on a non table layout doesn't become a grid focusgroup nor a linear one.");
+
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/grid-navigation/relayout-before-navigation.html b/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/grid-navigation/relayout-before-navigation.html
new file mode 100644
index 0000000000..d1fd0ce6eb
--- /dev/null
+++ b/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/grid-navigation/relayout-before-navigation.html
@@ -0,0 +1,39 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>HTML Test: focusgroup - Relayout before navigating in a grid</title>
+<link rel="author" title="Microsoft" href="http://www.microsoft.com/">
+<link rel="help" href="https://github.com/MicrosoftEdge/MSEdgeExplainers/blob/main/Focusgroup/explainer.md">
+<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="../resources/focusgroup-utils.js"></script>
+<script>
+ function removeMiddleRow() {
+ document.getElementById("middle-row").remove();
+ }
+</script>
+<table focusgroup="grid">
+ <tr>
+ <td id="item1" tabindex="0" onkeydown="removeMiddleRow()">item1</td>
+ </tr>
+ <tr id="middle-row">
+ <td id="item2" tabindex="-1">item2</td>
+ </tr>
+ <tr>
+ <td id="item3" tabindex="-1">item3</td>
+ </tr>
+</table>
+
+<script>
+
+ promise_test(async t => {
+ var item1 = document.getElementById("item1");
+ var item3 = document.getElementById("item3");
+
+ await focusAndKeyPress(item1, kArrowDown);
+ assert_equals(document.activeElement, item3);
+ }, "Since |item1| removes the middle row on key press, the grid focusgroup should check for a relayout before navigating to the next row.");
+
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/grid-navigation/row-flow-only.html b/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/grid-navigation/row-flow-only.html
new file mode 100644
index 0000000000..138b1b33d5
--- /dev/null
+++ b/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/grid-navigation/row-flow-only.html
@@ -0,0 +1,58 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>HTML Test: focusgroup - Validate that row-flow allows only horizontal flowing.</title>
+<link rel="author" title="Microsoft" href="http://www.microsoft.com/">
+<link rel="help" href="https://github.com/MicrosoftEdge/MSEdgeExplainers/blob/main/Focusgroup/explainer.md">
+<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="../resources/focusgroup-utils.js"></script>
+
+<table focusgroup="grid row-flow">
+ <tr>
+ <td id=r1c1 tabindex=0>r1c1</td>
+ <td id=r1c2 tabindex=-1>r1c2</td>
+ <td id=r1c3 tabindex=-1>r1c3</td>
+ </tr>
+ <tr>
+ <td id=r2c1 tabindex=-1>r2c1</td>
+ <td id=r2c2 tabindex=-1>r2c2</td>
+ <td id=r2c3 tabindex=-1>r2c3</td>
+ </tr>
+</table>
+
+<script>
+
+ promise_test(async t => {
+ var r1c3 = document.getElementById("r1c3");
+ var r2c1 = document.getElementById("r2c1");
+
+ await focusAndKeyPress(r1c3, kArrowRight);
+ assert_equals(document.activeElement, r2c1);
+ }, "When on the last column, a right arrow press should move the focus to first column and next row if the focusgroup flows horizontally.");
+
+ promise_test(async t => {
+ var r2c1 = document.getElementById("r2c1");
+
+ await focusAndKeyPress(r2c1, kArrowDown);
+ assert_equals(document.activeElement, r2c1);
+ }, "A down arrow press should not flow the focus vertically when not supported by the focusgroup.");
+
+ promise_test(async t => {
+ var r1c3 = document.getElementById("r1c3");
+ var r2c1 = document.getElementById("r2c1");
+
+ await focusAndKeyPress(r2c1, kArrowLeft);
+ assert_equals(document.activeElement, r1c3);
+ }, "When on the first column, a left arrow press should move the focus to last column and previous row if the focusgroup flows horizontally.");
+
+ promise_test(async t => {
+ var r1c1 = document.getElementById("r1c1");
+
+ await focusAndKeyPress(r1c1, kArrowUp);
+ assert_equals(document.activeElement, r1c1);
+ }, "An up arrow press should not flow the focus vertically when not supported by the focusgroup.");
+
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/grid-navigation/row-wrap-and-col-flow.html b/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/grid-navigation/row-wrap-and-col-flow.html
new file mode 100644
index 0000000000..8ed69e2e3e
--- /dev/null
+++ b/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/grid-navigation/row-wrap-and-col-flow.html
@@ -0,0 +1,60 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>HTML Test: focusgroup - Validate that we can row-wrap AND col-flow.</title>
+<link rel="author" title="Microsoft" href="http://www.microsoft.com/">
+<link rel="help" href="https://github.com/MicrosoftEdge/MSEdgeExplainers/blob/main/Focusgroup/explainer.md">
+<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="../resources/focusgroup-utils.js"></script>
+
+<table focusgroup="grid row-wrap col-flow">
+ <tr>
+ <td id=r1c1 tabindex=0>r1c1</td>
+ <td id=r1c2 tabindex=-1>r1c2</td>
+ <td id=r1c3 tabindex=-1>r1c3</td>
+ </tr>
+ <tr>
+ <td id=r2c1 tabindex=-1>r2c1</td>
+ <td id=r2c2 tabindex=-1>r2c2</td>
+ <td id=r2c3 tabindex=-1>r2c3</td>
+ </tr>
+</table>
+
+<script>
+
+ promise_test(async t => {
+ var r1c1 = document.getElementById("r1c1");
+ var r1c3 = document.getElementById("r1c3");
+
+ await focusAndKeyPress(r1c3, kArrowRight);
+ assert_equals(document.activeElement, r1c1);
+ }, "When on the last column, a right arrow press should move the focus to first column if the focusgroup wraps horizontally.");
+
+ promise_test(async t => {
+ var r1c2 = document.getElementById("r1c2");
+ var r2c1 = document.getElementById("r2c1");
+
+ await focusAndKeyPress(r2c1, kArrowDown);
+ assert_equals(document.activeElement, r1c2);
+ }, "When on the last row, a down arrow press should move the focus to first row and next column if the focusgroup flows vertically.");
+
+ promise_test(async t => {
+ var r1c1 = document.getElementById("r1c1");
+ var r1c3 = document.getElementById("r1c3");
+
+ await focusAndKeyPress(r1c1, kArrowLeft);
+ assert_equals(document.activeElement, r1c3);
+ }, "When on the first column, a left arrow press should move the focus to last column if the focusgroup wraps horizontally.");
+
+ promise_test(async t => {
+ var r1c1 = document.getElementById("r1c1");
+ var r2c3 = document.getElementById("r2c3");
+
+ await focusAndKeyPress(r1c1, kArrowUp);
+ assert_equals(document.activeElement, r2c3);
+ }, "When on the first row, an up arrow press should move the focus to last row and previous column if the focusgroup flows horizontally.");
+
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/grid-navigation/row-wrap-only.html b/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/grid-navigation/row-wrap-only.html
new file mode 100644
index 0000000000..0154ee2125
--- /dev/null
+++ b/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/grid-navigation/row-wrap-only.html
@@ -0,0 +1,58 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>HTML Test: focusgroup - Validate that row-wrap allows only horizontal wrapping.</title>
+<link rel="author" title="Microsoft" href="http://www.microsoft.com/">
+<link rel="help" href="https://github.com/MicrosoftEdge/MSEdgeExplainers/blob/main/Focusgroup/explainer.md">
+<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="../resources/focusgroup-utils.js"></script>
+
+<table focusgroup="grid row-wrap">
+ <tr>
+ <td id=r1c1 tabindex=0>r1c1</td>
+ <td id=r1c2 tabindex=-1>r1c2</td>
+ <td id=r1c3 tabindex=-1>r1c3</td>
+ </tr>
+ <tr>
+ <td id=r2c1 tabindex=-1>r2c1</td>
+ <td id=r2c2 tabindex=-1>r2c2</td>
+ <td id=r2c3 tabindex=-1>r2c3</td>
+ </tr>
+</table>
+
+<script>
+
+ promise_test(async t => {
+ var r1c1 = document.getElementById("r1c1");
+ var r1c3 = document.getElementById("r1c3");
+
+ await focusAndKeyPress(r1c3, kArrowRight);
+ assert_equals(document.activeElement, r1c1);
+ }, "When on the last column, a right arrow press should move the focus to first column if the focusgroup wraps horizontally.");
+
+ promise_test(async t => {
+ var r2c1 = document.getElementById("r2c1");
+
+ await focusAndKeyPress(r2c1, kArrowDown);
+ assert_equals(document.activeElement, r2c1);
+ }, "A down arrow press should not wrap the focus to the first row when only horizontal wrap is supported by the focusgroup.");
+
+ promise_test(async t => {
+ var r1c1 = document.getElementById("r1c1");
+ var r1c3 = document.getElementById("r1c3");
+
+ await focusAndKeyPress(r1c1, kArrowLeft);
+ assert_equals(document.activeElement, r1c3);
+ }, "When on the first column, a left arrow press should move the focus to last column if the focusgroup wraps horizontally.");
+
+ promise_test(async t => {
+ var r1c1 = document.getElementById("r1c1");
+
+ await focusAndKeyPress(r1c1, kArrowUp);
+ assert_equals(document.activeElement, r1c1);
+ }, "An up arrow press should not wrap the focus to the last row when only horizontal wrap is supported by the focusgroup.");
+
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/grid-navigation/rowspan.html b/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/grid-navigation/rowspan.html
new file mode 100644
index 0000000000..81ab54ef8e
--- /dev/null
+++ b/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/grid-navigation/rowspan.html
@@ -0,0 +1,148 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>HTML Test: focusgroup - Validate that we deal correctly with rowspans.</title>
+<link rel="author" title="Microsoft" href="http://www.microsoft.com/">
+<link rel="help" href="https://github.com/MicrosoftEdge/MSEdgeExplainers/blob/main/Focusgroup/explainer.md">
+<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="../resources/focusgroup-utils.js"></script>
+
+<table focusgroup="grid">
+ <tr>
+ <td id=r1c1 tabindex=-1>r1c1</td>
+ <td id=r1c2 tabindex=-1>r1c2</td>
+ <td id=r1c3 tabindex=-1>r1c3</td>
+ </tr>
+ <tr>
+ <td id=r2c1 tabindex=0>r2c1</td>
+ <td id=r2c2 tabindex=-1 rowspan=3>r2c2</td>
+ <td id=r2c3 tabindex=-1>r3c3</td>
+ </tr>
+ <tr>
+ <td id=r3c1 tabindex=-1>r3c1</td>
+ <td id=r3c3 tabindex=-1>r3c3</td>
+ </tr>
+ <tr>
+ <td id=r4c1 tabindex=-1>r4c1</td>
+ <td id=r4c3 tabindex=-1>r4c3</td>
+ </tr>
+ <tr>
+ <td id=r5c1 tabindex=-1>r5c1</td>
+ <td id=r5c2 tabindex=-1>r5c2</td>
+ <td id=r5c3 tabindex=-1>r5c3</td>
+ </tr>
+</table>
+
+<script>
+
+ promise_test(async t => {
+ var r2c1 = document.getElementById("r2c1");
+ var r2c2 = document.getElementById("r2c2");
+ var r2c3 = document.getElementById("r2c3");
+ var r3c1 = document.getElementById("r3c1");
+ var r4c1 = document.getElementById("r4c1");
+ var r5c1 = document.getElementById("r5c1");
+ var r5c2 = document.getElementById("r5c2");
+
+ await focusAndKeyPress(r2c1, kArrowRight);
+ assert_equals(document.activeElement, r2c2);
+
+ await focusAndKeyPress(r2c2, kArrowRight);
+ assert_equals(document.activeElement, r2c3);
+
+ await focusAndKeyPress(r3c1, kArrowRight);
+ assert_equals(document.activeElement, r2c2);
+
+ await focusAndKeyPress(r4c1, kArrowRight);
+ assert_equals(document.activeElement, r2c2);
+
+ await focusAndKeyPress(r5c1, kArrowRight);
+ assert_equals(document.activeElement, r5c2);
+ }, "A right arrow press should move the focus to the next column, dealing correctly with rowspans.");
+
+ promise_test(async t => {
+ var r1c2 = document.getElementById("r1c2");
+ var r1c3 = document.getElementById("r1c3");
+ var r2c2 = document.getElementById("r2c2");
+ var r2c3 = document.getElementById("r2c3");
+ var r3c3 = document.getElementById("r3c3");
+ var r5c2 = document.getElementById("r5c2");
+ var r5c3 = document.getElementById("r5c3");
+
+ await focusAndKeyPress(r1c2, kArrowDown);
+ assert_equals(document.activeElement, r2c2);
+
+ await focusAndKeyPress(r1c3, kArrowDown);
+ assert_equals(document.activeElement, r2c3);
+
+ await focusAndKeyPress(r2c2, kArrowDown);
+ assert_equals(document.activeElement, r5c2);
+
+ await focusAndKeyPress(r2c3, kArrowDown);
+ assert_equals(document.activeElement, r3c3);
+
+ await focusAndKeyPress(r3c3, kArrowDown);
+ assert_equals(document.activeElement, r4c3);
+
+ await focusAndKeyPress(r4c3, kArrowDown);
+ assert_equals(document.activeElement, r5c3);
+ }, "A down arrow press should move the focus to the right cell on the next row, dealing correctly with rowspans.");
+
+ promise_test(async t => {
+ var r2c1 = document.getElementById("r2c1");
+ var r2c2 = document.getElementById("r2c2");
+ var r2c3 = document.getElementById("r2c3");
+ var r3c3 = document.getElementById("r3c3");
+ var r4c3 = document.getElementById("r4c3");
+ var r5c3 = document.getElementById("r5c3");
+ var r5c2 = document.getElementById("r5c2");
+
+ await focusAndKeyPress(r2c3, kArrowLeft);
+ assert_equals(document.activeElement, r2c2);
+
+ await focusAndKeyPress(r2c2, kArrowLeft);
+ assert_equals(document.activeElement, r2c1);
+
+ await focusAndKeyPress(r3c3, kArrowLeft);
+ assert_equals(document.activeElement, r2c2);
+
+ await focusAndKeyPress(r4c3, kArrowLeft);
+ assert_equals(document.activeElement, r2c2);
+
+ await focusAndKeyPress(r5c3, kArrowLeft);
+ assert_equals(document.activeElement, r5c2);
+ }, "A left arrow press should move to the previous column, dealing correctly with the rowspans.");
+
+ promise_test(async t => {
+ var r1c2 = document.getElementById("r1c2");
+ var r1c3 = document.getElementById("r1c3");
+ var r2c2 = document.getElementById("r2c2");
+ var r2c3 = document.getElementById("r2c3");
+ var r3c3 = document.getElementById("r3c3");
+ var r4c3 = document.getElementById("r4c3");
+ var r5c2 = document.getElementById("r5c2");
+ var r5c3 = document.getElementById("r5c3");
+
+ await focusAndKeyPress(r5c2, kArrowUp);
+ assert_equals(document.activeElement, r2c2);
+
+ await focusAndKeyPress(r5c3, kArrowUp);
+ assert_equals(document.activeElement, r4c3);
+
+ await focusAndKeyPress(r2c2, kArrowUp);
+ assert_equals(document.activeElement, r1c2);
+
+ await focusAndKeyPress(r4c3, kArrowUp);
+ assert_equals(document.activeElement, r3c3);
+
+ await focusAndKeyPress(r3c3, kArrowUp);
+ assert_equals(document.activeElement, r2c3);
+
+ await focusAndKeyPress(r2c3, kArrowUp);
+ assert_equals(document.activeElement, r1c3);
+ }, "An up arrow press should move the focus to the right cell on the previous row, dealing correctly with rowspans.");
+
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/grid-navigation/simple-case-with-non-focusable-cell-in-the-center.html b/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/grid-navigation/simple-case-with-non-focusable-cell-in-the-center.html
new file mode 100644
index 0000000000..25d2900a9f
--- /dev/null
+++ b/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/grid-navigation/simple-case-with-non-focusable-cell-in-the-center.html
@@ -0,0 +1,65 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>HTML Test: focusgroup - Simple case with grid focusgroup, but with the cell R2C2 not focusable.</title>
+<link rel="author" title="Microsoft" href="http://www.microsoft.com/">
+<link rel="help" href="https://github.com/MicrosoftEdge/MSEdgeExplainers/blob/main/Focusgroup/explainer.md">
+<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="../resources/focusgroup-utils.js"></script>
+
+<table focusgroup=grid>
+ <tr>
+ <td id=r1c1 tabindex=0>r1c1</td>
+ <td id=r1c2 tabindex=-1>r1c2</td>
+ <td id=r1c3 tabindex=-1>r1c3</td>
+ </tr>
+ <tr>
+ <td id=r2c1 tabindex=-1>r2c1</td>
+ <td id=r2c2>r2c2</td>
+ <td id=r2c3 tabindex=-1>r2c3</td>
+ </tr>
+ <tr>
+ <td id=r3c1 tabindex=-1>r3c1</td>
+ <td id=r3c2 tabindex=-1>r3c2</td>
+ <td id=r3c3 tabindex=-1>r3c3</td>
+ </tr>
+</table>
+
+<script>
+
+ promise_test(async t => {
+ var r2c1 = document.getElementById("r2c1");
+ var r2c3 = document.getElementById("r2c3");
+
+ await focusAndKeyPress(r2c1, kArrowRight);
+ assert_equals(document.activeElement, r2c3);
+ }, "A right arrow press should move the focus to the next column, skipping the non-focusable cell.");
+
+ promise_test(async t => {
+ var r1c2 = document.getElementById("r1c2");
+ var r3c2 = document.getElementById("r3c2");
+
+ await focusAndKeyPress(r1c2, kArrowDown);
+ assert_equals(document.activeElement, r3c2);
+ }, "A down arrow press should move the focus to the next row, skipping the non-focusable cell.");
+
+ promise_test(async t => {
+ var r2c1 = document.getElementById("r2c1");
+ var r2c3 = document.getElementById("r2c3");
+
+ await focusAndKeyPress(r2c3, kArrowLeft);
+ assert_equals(document.activeElement, r2c1);
+ }, "A left arrow press should move to the previous column, skipping the non-focusable cell.");
+
+ promise_test(async t => {
+ var r1c2 = document.getElementById("r1c2");
+ var r3c2 = document.getElementById("r3c2");
+
+ await focusAndKeyPress(r3c2, kArrowUp);
+ assert_equals(document.activeElement, r1c2);
+ }, "An up arrow press should move the focus to the previous row, skipping the non-focusable cell.");
+
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/grid-navigation/simple-case.html b/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/grid-navigation/simple-case.html
new file mode 100644
index 0000000000..738895ce0b
--- /dev/null
+++ b/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/grid-navigation/simple-case.html
@@ -0,0 +1,60 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>HTML Test: focusgroup - Simple case with grid focusgroup</title>
+<link rel="author" title="Microsoft" href="http://www.microsoft.com/">
+<link rel="help" href="https://github.com/MicrosoftEdge/MSEdgeExplainers/blob/main/Focusgroup/explainer.md">
+<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="../resources/focusgroup-utils.js"></script>
+
+<table focusgroup=grid>
+ <tr>
+ <td id=r1c1 tabindex=0>r1c1</td>
+ <td id=r1c2 tabindex=-1>r1c2</td>
+ <td id=r1c3 tabindex=-1>r1c3</td>
+ </tr>
+ <tr>
+ <td id=r2c1 tabindex=-1>r2c1</td>
+ <td id=r2c2 tabindex=-1>r2c2</td>
+ <td id=r2c3 tabindex=-1>r2c3</td>
+ </tr>
+</table>
+
+<script>
+
+ promise_test(async t => {
+ var r1c1 = document.getElementById("r1c1");
+ var r1c2 = document.getElementById("r1c2");
+
+ await focusAndKeyPress(r1c1, kArrowRight);
+ assert_equals(document.activeElement, r1c2);
+ }, "A right arrow press should move the focus to the next column.");
+
+ promise_test(async t => {
+ var r1c1 = document.getElementById("r1c1");
+ var r2c1 = document.getElementById("r2c1");
+
+ await focusAndKeyPress(r1c1, kArrowDown);
+ assert_equals(document.activeElement, r2c1);
+ }, "A down arrow press should move the focus to the next row.");
+
+ promise_test(async t => {
+ var r1c1 = document.getElementById("r1c1");
+ var r1c2 = document.getElementById("r1c2");
+
+ await focusAndKeyPress(r1c2, kArrowLeft);
+ assert_equals(document.activeElement, r1c1);
+ }, "A left arrow press should move to the previous column.");
+
+ promise_test(async t => {
+ var r1c1 = document.getElementById("r1c1");
+ var r2c1 = document.getElementById("r2c1");
+
+ await focusAndKeyPress(r2c1, kArrowUp);
+ assert_equals(document.activeElement, r1c1);
+ }, "An up arrow press should move the focus to the previous row.");
+
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/grid-navigation/wraps-in-both-axes.html b/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/grid-navigation/wraps-in-both-axes.html
new file mode 100644
index 0000000000..809ee53645
--- /dev/null
+++ b/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/grid-navigation/wraps-in-both-axes.html
@@ -0,0 +1,60 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>HTML Test: focusgroup - Validate that wrap allows both horizontal and vertical wrapping.</title>
+<link rel="author" title="Microsoft" href="http://www.microsoft.com/">
+<link rel="help" href="https://github.com/MicrosoftEdge/MSEdgeExplainers/blob/main/Focusgroup/explainer.md">
+<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="../resources/focusgroup-utils.js"></script>
+
+<table focusgroup="grid wrap">
+ <tr>
+ <td id=r1c1 tabindex=0>r1c1</td>
+ <td id=r1c2 tabindex=-1>r1c2</td>
+ <td id=r1c3 tabindex=-1>r1c3</td>
+ </tr>
+ <tr>
+ <td id=r2c1 tabindex=-1>r2c1</td>
+ <td id=r2c2 tabindex=-1>r2c2</td>
+ <td id=r2c3 tabindex=-1>r2c3</td>
+ </tr>
+</table>
+
+<script>
+
+ promise_test(async t => {
+ var r1c1 = document.getElementById("r1c1");
+ var r1c3 = document.getElementById("r1c3");
+
+ await focusAndKeyPress(r1c3, kArrowRight);
+ assert_equals(document.activeElement, r1c1);
+ }, "When on the last column, a right arrow press should move the focus to first column if the focusgroup wraps horizontally.");
+
+ promise_test(async t => {
+ var r1c1 = document.getElementById("r1c1");
+ var r2c1 = document.getElementById("r2c1");
+
+ await focusAndKeyPress(r2c1, kArrowDown);
+ assert_equals(document.activeElement, r1c1);
+ }, "When on the last row, a down arrow press should move the focus to first row if the focusgroup wraps vertically.");
+
+ promise_test(async t => {
+ var r1c1 = document.getElementById("r1c1");
+ var r1c3 = document.getElementById("r1c3");
+
+ await focusAndKeyPress(r1c1, kArrowLeft);
+ assert_equals(document.activeElement, r1c3);
+ }, "When on the first column, a left arrow press should move the focus to last column if the focusgroup wraps horizontally.");
+
+ promise_test(async t => {
+ var r1c1 = document.getElementById("r1c1");
+ var r2c1 = document.getElementById("r2c1");
+
+ await focusAndKeyPress(r1c1, kArrowUp);
+ assert_equals(document.activeElement, r2c1);
+ }, "When on the first row, an up arrow press should move the focus to last row if the focusgroup wraps vertically.");
+
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/resources/focusgroup-utils.js b/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/resources/focusgroup-utils.js
new file mode 100644
index 0000000000..9a1a14a301
--- /dev/null
+++ b/testing/web-platform/tests/html/interaction/focus/focusgroup/tentative/resources/focusgroup-utils.js
@@ -0,0 +1,15 @@
+/*
+ Methods for testing the focusgroup feature.
+*/
+
+// https://w3c.github.io/webdriver/#keyboard-actions
+const kArrowLeft = '\uE012';
+const kArrowUp = '\uE013';
+const kArrowRight = '\uE014';
+const kArrowDown = '\uE015';
+
+// Set the focus on target and send the arrow key press event from it.
+function focusAndKeyPress(target, key) {
+ target.focus();
+ return test_driver.send_keys(target, key);
+}
diff --git a/testing/web-platform/tests/html/interaction/focus/processing-model/focus-fixup-rule-one-no-dialogs.html b/testing/web-platform/tests/html/interaction/focus/processing-model/focus-fixup-rule-one-no-dialogs.html
new file mode 100644
index 0000000000..2413fe2667
--- /dev/null
+++ b/testing/web-platform/tests/html/interaction/focus/processing-model/focus-fixup-rule-one-no-dialogs.html
@@ -0,0 +1,109 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Focus fixup rule one (no &lt;dialog>s involved)</title>
+<link rel="author" title="Domenic Denicola" href="mailto:d@domenic.me">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/interaction.html#focus-fixup-rule">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/forms.html#attr-fieldset-disabled">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<div>
+ <button id="button1">Button 1</button>
+ <button id="button2">Button 2</button>
+ <button id="button3">Button 3</button>
+ <fieldset id="fieldset1"><button id="button4">Button 4</button></fieldset>
+ <fieldset id="fieldset2" disabled><legend><button id="button5">Button 5</button></legend></fieldset>
+ <div id="div" tabindex="0">Div</div>
+ <div id="editable" contenteditable=true>editor</div>
+</div>
+
+<script>
+"use strict";
+
+test(() => {
+ const button = document.querySelector("#button1");
+ button.focus();
+
+ assert_equals(document.activeElement, button, "Sanity check: the button must start focused");
+
+ button.disabled = true;
+
+ assert_not_equals(document.activeElement, button, "After disabling, the button must no longer be focused");
+ assert_equals(document.activeElement, document.body, "After disabling, the body must be focused");
+
+}, "Disabling the active element (making it inert)");
+
+test(() => {
+ const button = document.querySelector("#button2");
+ button.focus();
+
+ assert_equals(document.activeElement, button, "Sanity check: the button must start focused");
+
+ button.hidden = true;
+
+ assert_not_equals(document.activeElement, button, "After hiding, the button must no longer be focused");
+ assert_equals(document.activeElement, document.body, "After hiding, the body must be focused");
+
+}, "Hiding the active element");
+
+test(() => {
+ const button = document.querySelector("#button3");
+ button.focus();
+
+ assert_equals(document.activeElement, button, "Sanity check: the button must start focused");
+
+ button.remove();
+
+ assert_not_equals(document.activeElement, button, "After removing, the button must no longer be focused");
+ assert_equals(document.activeElement, document.body, "After removing, the body must be focused");
+
+}, "Removing the active element from the DOM");
+
+test(() => {
+ const fieldset = document.querySelector("#fieldset1");
+ const button = document.querySelector("#button4");
+ button.focus();
+ assert_equals(document.activeElement, button, "Sanity check: the button must start focused");
+
+ fieldset.disabled = true;
+
+ assert_not_equals(document.activeElement, button, "After disabling ancestor fieldset, the button must no longer be focused");
+ assert_equals(document.activeElement, document.body, "After disabling ancestor fieldset, the body must be focused");
+}, "Disabling <fieldset> affects its descendants");
+
+test(() => {
+ const fieldset = document.querySelector("#fieldset2");
+ const button = document.querySelector("#button5");
+ button.focus();
+ assert_equals(document.activeElement, button, "Sanity check: the button must start focused");
+
+ fieldset.insertBefore(document.createElement("legend"), fieldset.firstChild);
+
+ assert_not_equals(document.activeElement, button, "After changing a legend element, the button must no longer be focused");
+ assert_equals(document.activeElement, document.body, "After changing a legend element, the body must be focused");
+}, "Changing the first legend element in disabled <fieldset>");
+
+test(() => {
+ const div = document.querySelector("#div");
+ div.focus();
+
+ assert_equals(document.activeElement, div, "Sanity check: the div must start focused");
+
+ div.removeAttribute("tabindex");
+
+ assert_not_equals(document.activeElement, div, "After removing tabindex, the div must no longer be focused");
+ assert_equals(document.activeElement, document.body, "After removing tabindex, the body must be focused");
+
+}, "Removing the tabindex attribute from a div");
+
+test(() => {
+ const div = document.querySelector("#editable");
+ div.focus();
+ assert_equals(document.activeElement, div, "Sanity check: the div must start focused");
+
+ div.contentEditable = false;
+
+ assert_not_equals(document.activeElement, div, "After disabling contentEditable, the div must no longer be focused");
+ assert_equals(document.activeElement, document.body, "After disabling contentEditable, the body must be focused");
+}, "Disabling contenteditable");
+</script>
diff --git a/testing/web-platform/tests/html/interaction/focus/processing-model/focusVisible.html b/testing/web-platform/tests/html/interaction/focus/processing-model/focusVisible.html
new file mode 100644
index 0000000000..aa7e66fffe
--- /dev/null
+++ b/testing/web-platform/tests/html/interaction/focus/processing-model/focusVisible.html
@@ -0,0 +1,65 @@
+<!doctype html>
+
+<title>focus(options) - focusVisible</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-actions.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+
+<style>
+ div {
+ height: 10px;
+ border: 1px solid black;
+ }
+</style>
+
+<button>ABC</button>
+<input>
+<div id="contenteditable" contenteditable></div>
+<div id="tabindex" tabindex=0></div>
+<div id="not_focusable"></div>
+
+<div id="reset_focus" tabindex=0></div>
+
+<script>
+const reset_focus = document.getElementById("reset_focus");
+
+async function check_focus_visible(element, options, { shouldBeVisible, shouldBeFocused }) {
+ // Reset focus by clicking on a div, which should not show focus rings.
+ await test_driver.click(reset_focus);
+
+ assert_equals(document.activeElement, reset_focus, "Reset should be focused");
+ assert_true(reset_focus.matches(":focus"), "Clicking focusable div should match :focus");
+ assert_false(reset_focus.matches(":focus-visible"), "Clicking focusable div shouldn't match :focus-visible");
+
+ element.focus(options);
+
+ assert_equals(document.activeElement, shouldBeFocused ? element : reset_focus, "activeElement");
+ assert_equals(element.matches(":focus"), shouldBeFocused, ":focus");
+ assert_equals(element.matches(":focus-visible"), shouldBeVisible, ":focus-visible");
+}
+
+for (let selector of ["button", "input", "#contenteditable", "#tabindex", "#not_focusable"]) {
+ promise_test(async function() {
+ const takesKeyboardInput = selector == "#contenteditable" || selector == "input";
+ const shouldBeFocused = selector != "#not_focusable";
+
+ const element = document.querySelector(selector);
+
+ await check_focus_visible(element, {}, {
+ shouldBeVisible: takesKeyboardInput,
+ shouldBeFocused,
+ });
+
+ await check_focus_visible(element, { focusVisible: true }, {
+ shouldBeVisible: shouldBeFocused,
+ shouldBeFocused,
+ });
+ await check_focus_visible(element, { focusVisible: false }, {
+ shouldBeVisible: false,
+ shouldBeFocused,
+ });
+ }, "FocusOptions.focusVisible: " + selector);
+}
+</script>
diff --git a/testing/web-platform/tests/html/interaction/focus/processing-model/legend-focusable.html b/testing/web-platform/tests/html/interaction/focus/processing-model/legend-focusable.html
new file mode 100644
index 0000000000..c9209d3cf6
--- /dev/null
+++ b/testing/web-platform/tests/html/interaction/focus/processing-model/legend-focusable.html
@@ -0,0 +1,17 @@
+<!doctype html>
+<title>legend focusable</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const t = async_test();
+</script>
+<fieldset>
+ <legend tabindex=0 onfocus="t.step(() => { t.done(); })">
+ legend
+ <input onfocus="t.unreached_func('input in legend was focused')();">
+ </legend>
+ <input onfocus="t.unreached_func('input after legend was focused')();">
+</fieldset>
+<script>
+ document.querySelector('legend').focus();
+</script>
diff --git a/testing/web-platform/tests/html/interaction/focus/processing-model/legend.html b/testing/web-platform/tests/html/interaction/focus/processing-model/legend.html
new file mode 100644
index 0000000000..b53839374d
--- /dev/null
+++ b/testing/web-platform/tests/html/interaction/focus/processing-model/legend.html
@@ -0,0 +1,20 @@
+<!doctype html>
+<title>legend</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const t = async_test();
+</script>
+<fieldset>
+ <legend onfocus="t.unreached_func('legend was focused')()">
+ legend
+ <input onfocus="t.unreached_func('input in legend was focused')();">
+ </legend>
+ <input onfocus="t.unreached_func('input after legend was focused')();">
+</fieldset>
+<script>
+ document.querySelector('legend').focus();
+ t.step_timeout(() => {
+ t.done();
+ }, 500);
+</script>
diff --git a/testing/web-platform/tests/html/interaction/focus/processing-model/preventScroll-nested-scroll-elements.html b/testing/web-platform/tests/html/interaction/focus/processing-model/preventScroll-nested-scroll-elements.html
new file mode 100644
index 0000000000..a7ae76f733
--- /dev/null
+++ b/testing/web-platform/tests/html/interaction/focus/processing-model/preventScroll-nested-scroll-elements.html
@@ -0,0 +1,62 @@
+<!doctype html>
+<title>focus(options) - preventScroll on nested scroll elements</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<style>
+ .scrollBox { width: 400px; height: 300px; border: 1px solid }
+ .bigbox { width: 380px; height: 300px; border: 1px solid }
+ .box { width: 380px; height: 150px; border: 1px solid }
+</style>
+<div class="scrollBox" style="overflow-y: scroll;">
+ <div class="bigbox" id="1" style="overflow-y: scroll;" tabindex=1>
+ <div class="box" id="1_1" tabindex=1>1_1</div>
+ <div class="box" id="1_2" tabindex=1>1_2</div>
+ <div class="box" id="1_3" tabindex=1>1_3</div>
+ </div>
+ <div class="bigbox" id="2" style="overflow-y: scroll;" tabindex=1>
+ <div class="box" id="2_1" tabindex=1>2_1</div>
+ <div class="box" id="2_2" tabindex=1>2_2</div>
+ <div class="box" id="2_3" tabindex=1>2_3</div>
+ </div>
+ <div class="bigbox" id="3" style="overflow-y: scroll;" tabindex=1>
+ <div class="box" id="3_1" tabindex=1>3_1</div>
+ <div class="box" id="3_2" tabindex=1>3_2</div>
+ <div class="box" id="3_3" tabindex=1>3_3</div>
+ </div>
+</div>
+<script>
+promise_test(async function(t) {
+ await new Promise(resolve => window.addEventListener("load", resolve));
+ let div2_2 = document.getElementById("2_2");
+ div2_2.focus({ preventScroll: true });
+
+ await new Promise(resolve => {
+ requestAnimationFrame(() => requestAnimationFrame(resolve));
+ });
+
+ assert_equals(document.activeElement, div2_2, `box 2_2: should have been focused`);
+ assert_equals(div2_2.scrollTop, 0, "box 2_2: should not have scrolled");
+ assert_equals(div2_2.parentNode.scrollTop, 0, "box 2_2: should not have scrolled ancestor");
+
+ // Reset focus
+ let div1_1 = document.getElementById("1_1");
+ div1_1.focus();
+
+ await new Promise(resolve => {
+ requestAnimationFrame(() => requestAnimationFrame(resolve));
+ });
+ assert_equals(document.activeElement, div1_1, `box 1_1: should have been focused`);
+
+ let div2 = document.getElementById("2");
+ div2.focus({ preventScroll: true });
+
+ await new Promise(resolve => {
+ requestAnimationFrame(() => requestAnimationFrame(resolve));
+ });
+
+ assert_equals(document.activeElement, div2, `box 2: should have been focused`);
+ assert_equals(div2.scrollTop, 0, "box 2: should not have scrolled");
+ assert_equals(div2_2.scrollTop, 0, "box 2_2: should not have scrolled");
+ assert_equals(div2.parentNode.scrollTop, 0, "box 2: should not have scrolled ancestor");
+});
+</script>
diff --git a/testing/web-platform/tests/html/interaction/focus/processing-model/preventScroll-textarea.html b/testing/web-platform/tests/html/interaction/focus/processing-model/preventScroll-textarea.html
new file mode 100644
index 0000000000..446284b186
--- /dev/null
+++ b/testing/web-platform/tests/html/interaction/focus/processing-model/preventScroll-textarea.html
@@ -0,0 +1,40 @@
+<!doctype html>
+<title>focus(options) - preventScroll on textarea element</title>
+<link rel="author" href="https://mozilla.org" title="Mozilla">
+<link rel="author" href="mailto:emilio@crisal.io" title="Emilio Cobos Álvarez">
+<link rel="help" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1634153">
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<div style="height: 200vh"></div>
+<textarea>ABCD</textarea>
+<input value="EFGH">
+<button></button>
+<div style="height: 200vh"></div>
+<script>
+promise_test(async function(t) {
+ await new Promise(resolve => window.addEventListener("load", resolve));
+ let elements = document.querySelectorAll("textarea, input, button");
+ assert_equals(elements.length, 3, `Precondition`);
+ for (let element of elements) {
+ let name = element.nodeName;
+ assert_equals(window.scrollY, 0, `${name}: Precondition`);
+ element.focus({ preventScroll: true });
+ assert_equals(window.scrollY, 0, `${name}: Should not have scrolled`);
+ assert_equals(document.activeElement, element, `${name}: Should have been focused`);
+
+ // Wait a couple event loop turns because the original bug was triggered off
+ // an async event.
+ await new Promise(resolve => t.step_timeout(resolve, 0));
+ await new Promise(resolve => t.step_timeout(resolve, 0));
+ assert_equals(window.scrollY, 0, `${name}: Should not have scrolled after a couple event loop ticks`);
+ assert_equals(document.activeElement, element, `${name}: Should remain focused`);
+
+ // Also wait for rendering, just out of paranoia.
+ await new Promise(resolve => requestAnimationFrame(resolve));
+ await new Promise(resolve => requestAnimationFrame(resolve));
+
+ assert_equals(window.scrollY, 0, `${name}: Should not have scrolled after rendering`);
+ assert_equals(document.activeElement, element, `${name}: Should remain focused after rendering`);
+ }
+}, "preventScroll: true on a textarea element");
+</script>
diff --git a/testing/web-platform/tests/html/interaction/focus/processing-model/preventScroll.html b/testing/web-platform/tests/html/interaction/focus/processing-model/preventScroll.html
new file mode 100644
index 0000000000..97d341b30e
--- /dev/null
+++ b/testing/web-platform/tests/html/interaction/focus/processing-model/preventScroll.html
@@ -0,0 +1,76 @@
+<!doctype html>
+<title>focus(options) - preventScroll</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<style>
+#iframe { width: 500px; height: 500px; border: none }
+</style>
+<iframe id=iframe src="support/preventScroll-helper.html"></iframe>
+<script>
+function isEntirelyInView(elm, win) {
+ const inViewHorizontal = (elm.offsetLeft >= win.scrollX) &&
+ ((elm.offsetLeft + elm.clientWidth) <= (win.scrollX + win.innerWidth));
+ const inViewVertical = (elm.offsetTop >= win.scrollY) &&
+ ((elm.offsetTop + elm.clientHeight) <= (win.scrollY + win.innerHeight));
+ return inViewHorizontal && inViewVertical;
+}
+
+setup({explicit_done: true});
+
+function resetState(win) {
+ win.scrollTo(0, 0);
+ win.document.activeElement.blur();
+}
+
+onload = () => {
+ const win = document.getElementById('iframe').contentWindow;
+ const elm = win.document.getElementById('button');
+
+ test(() => {
+ assert_false(isEntirelyInView(elm, win), 'initial state');
+ elm.scrollIntoView();
+ assert_true(isEntirelyInView(elm, win), 'after elm.scrollIntoView()');
+ resetState(win);
+ assert_false(isEntirelyInView(elm, win), 'after resetScrollPosition(win)');
+ }, 'Sanity test');
+
+ test(() => {
+ resetState(win);
+ elm.focus();
+ assert_true(isEntirelyInView(elm, win));
+ }, 'elm.focus() without arguments');
+
+ test(() => {
+ resetState(win);
+ elm.focus(undefined);
+ assert_true(isEntirelyInView(elm, win));
+ }, 'elm.focus(undefined)');
+
+ test(() => {
+ resetState(win);
+ elm.focus(null);
+ assert_true(isEntirelyInView(elm, win));
+ }, 'elm.focus(null)');
+
+ test(() => {
+ resetState(win);
+ elm.focus({});
+ assert_true(isEntirelyInView(elm, win));
+ }, 'elm.focus({})');
+
+ test(() => {
+ resetState(win);
+ elm.focus({preventScroll: false});
+ assert_true(isEntirelyInView(elm, win));
+ }, 'elm.focus({preventScroll: false})');
+
+ test(() => {
+ resetState(win);
+ elm.focus({preventScroll: true});
+ assert_equals(win.scrollX, 0);
+ assert_equals(win.scrollY, 0);
+ }, 'elm.focus({preventScroll: true})');
+
+ done();
+}
+</script>
diff --git a/testing/web-platform/tests/html/interaction/focus/processing-model/support/preventScroll-helper.html b/testing/web-platform/tests/html/interaction/focus/processing-model/support/preventScroll-helper.html
new file mode 100644
index 0000000000..43c6d86a57
--- /dev/null
+++ b/testing/web-platform/tests/html/interaction/focus/processing-model/support/preventScroll-helper.html
@@ -0,0 +1,6 @@
+<!doctype html>
+<title>Helper document for preventScroll test</title>
+<style>
+body { padding: 2000px }
+</style>
+<button id=button>X</button>
diff --git a/testing/web-platform/tests/html/interaction/focus/processing-model/textarea-scroll-selection.html b/testing/web-platform/tests/html/interaction/focus/processing-model/textarea-scroll-selection.html
new file mode 100644
index 0000000000..c8e252f7da
--- /dev/null
+++ b/testing/web-platform/tests/html/interaction/focus/processing-model/textarea-scroll-selection.html
@@ -0,0 +1,46 @@
+<!doctype html>
+<title>programatic focus() scrolls selection into view including ancestors</title>
+<link rel="author" href="https://mozilla.org" title="Mozilla">
+<link rel="author" href="mailto:emilio@crisal.io" title="Emilio Cobos Álvarez">
+<link rel="help" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1644366">
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<div style="overflow: auto; height: 100px">
+ <textarea style="overflow: hidden; height: 200px">
+ Some text
+ That is surely more
+ Than 100px tall
+ For sure.
+ For sure.
+ For sure.
+ For sure.
+ For sure.
+ For sure.
+ For sure.
+ For sure.
+ For sure.
+ For sure.
+ For sure.
+ For sure.
+ For sure.
+ For sure.
+ For sure.
+ For sure.
+ For sure.
+ For sure.
+ </textarea>
+</div>
+<script>
+promise_test(async function(t) {
+ await new Promise(resolve => window.addEventListener("load", resolve));
+ let textarea = document.querySelector("textarea");
+ textarea.setSelectionRange(textarea.value.length, textarea.value.length);
+ textarea.focus();
+
+ await new Promise(resolve => {
+ requestAnimationFrame(() => requestAnimationFrame(resolve));
+ });
+
+ assert_not_equals(textarea.parentNode.scrollTop, 0, "Should've scrolled ancestor to show the selection");
+});
+</script>
diff --git a/testing/web-platform/tests/html/interaction/focus/sequential-focus-navigation-and-the-tabindex-attribute/focus-tabindex-default-value.html b/testing/web-platform/tests/html/interaction/focus/sequential-focus-navigation-and-the-tabindex-attribute/focus-tabindex-default-value.html
new file mode 100644
index 0000000000..25e359c2a2
--- /dev/null
+++ b/testing/web-platform/tests/html/interaction/focus/sequential-focus-navigation-and-the-tabindex-attribute/focus-tabindex-default-value.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>HTML Test: focus - default value of tabindex</title>
+<link rel="author" title="Intel" href="http://www.intel.com/">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#sequential-focus-navigation-and-the-tabindex-attribute">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<button id="test1">TEST1</button>
+<div id="test2">TEST2</div>
+<script>
+
+test(function() {
+ assert_equals(document.getElementById("test1").tabIndex, 0, "The value of tabIndex attribute should be 0.");
+}, "The default value of tabIndex attribute must be 0 for elements that are focusable");
+
+test(function() {
+ assert_equals(document.getElementById("test2").tabIndex, -1, "The value of tabIndex attribute should be -1.");
+}, "The default value of tabIndex attribute must be -1 for elements that are not focusable");
+
+</script>
diff --git a/testing/web-platform/tests/html/interaction/focus/sequential-focus-navigation-and-the-tabindex-attribute/focus-tabindex-negative.html b/testing/web-platform/tests/html/interaction/focus/sequential-focus-navigation-and-the-tabindex-attribute/focus-tabindex-negative.html
new file mode 100644
index 0000000000..859de94517
--- /dev/null
+++ b/testing/web-platform/tests/html/interaction/focus/sequential-focus-navigation-and-the-tabindex-attribute/focus-tabindex-negative.html
@@ -0,0 +1,45 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>HTML Test: focus - negative tabindex</title>
+<meta name="timeout" content="long">
+<link rel="author" title="Intel" href="http://www.intel.com/">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#sequential-focus-navigation-and-the-tabindex-attribute">
+<meta assert="flag" content="interact">
+<meta assert="assert" content="Check if the tabindex attribute controls whether an element is supposed to be focusable">
+<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>
+<div id="log"></div>
+<form id="fm">
+ <input id="test1" tabindex="-1">
+ <input id="test2" tabindex="0">
+</form>
+<script>
+
+var t = async_test("The element with a negative tabindex must not be focused by press 'Tab' key");
+
+setup({timeout: 10000});
+
+document.forms.fm.addEventListener("focus", function (evt) {
+ t.step(function () {
+ var testEle = document.getElementById("test1");
+ assert_equals(testEle.tabIndex, -1, "The tabIndex attribute of the first input element should be -1.");
+ assert_not_equals(evt.target, testEle, "The second input element must be focused.");
+ assert_equals(document.activeElement, document.getElementById("test2"), "The second input element must be activated.");
+ });
+ t.done();
+}, true);
+
+document.addEventListener("keydown", function (evt) {
+ t.step(function () {
+ assert_equals(evt.keyCode, 9, "Please press 'Tab' key.");
+ });
+}, true);
+
+t.step(function () {
+ // TAB = '\ue004'
+ test_driver.send_keys(document.body, "\ue004");
+});
+
+</script>
diff --git a/testing/web-platform/tests/html/interaction/focus/sequential-focus-navigation-and-the-tabindex-attribute/focus-tabindex-order.html b/testing/web-platform/tests/html/interaction/focus/sequential-focus-navigation-and-the-tabindex-attribute/focus-tabindex-order.html
new file mode 100644
index 0000000000..d2ca0a6224
--- /dev/null
+++ b/testing/web-platform/tests/html/interaction/focus/sequential-focus-navigation-and-the-tabindex-attribute/focus-tabindex-order.html
@@ -0,0 +1,65 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>HTML Test: focus - the sequential focus navigation order</title>
+<meta name="timeout" content="long">
+<link rel="author" title="Intel" href="http://www.intel.com/">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/interaction.html#sequential-focus-navigation">
+<meta assert="flag" content="interact">
+<meta assert="assert" content="Check the sequential focus navigation order">
+<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>
+<div id="log"></div>
+<form id="fm">
+ <button id="btn0">tabindex(omitted)</button>
+ <button id="btn1" tabindex="">tabindex(empty)</button>
+ <button id="btn2" tabindex="a">tabindex(a)</button>
+ <button id="btn3" tabindex="-1">tabindex(-1)</button>
+ <button id="btn4" tabindex="0">tabindex(0)</button>
+ <button id="btn5" tabindex="3">tabindex(3)</button>
+ <button id="btn6" tabindex="2">tabindex(2)</button>
+ <button id="btn7" tabindex="2">tabindex(2)</button>
+ <button id="btn8" tabindex="2">tabindex(2)</button>
+ <button id="btn9" tabindex="1">tabindex(1)</button>
+</form>
+<script>
+
+var i = 0,
+ expectation = ["btn9", "btn6", "btn7", "btn8", "btn5", "btn0", "btn1", "btn2", "btn4"],
+ results = [],
+ t = async_test("Elements with different tabindex must be focused sequentially when pressing 'Tab' keys");
+
+setup(function () {
+ document.body.focus();
+});
+
+
+
+document.forms.fm.addEventListener("focus", function (evt) {
+ results.push(evt.target.id);
+ if (i >= 8) {
+ t.step(function () {
+ assert_array_equals(results, expectation);
+ });
+ t.done();
+ } else {
+ t.step(function () {
+ // TAB = '\ue004'
+ test_driver.send_keys(document.body, "\ue004");
+ });
+ }
+ i++;
+}, true);
+
+document.addEventListener("keydown", function (evt) {
+ t.step(function () {
+ assert_equals(evt.keyCode, 9, "Please press 'Tab' key.");
+ });
+}, true);
+
+t.step(function () {
+ // TAB = '\ue004'
+ test_driver.send_keys(document.body, "\ue004");
+});
+</script>
diff --git a/testing/web-platform/tests/html/interaction/focus/sequential-focus-navigation-and-the-tabindex-attribute/focus-tabindex-positive.html b/testing/web-platform/tests/html/interaction/focus/sequential-focus-navigation-and-the-tabindex-attribute/focus-tabindex-positive.html
new file mode 100644
index 0000000000..1fab1f3adb
--- /dev/null
+++ b/testing/web-platform/tests/html/interaction/focus/sequential-focus-navigation-and-the-tabindex-attribute/focus-tabindex-positive.html
@@ -0,0 +1,44 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>HTML Test: focus - positive tabindex</title>
+<meta name="timeout" content="long">
+<link rel="author" title="Intel" href="http://www.intel.com/">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#sequential-focus-navigation-and-the-tabindex-attribute">
+<meta assert="flag" content="interact">
+<meta assert="assert" content="Check if the tabindex attribute controls whether an element is supposed to be focusable">
+<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>
+<div id="log"></div>
+<form id="fm">
+ <input id="test" tabindex="1">
+</form>
+<script>
+
+var t = async_test("The element with a positive tabindex must be focused by press 'Tab' key");
+
+setup({timeout: 10000});
+
+document.forms.fm.addEventListener("focus", function (evt) {
+ t.step(function () {
+ var testEle = document.getElementById("test");
+ assert_equals(testEle.tabIndex, 1, "The tabIndex attribute of the input element should be 1.");
+ assert_equals(evt.target, testEle, "The input element must be focused.");
+ assert_equals(document.activeElement, testEle, "The input element must be activated.");
+ });
+ t.done();
+}, true);
+
+document.addEventListener("keydown", function (evt) {
+ t.step(function () {
+ assert_equals(evt.keyCode, 9, "Please press 'Tab' key.");
+ });
+}, true);
+
+t.step(function () {
+ // TAB = '\ue004'
+ test_driver.send_keys(document.body, "\ue004");
+});
+
+</script>
diff --git a/testing/web-platform/tests/html/interaction/focus/sequential-focus-navigation-and-the-tabindex-attribute/focus-tabindex-zero.html b/testing/web-platform/tests/html/interaction/focus/sequential-focus-navigation-and-the-tabindex-attribute/focus-tabindex-zero.html
new file mode 100644
index 0000000000..bda7c84687
--- /dev/null
+++ b/testing/web-platform/tests/html/interaction/focus/sequential-focus-navigation-and-the-tabindex-attribute/focus-tabindex-zero.html
@@ -0,0 +1,46 @@
+<!DOCTYPE html>
+<head>
+<meta charset="utf-8">
+<title>HTML Test: focus - zero tabindex</title>
+<meta name="timeout" content="long">
+<link rel="author" title="Intel" href="http://www.intel.com/">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#sequential-focus-navigation-and-the-tabindex-attribute">
+<meta assert="flag" content="interact">
+<meta assert="assert" content="Check if the tabindex attribute controls whether an element is supposed to be focusable">
+<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>
+</head>
+<div id="log"></div>
+<form id="fm">
+ <input id="test" tabindex="0">
+</form>
+<script>
+
+var t = async_test("The element with a zero tabindex must be focused by press 'Tab' key");
+
+setup({timeout: 10000});
+
+document.forms.fm.addEventListener("focus", function (evt) {
+ t.step(function () {
+ var testEle = document.getElementById("test");
+ assert_equals(testEle.tabIndex, 0, "The tabIndex attribute of the input element should be 0.");
+ assert_equals(evt.target, testEle, "The input element must be focused.");
+ assert_equals(document.activeElement, testEle, "The input element must be activated.");
+ });
+ t.done();
+}, true);
+
+document.addEventListener("keydown", function (evt) {
+ t.step(function () {
+ assert_equals(evt.keyCode, 9, "Please press 'Tab' key.");
+ });
+}, true);
+
+t.step(function () {
+ // TAB = '\ue004'
+ test_driver.send_keys(document.body, "\ue004");
+});
+
+</script>
diff --git a/testing/web-platform/tests/html/interaction/focus/sequential-focus-navigation-and-the-tabindex-attribute/resources/frameset-using-page.html b/testing/web-platform/tests/html/interaction/focus/sequential-focus-navigation-and-the-tabindex-attribute/resources/frameset-using-page.html
new file mode 100644
index 0000000000..e3aedea246
--- /dev/null
+++ b/testing/web-platform/tests/html/interaction/focus/sequential-focus-navigation-and-the-tabindex-attribute/resources/frameset-using-page.html
@@ -0,0 +1,6 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>We'll grab a frame from this page to test its tabIndex</title>
+<frameset>
+ <frame></frame>
+</frameset>
diff --git a/testing/web-platform/tests/html/interaction/focus/sequential-focus-navigation-and-the-tabindex-attribute/tabindex-getter-frame.html b/testing/web-platform/tests/html/interaction/focus/sequential-focus-navigation-and-the-tabindex-attribute/tabindex-getter-frame.html
new file mode 100644
index 0000000000..27a92f76ab
--- /dev/null
+++ b/testing/web-platform/tests/html/interaction/focus/sequential-focus-navigation-and-the-tabindex-attribute/tabindex-getter-frame.html
@@ -0,0 +1,55 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>HTML Test: tabIndex getter return value for frames</title>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/interaction.html#dom-tabindex">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<!-- <frame> elements are harder to test than the rest so they get their own file -->
+
+<body>
+
+<script>
+"use strict";
+test(() => {
+ const frame = document.createElement("frame");
+ assert_equals(frame.tabIndex, 0);
+}, "disconnected frame element .tabIndex should return 0 by default");
+
+for (const setValue of [-1, 0, 1]) {
+ test(() => {
+ const frame = document.createElement("frame");
+ frame.setAttribute("tabindex", setValue);
+ assert_equals(frame.tabIndex, setValue);
+ }, `disconnected frame element .tabIndex should return ${setValue} when set to ${setValue}`);
+}
+
+promise_test(async t => {
+ const frame = await getFrame(t);
+ assert_equals(frame.tabIndex, 0);
+}, "connected frame element inside frameset .tabIndex should return 0 by default");
+
+for (const setValue of [-1, 0, 1]) {
+ promise_test(async t => {
+ const frame = await getFrame(t);
+ frame.setAttribute("tabindex", setValue);
+ assert_equals(frame.tabIndex, setValue);
+ }, `connected frame element inside frameset .tabIndex should return ${setValue} when set to ${setValue}`);
+}
+
+
+function getFrame(t) {
+ return new Promise((resolve, reject) => {
+ const iframe = document.createElement("iframe");
+ t.add_cleanup(() => iframe.remove());
+
+ iframe.src = "resources/frameset-using-page.html";
+ iframe.onload = () => {
+ resolve(iframe.contentDocument.querySelector("frame"));
+ };
+ iframe.onerror = () => reject(new Error("Could not load frameset page"));
+
+ document.body.append(iframe);
+ });
+}
+</script>
diff --git a/testing/web-platform/tests/html/interaction/focus/sequential-focus-navigation-and-the-tabindex-attribute/tabindex-getter.html b/testing/web-platform/tests/html/interaction/focus/sequential-focus-navigation-and-the-tabindex-attribute/tabindex-getter.html
new file mode 100644
index 0000000000..785e8c91ff
--- /dev/null
+++ b/testing/web-platform/tests/html/interaction/focus/sequential-focus-navigation-and-the-tabindex-attribute/tabindex-getter.html
@@ -0,0 +1,95 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>HTML Test: tabIndex getter return value</title>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/interaction.html#dom-tabindex">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<style>
+#scrollable {
+ width: 100px;
+ height: 100px;
+ overflow: scroll;
+}
+#scrollable-inner {
+ width: 1024px;
+ height: 2048px;
+}
+</style>
+<body>
+<input>
+<input type="hidden">
+<button>button</button>
+<button disabled>button</button>
+<button hidden>button</button>
+<a>a</a>
+<a href="#">a</a>
+<svg><a>svg a</a></svg>
+<svg><a>svg a</a></svg>
+<link id="nohref">
+<textarea></textarea>
+<select><optgroup><option>option</option></optgroup></select>
+<select multiple></select>
+<iframe width="10" height="10"></iframe>
+<embed width="10" height="10"></embed>
+<object width="10" height="10"></object>
+<span></span>
+<div></div>
+<details><summary>summary</summary><summary id="secondsummary">second summary</summary>details</details>
+<div id="hostDelegatesFocus"></div>
+<div id="hostNonDelegatesFocus"></div>
+<div contenteditable="true"></div>
+<div id="scrollable"><div id="scrollable-inner"></div></div>
+<fieldset></fieldset>
+<output></output>
+<slot></slot>
+<script>
+document.getElementById("hostDelegatesFocus").attachShadow({ mode: "open", delegatesFocus: true });
+document.getElementById("hostNonDelegatesFocus").attachShadow({ mode: "open", delegatesFocus: false });
+const defaultList = [
+ ["input", 0],
+ ["input[type='hidden']", 0],
+ ["button", 0],
+ ["button[disabled]", 0],
+ ["button[hidden]", 0],
+ ["a", 0],
+ ["a[href]", 0],
+ ["svg a", 0],
+ ["textarea", 0],
+ ["select", 0],
+ ["select[multiple]", 0],
+ ["option", -1],
+ ["optgroup", -1],
+ ["iframe", 0],
+ ["embed", -1],
+ ["object", 0],
+ ["span", -1],
+ ["div", -1],
+ ["link#nohref", -1],
+ ["link[href]", -1],
+ ["details", -1],
+ ["summary", 0],
+ ["summary#secondsummary", -1],
+ ["#hostDelegatesFocus", -1],
+ ["#hostNonDelegatesFocus", -1],
+ ["div[contenteditable]", -1],
+ ["#scrollable", -1],
+ ["fieldset", -1],
+ ["output", -1],
+ ["slot", -1],
+];
+const tabIndexValue = [-1, 0, 1];
+for (const entry of defaultList) {
+ const element = document.querySelector(entry[0]);
+ test(() => {
+ assert_equals(element.tabIndex, entry[1]);
+ }, entry[0] + ".tabIndex should return " + entry[1] + " by default");
+ for (const setValue of tabIndexValue ) {
+ test(() => {
+ element.setAttribute("tabindex", setValue);
+ assert_equals(element.tabIndex, setValue);
+ }, entry[0] + ".tabIndex should return " + setValue + " when set to " + setValue);
+ }
+}
+</script>
+</body>
+
diff --git a/testing/web-platform/tests/html/interaction/focus/setSequentialFocusStartingPoint.tentative.html b/testing/web-platform/tests/html/interaction/focus/setSequentialFocusStartingPoint.tentative.html
new file mode 100644
index 0000000000..a7c7506b13
--- /dev/null
+++ b/testing/web-platform/tests/html/interaction/focus/setSequentialFocusStartingPoint.tentative.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<link rel=author href="mailto:jarhar@chromium.org">
+<link rel=help href="https://github.com/whatwg/html/issues/5326">
+<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>
+
+<button id=b1>b1</button>
+<div id=d1>d1</div>
+<button id=b2>b2</button>
+
+<script>
+const tabKey = '\uE004';
+promise_test(async () => {
+ assert_equals(document.activeElement, document.body,
+ 'Focus should initially be set on the body element.');
+
+ document.setSequentialFocusStartingPoint(d1);
+ assert_equals(document.activeElement, document.body,
+ 'Calling setSequentialFocusStartingPoint should not change the focused element.');
+
+ await test_driver.send_keys(document.activeElement, tabKey);
+ assert_equals(document.activeElement, b2,
+ 'Pressing tab should focus the next button after the sequential focus starting point.');
+}, 'document.setSequentialFocusStartingPoint should set the sequential focus starting point on any element.');
+</script>
diff --git a/testing/web-platform/tests/html/interaction/focus/tabindex-focus-flag.html b/testing/web-platform/tests/html/interaction/focus/tabindex-focus-flag.html
new file mode 100644
index 0000000000..93fdb19a59
--- /dev/null
+++ b/testing/web-platform/tests/html/interaction/focus/tabindex-focus-flag.html
@@ -0,0 +1,134 @@
+<!DOCTYPE html>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/interaction.html#specially-focusable">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<body>
+<div id="default-samples">
+<a></a>
+<a href=""></a>
+<button></button>
+<input type="hidden">
+<input type="button">
+<select><option>abc</option></select>
+<textarea></textarea>
+<summary id="summary-out"></summary>
+<details open><summary id="summary-first"></summary><summary id="summary-second"></summary></details>
+<div contenteditable="true"></div>
+<iframe></iframe>
+<svg><a id="svg-a"></a></svg>
+<svg><text id="svg-text"></text></svg>
+<img>
+</div>
+<script>
+setup({ explicit_done: true });
+window.addEventListener("load", runTests);
+
+function runTests() {
+ const defaultList = [
+ ['a', false],
+ ['a[href]', true],
+ ['button', true],
+ ['input[type="hidden"]', false],
+ ['input[type="button"]', true],
+ ['select', true],
+ ['textarea', true],
+ ['#summary-out', false],
+ ['#summary-first', true],
+ ['#summary-second', false],
+ ['[contenteditable]', true],
+ ['iframe', true],
+ ['#svg-a', false],
+ ['#svg-text', false],
+ ['img', false],
+ ];
+ for (entry of defaultList) {
+ test(() => {
+ var element = document.querySelector('#default-samples ' + entry[0]);
+ element.focus();
+ if (entry[1])
+ assert_equals(document.activeElement, element);
+ else
+ assert_not_equals(document.activeElement, element);
+ }, entry[0] + ' should ' + (entry[1] ? '' : 'not ') + 'be focusable by default.');
+ }
+
+ runTests_tabindex0();
+}
+</script>
+
+<div id="tabindex-0">
+<a tabindex="0"></a>
+<svg><a tabindex="0"></a></svg>
+<svg><text tabindex="0"></text></svg>
+<summary tabindex="0" id="summary-out-tabindex0"></summary>
+<details open><summary id="summary-first"></summary><summary tabindex="0" id="summary-second-tabindex0"></summary></details>
+<img tabindex="0">
+</div>
+<script>
+function runTests_tabindex0() {
+ for (element of document.querySelectorAll('#tabindex-0 [tabindex]')) {
+ var elementDesc = element.tagName;
+ if (element.id)
+ elementDesc += '#' + element.id;
+ test(() => {
+ element.focus();
+ assert_equals(document.activeElement, element);
+ }, elementDesc + ' with tabindex=0 should be focusable.');
+ }
+
+ runTests_tabindex_negative();
+}
+</script>
+
+<div id="tabindex-negative">
+<a tabindex="-1"></a>
+<svg><a tabindex="-1"></a></svg>
+<svg><text tabindex="-1"></text></svg>
+<summary tabindex="-1" id="summary-out-tabindex-negative"></summary>
+<details open><summary id="summary-first"></summary><summary tabindex="0" id="summary-second-tabindex-negative"></summary></details>
+<img tabindex="-1">
+</div>
+<script>
+function runTests_tabindex_negative() {
+ for (element of document.querySelectorAll('#tabindex-negative [tabindex]')) {
+ var elementDesc = element.tagName;
+ if (element.id)
+ elementDesc += '#' + element.id;
+ test(() => {
+ element.focus();
+ assert_equals(document.activeElement, element);
+ }, elementDesc + ' with tabindex=-1 should be focusable.');
+ }
+
+ runTests_tabindex_invalid();
+}
+</script>
+
+<div id="tabindex-invalid">
+<a tabindex="invalid"></a>
+<a href="#" tabindex="invalid" id="with-href" data-focusable=true></a>
+<svg><a tabindex="invalid"></a></svg>
+<svg><a href="#" tabindex="invalid" id="with-href" data-focusable=true></a></svg>
+<svg><text tabindex="invalid"></text></svg>
+<div tabindex="invalid"></div>
+<summary tabindex="invalid" id="summary-out-tabindex-invalid"></summary>
+<img tabindex="invalid">
+</div>
+<script>
+function runTests_tabindex_invalid() {
+ for (element of document.querySelectorAll('#tabindex-invalid [tabindex]')) {
+ var focusable = element.dataset && element.dataset.focusable;
+ var elementDesc = element.tagName;
+ if (element.id)
+ elementDesc += '#' + element.id;
+ test(() => {
+ element.focus();
+ focusable ? assert_equals(document.activeElement, element)
+ : assert_not_equals(document.activeElement, element);
+ }, `${elementDesc} with tabindex=invalid should ${focusable ? "be" : "not be"} focusable.`);
+ }
+
+ done();
+}
+</script>
+</body>
diff --git a/testing/web-platform/tests/html/interaction/focus/the-autofocus-attribute/autofocus-area.html b/testing/web-platform/tests/html/interaction/focus/the-autofocus-attribute/autofocus-area.html
new file mode 100644
index 0000000000..e1ff72eb61
--- /dev/null
+++ b/testing/web-platform/tests/html/interaction/focus/the-autofocus-attribute/autofocus-area.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/interaction.html#the-autofocus-attribute">
+<link rel='author' href='mailto:sefeng@mozilla.com'>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/utils.js"></script>
+
+<map name="mymap">
+ <area id="myarea" shape="circle" coords="100,100,80" href="#" autofocus>
+</map>
+<img style="width:200px; height: 200px"
+ usemap="#mymap"
+ src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAIAAACQd1PeAAAADElEQVR42mP4%2F58BAAT%2FAf9jgNErAAAAAElFTkSuQmCC">
+<script>
+promise_test(async t => {
+ await waitForLoad(window);
+ await waitUntilStableAutofocusState(window);
+ const area = document.querySelector("area");
+ assert_equals(document.activeElement, area);
+}, 'autofocus works on area element');
+</script>
diff --git a/testing/web-platform/tests/html/interaction/focus/the-autofocus-attribute/autofocus-dialog.html b/testing/web-platform/tests/html/interaction/focus/the-autofocus-attribute/autofocus-dialog.html
new file mode 100644
index 0000000000..08a0913f42
--- /dev/null
+++ b/testing/web-platform/tests/html/interaction/focus/the-autofocus-attribute/autofocus-dialog.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/interaction.html#the-autofocus-attribute">
+<link rel='author' href='mailto:masonf@chromium.org'>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/utils.js"></script>
+
+<body>
+<script>
+promise_test(async t => {
+ let w = window.open('/common/blank.html');
+ await waitForLoad(w);
+ t.add_cleanup(() => { w.close(); });
+ w.document.body.innerHTML = '<dialog><div tabindex=0 autofocus></dialog><input autofocus>';
+ await waitUntilStableAutofocusState(w);
+ assert_equals(w.document.activeElement.tagName, 'INPUT');
+}, '<dialog> can contain autofocus, without stopping page autofocus content from working');
+
+promise_test(async t => {
+ let w = window.open('/common/blank.html');
+ await waitForLoad(w);
+ t.add_cleanup(() => { w.close(); });
+ w.document.body.innerHTML = '<dialog><div tabindex=0 autofocus></dialog><input autofocus>';
+ await waitUntilStableAutofocusState(w);
+ w.document.querySelector('dialog').show();
+ assert_equals(w.document.activeElement.tagName, 'DIV');
+}, '<dialog>-contained autofocus element gets focused when the dialog is shown');
+</script>
diff --git a/testing/web-platform/tests/html/interaction/focus/the-autofocus-attribute/autofocus-in-not-fully-active-document.html b/testing/web-platform/tests/html/interaction/focus/the-autofocus-attribute/autofocus-in-not-fully-active-document.html
new file mode 100644
index 0000000000..a26a44dbfb
--- /dev/null
+++ b/testing/web-platform/tests/html/interaction/focus/the-autofocus-attribute/autofocus-in-not-fully-active-document.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/utils.js"></script>
+
+<script>
+'use strict';
+
+promise_test(async () => {
+ let doc = document.cloneNode(false);
+ doc.appendChild(doc.createElement('html'))
+ doc.firstChild.innerHTML = '<body><input autofocus/></body>';
+ await waitUntilStableAutofocusState();
+ assert_equals(doc.activeElement, doc.body);
+}, 'Autofocus element in not-fully-active document should not be queued.');
+</script>
diff --git a/testing/web-platform/tests/html/interaction/focus/the-autofocus-attribute/autofocus-on-stable-document.html b/testing/web-platform/tests/html/interaction/focus/the-autofocus-attribute/autofocus-on-stable-document.html
new file mode 100644
index 0000000000..47e3e3fd0a
--- /dev/null
+++ b/testing/web-platform/tests/html/interaction/focus/the-autofocus-attribute/autofocus-on-stable-document.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/utils.js"></script>
+
+<body>
+<script>
+'use strict';
+
+promise_test(async t => {
+ await waitForLoad(window);
+ await timeOut(t, 1000);
+ let element = document.createElement('input');
+ element.autofocus = true;
+ document.body.appendChild(element);
+ await waitUntilStableAutofocusState();
+ assert_equals(document.activeElement, element);
+}, 'Autofocus should work if an element with autofocus is inserted into a ' +
+ 'document which was loaded some time ago.');
+</script>
+</body>
diff --git a/testing/web-platform/tests/html/interaction/focus/the-autofocus-attribute/document-with-fragment-empty.html b/testing/web-platform/tests/html/interaction/focus/the-autofocus-attribute/document-with-fragment-empty.html
new file mode 100644
index 0000000000..ec9d16d498
--- /dev/null
+++ b/testing/web-platform/tests/html/interaction/focus/the-autofocus-attribute/document-with-fragment-empty.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/utils.js"></script>
+
+<iframe src="resources/frame-with-autofocus-element.html#"></iframe>
+
+<script>
+'use strict';
+
+promise_test(async () => {
+ await waitForLoad(window);
+ await waitUntilStableAutofocusState();
+ assert_equals(document.activeElement, document.querySelector('iframe'),
+ 'Autofocus elements in iframes should be focused.');
+
+ let input = document.createElement('input');
+ input.autofocus = true;
+ document.body.appendChild(input);
+ await waitUntilStableAutofocusState();
+ assert_not_equals(document.activeElement, input);
+}, 'Autofocus elements in iframed documents with empty fragments should work.');
+
+promise_test(async () => {
+ let w = window.open('resources/frame-with-autofocus-element.html#');
+ await waitForLoad(w);
+ await waitUntilStableAutofocusState(w);
+ assert_not_equals(w.document.activeElement, w.document.body);
+ w.close();
+}, 'Autofocus elements in top-level browsing context\'s documents with empty fragments should work.');
+</script>
diff --git a/testing/web-platform/tests/html/interaction/focus/the-autofocus-attribute/document-with-fragment-nonexistent.html b/testing/web-platform/tests/html/interaction/focus/the-autofocus-attribute/document-with-fragment-nonexistent.html
new file mode 100644
index 0000000000..3f2dcc4167
--- /dev/null
+++ b/testing/web-platform/tests/html/interaction/focus/the-autofocus-attribute/document-with-fragment-nonexistent.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/utils.js"></script>
+
+<iframe src="resources/frame-with-autofocus-element.html#non-existent"></iframe>
+
+<script>
+'use strict';
+
+promise_test(async () => {
+ await waitForLoad(window);
+ await waitUntilStableAutofocusState();
+ assert_equals(document.activeElement, document.querySelector('iframe'),
+ 'Autofocus elements in iframes should be focused.');
+
+ let input = document.createElement('input');
+ input.autofocus = true;
+ document.body.appendChild(input);
+ await waitUntilStableAutofocusState();
+ assert_not_equals(document.activeElement, input);
+}, 'Autofocus elements in iframed documents with non-existent fragments should work.');
+
+promise_test(async () => {
+ let w = window.open('resources/frame-with-autofocus-element.html#non-existent');
+ await waitForLoad(w);
+ await waitUntilStableAutofocusState(w);
+ assert_not_equals(w.document.activeElement, w.document.body);
+ w.close();
+}, 'Autofocus elements in top-level browsing context\'s documents with non-existent fragments should work.');
+</script>
diff --git a/testing/web-platform/tests/html/interaction/focus/the-autofocus-attribute/document-with-fragment-top.html b/testing/web-platform/tests/html/interaction/focus/the-autofocus-attribute/document-with-fragment-top.html
new file mode 100644
index 0000000000..f2d2aaae60
--- /dev/null
+++ b/testing/web-platform/tests/html/interaction/focus/the-autofocus-attribute/document-with-fragment-top.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/utils.js"></script>
+
+<iframe src="resources/frame-with-autofocus-element.html#top"></iframe>
+
+<script>
+'use strict';
+
+promise_test(async () => {
+ await waitForLoad(window);
+ const iframe = document.querySelector('iframe');
+ await waitUntilStableAutofocusState();
+ assert_equals(document.activeElement, iframe,
+ 'Autofocus elements in iframes should be focused.');
+ const doc = iframe.contentDocument;
+ assert_true(!doc.querySelector(':target'));
+
+ let input = document.createElement('input');
+ input.autofocus = true;
+ document.body.appendChild(input);
+ await waitUntilStableAutofocusState();
+ assert_not_equals(document.activeElement, input);
+}, 'Autofocus elements in iframed documents with "top" fragments should work.');
+
+promise_test(async () => {
+ let w = window.open('resources/frame-with-autofocus-element.html#top');
+ await waitForLoad(w);
+ await waitUntilStableAutofocusState(w);
+ assert_not_equals(w.document.activeElement, w.document.body);
+ w.close();
+}, 'Autofocus elements in top-level browsing context\'s documents with "top" fragments should work.');
+</script>
diff --git a/testing/web-platform/tests/html/interaction/focus/the-autofocus-attribute/document-with-fragment-valid.html b/testing/web-platform/tests/html/interaction/focus/the-autofocus-attribute/document-with-fragment-valid.html
new file mode 100644
index 0000000000..5078f1a0ac
--- /dev/null
+++ b/testing/web-platform/tests/html/interaction/focus/the-autofocus-attribute/document-with-fragment-valid.html
@@ -0,0 +1,57 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/utils.js"></script>
+
+<iframe src="resources/frame-with-anchor.html"></iframe>
+
+<script>
+'use strict';
+
+promise_test(async () => {
+ await waitForLoad(window);
+ const iframe = document.querySelector('iframe');
+ iframe.contentWindow.location.hash = 'anchor1';
+ await waitForEvent(iframe.contentWindow, 'hashchange');
+ const doc = iframe.contentDocument;
+ assert_true(!!doc.querySelector(':target'));
+
+ let input = doc.createElement('input');
+ input.autofocus = true;
+ doc.body.appendChild(input);
+ await waitUntilStableAutofocusState();
+ assert_not_equals(doc.activeElement, input);
+ iframe.remove();
+}, 'Autofocus elements in iframed documents with URL fragments should be skipped. (id matches)');
+
+promise_test(async () => {
+ let iframe = await waitForIframeLoad("resources/frame-with-a.html");
+ iframe.contentWindow.location.hash = 'anchor1';
+ await waitForEvent(iframe.contentWindow, 'hashchange');
+ const doc = iframe.contentDocument;
+ assert_true(!!doc.querySelector(':target'));
+
+ let input = doc.createElement('input');
+ input.autofocus = true;
+ doc.body.appendChild(input);
+ await waitUntilStableAutofocusState();
+ assert_not_equals(doc.activeElement, input);
+ iframe.remove();
+}, 'Autofocus elements in iframed documents with URL fragments should be skipped.(a element)');
+
+promise_test(async () => {
+ let w = window.open('resources/frame-with-anchor.html');
+ await waitForLoad(w);
+ w.location.hash = 'anchor1';
+ await waitForEvent(w, 'hashchange');
+ const doc = w.document;
+ assert_true(!!doc.querySelector(':target'));
+
+ let input = doc.createElement('input');
+ input.autofocus = true;
+ doc.body.appendChild(input);
+ await waitUntilStableAutofocusState();
+ assert_not_equals(doc.activeElement, input);
+ w.close();
+}, 'Autofocus elements in top-level browsing context\'s documents with URL fragments should be skipped.');
+</script>
diff --git a/testing/web-platform/tests/html/interaction/focus/the-autofocus-attribute/first-reconnected.html b/testing/web-platform/tests/html/interaction/focus/the-autofocus-attribute/first-reconnected.html
new file mode 100644
index 0000000000..99ee9198d1
--- /dev/null
+++ b/testing/web-platform/tests/html/interaction/focus/the-autofocus-attribute/first-reconnected.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/utils.js"></script>
+
+<input autofocus id="i1">
+<input autofocus id="i2">
+<script>
+"use strict";
+
+promise_test(async () => {
+ const input1 = document.querySelector("#i1");
+ const input2 = document.querySelector("#i2");
+ input1.remove();
+ input2.parentNode.insertBefore(input1, input2);
+
+ await waitUntilStableAutofocusState();
+ assert_equals(document.activeElement, input2);
+}, 'The second autofocus element wins if the first autofocus element was ' +
+ 'disconnected and reconnected before flushing the autofocus candidates.');
+</script>
diff --git a/testing/web-platform/tests/html/interaction/focus/the-autofocus-attribute/first-when-later-but-before.html b/testing/web-platform/tests/html/interaction/focus/the-autofocus-attribute/first-when-later-but-before.html
new file mode 100644
index 0000000000..f361463401
--- /dev/null
+++ b/testing/web-platform/tests/html/interaction/focus/the-autofocus-attribute/first-when-later-but-before.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>The temporally first autofocus in the document wins, even if an element is inserted later that is previous in the document tree</title>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#autofocusing-a-form-control:-the-autofocus-attribute">
+<link rel="author" title="Domenic Denicola" href="d@domenic.me">
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/utils.js"></script>
+
+<input autofocus>
+
+<script>
+"use strict";
+
+promise_test(async () => {
+ const input1 = document.querySelector("input");
+ const input2 = document.createElement("input");
+ input2.autofocus = true;
+ document.body.prepend(input2);
+
+ await waitUntilStableAutofocusState();
+ assert_equals(document.activeElement, input1);
+ assert_not_equals(document.activeElement, input2);
+}, 'The temporally first autofocus in the document wins, even if an element is inserted later that is previous in the document tree.');
+</script>
diff --git a/testing/web-platform/tests/html/interaction/focus/the-autofocus-attribute/first-when-later.html b/testing/web-platform/tests/html/interaction/focus/the-autofocus-attribute/first-when-later.html
new file mode 100644
index 0000000000..1d64b863a1
--- /dev/null
+++ b/testing/web-platform/tests/html/interaction/focus/the-autofocus-attribute/first-when-later.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>The first autofocus in the document wins, even if elements are inserted later</title>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#autofocusing-a-form-control:-the-autofocus-attribute">
+<link rel="author" title="Domenic Denicola" href="d@domenic.me">
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/utils.js"></script>
+
+<input autofocus>
+
+<script>
+"use strict";
+
+promise_test(async () => {
+ const input1 = document.querySelector("input");
+ const input2 = document.createElement("input");
+ input2.autofocus = true;
+ document.body.appendChild(input2);
+
+ await waitUntilStableAutofocusState();
+ assert_equals(document.activeElement, input1);
+ assert_not_equals(document.activeElement, input2);
+}, 'The first autofocus in the document wins, even if elements are inserted later.');
+</script>
diff --git a/testing/web-platform/tests/html/interaction/focus/the-autofocus-attribute/first.html b/testing/web-platform/tests/html/interaction/focus/the-autofocus-attribute/first.html
new file mode 100644
index 0000000000..02ebb79a3e
--- /dev/null
+++ b/testing/web-platform/tests/html/interaction/focus/the-autofocus-attribute/first.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>The first autofocus in the document wins</title>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#autofocusing-a-form-control:-the-autofocus-attribute">
+<link rel="author" title="Domenic Denicola" href="d@domenic.me">
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/utils.js"></script>
+
+<input autofocus>
+<input autofocus>
+
+<script>
+"use strict";
+
+promise_test(async () => {
+ const [input1, input2] = document.querySelectorAll("input");
+
+ await waitUntilStableAutofocusState();
+ assert_equals(document.activeElement, input1);
+ assert_not_equals(document.activeElement, input2);
+}, 'The first autofocus element in the document should win.');
+</script>
diff --git a/testing/web-platform/tests/html/interaction/focus/the-autofocus-attribute/focusable-area-in-top-document.html b/testing/web-platform/tests/html/interaction/focus/the-autofocus-attribute/focusable-area-in-top-document.html
new file mode 100644
index 0000000000..327040eeee
--- /dev/null
+++ b/testing/web-platform/tests/html/interaction/focus/the-autofocus-attribute/focusable-area-in-top-document.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/utils.js"></script>
+
+<iframe srcdoc="<input><script>document.querySelector('input').focus();</script>"></iframe>
+
+<script>
+'use strict';
+
+promise_test(async () => {
+ await waitForLoad(window);
+ let iframe = document.querySelector('iframe');
+ assert_equals(document.activeElement, iframe, 'Prereq: IFRAME should be focused');
+
+ let input = document.createElement('input');
+ input.autofocus = true;
+ document.body.appendChild(input);
+
+ await waitUntilStableAutofocusState();
+ assert_equals(document.activeElement, iframe, 'activeElement should not be changed');
+ assert_not_equals(document.activeElement, input);
+}, 'If topDocument\'s focused area is not topDocument, autofocus is not processed.');
+</script>
diff --git a/testing/web-platform/tests/html/interaction/focus/the-autofocus-attribute/no-autofocus-on-changing-input-type.html b/testing/web-platform/tests/html/interaction/focus/the-autofocus-attribute/no-autofocus-on-changing-input-type.html
new file mode 100644
index 0000000000..79a39ef2f9
--- /dev/null
+++ b/testing/web-platform/tests/html/interaction/focus/the-autofocus-attribute/no-autofocus-on-changing-input-type.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/utils.js"></script>
+<body>
+
+<input id="input1" autofocus>
+<select><option>o1</option></select>
+
+<script>
+"use strict";
+
+// WebKit had a bug that reattaching RenderObject triggered autofocus again.
+// https://bugs.webkit.org/show_bug.cgi?id=68513
+promise_test(async () => {
+ const input1 = document.querySelector('input');
+ const select = document.querySelector('select');
+
+ await waitUntilStableAutofocusState();
+ assert_equals(document.activeElement, input1);
+ input1.onblur = () => { input1.type = 'password'; };
+ select.focus();
+ await waitUntilStableAutofocusState();
+ assert_equals(document.activeElement, select);
+}, 'Changing input type should not refocus on the element.');
+</script>
+</body>
diff --git a/testing/web-platform/tests/html/interaction/focus/the-autofocus-attribute/no-cross-origin-autofocus.html b/testing/web-platform/tests/html/interaction/focus/the-autofocus-attribute/no-cross-origin-autofocus.html
new file mode 100644
index 0000000000..2cf7428f36
--- /dev/null
+++ b/testing/web-platform/tests/html/interaction/focus/the-autofocus-attribute/no-cross-origin-autofocus.html
@@ -0,0 +1,44 @@
+<!DOCTYPE html>
+<html>
+<head>
+<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="/common/get-host-info.sub.js"></script>
+</head>
+<body>
+ <h1>Autofocus shouldn't work in cross-origin iframe.</h1>
+ <iframe id="child" width="200" height="100"></iframe>
+
+ <script>
+ let parent_loaded = false;
+ let child_loaded = false;
+
+ async_test(function(t) {
+ function pingChildIfBothFramesLoaded() {
+ if (parent_loaded && child_loaded)
+ frames[0].postMessage("report_focus_state", "*");
+ }
+
+ window.addEventListener("load", t.step_func(event => {
+ parent_loaded = true;
+ pingChildIfBothFramesLoaded();
+ }));
+
+ window.addEventListener("message", t.step_func(event => {
+ if (event.data == "child_loaded") {
+ child_loaded = true;
+ pingChildIfBothFramesLoaded();
+ } else if (event.data == "child_is_focused") {
+ assert_unreached("The iframe shouldn't get focus");
+ } else if (event.data == "child_is_not_focused") {
+ t.done();
+ }
+ }));
+ document.getElementById("child").src =
+ get_host_info().HTTP_REMOTE_ORIGIN + "/html/interaction/focus/the-autofocus-attribute/resources/child-autofocus.html";
+ }, "Autofocus shouldn't work in cross-origin iframe");
+ </script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/interaction/focus/the-autofocus-attribute/no-sandboxed-automatic-features.html b/testing/web-platform/tests/html/interaction/focus/the-autofocus-attribute/no-sandboxed-automatic-features.html
new file mode 100644
index 0000000000..991373d336
--- /dev/null
+++ b/testing/web-platform/tests/html/interaction/focus/the-autofocus-attribute/no-sandboxed-automatic-features.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/utils.js"></script>
+
+<iframe sandbox srcdoc="<input autofocus>"></iframe>
+
+<script>
+'use strict';
+
+promise_test(async () => {
+ await waitForLoad(window);
+ await waitUntilStableAutofocusState();
+ assert_not_equals(document.activeElement, document.querySelector('iframe'));
+}, 'If the sandboxed automatic features browsing context flag is set, ' +
+ 'autofocus in the browsing context should not be handled.');
+</script>
diff --git a/testing/web-platform/tests/html/interaction/focus/the-autofocus-attribute/not-on-first-task.html b/testing/web-platform/tests/html/interaction/focus/the-autofocus-attribute/not-on-first-task.html
new file mode 100644
index 0000000000..ab27342f4f
--- /dev/null
+++ b/testing/web-platform/tests/html/interaction/focus/the-autofocus-attribute/not-on-first-task.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>The element is not focused during the initial parsing task</title>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#autofocusing-a-form-control:-the-autofocus-attribute">
+<link rel="author" title="Domenic Denicola" href="d@domenic.me">
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<input autofocus>
+<input autofocus>
+
+<script>
+"use strict";
+
+test(() => {
+ const input = document.querySelector("input");
+
+ assert_equals(document.activeElement, document.body);
+ assert_not_equals(document.activeElement, input);
+});
+</script>
diff --git a/testing/web-platform/tests/html/interaction/focus/the-autofocus-attribute/queue-non-focusable.html b/testing/web-platform/tests/html/interaction/focus/the-autofocus-attribute/queue-non-focusable.html
new file mode 100644
index 0000000000..e3b556035d
--- /dev/null
+++ b/testing/web-platform/tests/html/interaction/focus/the-autofocus-attribute/queue-non-focusable.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/utils.js"></script>
+
+<textarea autofocus disabled></textarea>
+<select autofocus></select>
+
+<script>
+'use strict';
+
+promise_test(async () => {
+ const [textarea, select] = document.querySelectorAll('[autofocus]');
+ textarea.disabled = false;
+
+ await waitUntilStableAutofocusState();
+ assert_equals(document.activeElement, textarea);
+ assert_not_equals(document.activeElement, select);
+}, 'If the first autofocus element is not focusable, but becomes focusable before a frame, it should be focused.');
+</script>
diff --git a/testing/web-platform/tests/html/interaction/focus/the-autofocus-attribute/resources/child-autofocus.html b/testing/web-platform/tests/html/interaction/focus/the-autofocus-attribute/resources/child-autofocus.html
new file mode 100644
index 0000000000..afd5601a52
--- /dev/null
+++ b/testing/web-platform/tests/html/interaction/focus/the-autofocus-attribute/resources/child-autofocus.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<input id="target" value="This should be unfocused!" autofocus></input>
+
+<script>
+ let got_focus = false;
+ document.getElementById("target").addEventListener("focus", () => {
+ got_focus = true;
+ });
+
+ window.addEventListener("load", () => {
+ parent.postMessage("child_loaded", "*");
+ });
+
+ window.addEventListener("message", event => {
+ if (event.data == "report_focus_state") {
+ let msg = got_focus ? "child_is_focused" : "child_is_not_focused";
+ parent.postMessage(msg, "*");
+ }
+ });
+</script>
diff --git a/testing/web-platform/tests/html/interaction/focus/the-autofocus-attribute/resources/child-iframe.html b/testing/web-platform/tests/html/interaction/focus/the-autofocus-attribute/resources/child-iframe.html
new file mode 100644
index 0000000000..f60acfc871
--- /dev/null
+++ b/testing/web-platform/tests/html/interaction/focus/the-autofocus-attribute/resources/child-iframe.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<script src="/common/get-host-info.sub.js"></script>
+<iframe id="iframe" width="200" height="100"></iframe>
+
+<script>
+ iframe.src =
+ get_host_info().ORIGIN + "/html/interaction/focus/the-autofocus-attribute/resources/grand-child-autofocus.html";
+ window.addEventListener("message", event => {
+ if (event.data == "grand_child_loaded") {
+ parent.postMessage("ready", "*");
+ } else if (event.data == "report_focus_state") {
+ frames[0].postMessage("report_focus_state", "*");
+ } else if (event.data == "grand_child_is_focused" ||
+ event.data == "grand_child_is_not_focused") {
+ parent.postMessage(event.data, "*");
+ }
+ });
+</script>
diff --git a/testing/web-platform/tests/html/interaction/focus/the-autofocus-attribute/resources/erase-first.css b/testing/web-platform/tests/html/interaction/focus/the-autofocus-attribute/resources/erase-first.css
new file mode 100644
index 0000000000..bbbcf79939
--- /dev/null
+++ b/testing/web-platform/tests/html/interaction/focus/the-autofocus-attribute/resources/erase-first.css
@@ -0,0 +1,3 @@
+#first {
+ display: none;
+}
diff --git a/testing/web-platform/tests/html/interaction/focus/the-autofocus-attribute/resources/frame-with-a.html b/testing/web-platform/tests/html/interaction/focus/the-autofocus-attribute/resources/frame-with-a.html
new file mode 100644
index 0000000000..1f31736902
--- /dev/null
+++ b/testing/web-platform/tests/html/interaction/focus/the-autofocus-attribute/resources/frame-with-a.html
@@ -0,0 +1,4 @@
+<!DOCTYPE html>
+<body>
+<a name="anchor1"></a>
+</body>
diff --git a/testing/web-platform/tests/html/interaction/focus/the-autofocus-attribute/resources/frame-with-anchor.html b/testing/web-platform/tests/html/interaction/focus/the-autofocus-attribute/resources/frame-with-anchor.html
new file mode 100644
index 0000000000..b9070159e7
--- /dev/null
+++ b/testing/web-platform/tests/html/interaction/focus/the-autofocus-attribute/resources/frame-with-anchor.html
@@ -0,0 +1,4 @@
+<!DOCTYPE html>
+<body>
+<div id="anchor1"></div>
+</body>
diff --git a/testing/web-platform/tests/html/interaction/focus/the-autofocus-attribute/resources/frame-with-autofocus-element.html b/testing/web-platform/tests/html/interaction/focus/the-autofocus-attribute/resources/frame-with-autofocus-element.html
new file mode 100644
index 0000000000..985cba4149
--- /dev/null
+++ b/testing/web-platform/tests/html/interaction/focus/the-autofocus-attribute/resources/frame-with-autofocus-element.html
@@ -0,0 +1,5 @@
+<!DOCTYPE html>
+<body>
+<div id="anchor1"></div>
+<input autofocus>
+</body>
diff --git a/testing/web-platform/tests/html/interaction/focus/the-autofocus-attribute/resources/grand-child-autofocus.html b/testing/web-platform/tests/html/interaction/focus/the-autofocus-attribute/resources/grand-child-autofocus.html
new file mode 100644
index 0000000000..88be6e0b04
--- /dev/null
+++ b/testing/web-platform/tests/html/interaction/focus/the-autofocus-attribute/resources/grand-child-autofocus.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<input id="target" value="This should be focused!" autofocus></input>
+
+<script>
+ let got_focus = false;
+ target.addEventListener("focus", () => got_focus = true);
+
+ window.addEventListener("load", () => {
+ parent.postMessage("grand_child_loaded", "*");
+ });
+
+ window.addEventListener("message", event => {
+ if (event.data == "report_focus_state") {
+ let msg = got_focus ? "grand_child_is_focused" : "grand_child_is_not_focused";
+ parent.postMessage(msg, "*");
+ }
+ });
+</script>
diff --git a/testing/web-platform/tests/html/interaction/focus/the-autofocus-attribute/resources/imagemap.html b/testing/web-platform/tests/html/interaction/focus/the-autofocus-attribute/resources/imagemap.html
new file mode 100644
index 0000000000..fa3d95c81b
--- /dev/null
+++ b/testing/web-platform/tests/html/interaction/focus/the-autofocus-attribute/resources/imagemap.html
@@ -0,0 +1,5 @@
+<!DOCTYPE html>
+<body>
+<img src="/media/poster.png" usemap="#map">
+<map name="map"></map>
+</body>
diff --git a/testing/web-platform/tests/html/interaction/focus/the-autofocus-attribute/resources/moving-autofocus-to-parent.html b/testing/web-platform/tests/html/interaction/focus/the-autofocus-attribute/resources/moving-autofocus-to-parent.html
new file mode 100644
index 0000000000..fc6c298a46
--- /dev/null
+++ b/testing/web-platform/tests/html/interaction/focus/the-autofocus-attribute/resources/moving-autofocus-to-parent.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<body>
+<script>
+const input = document.createElement('input');
+input.autofocus = true;
+document.body.appendChild(input);
+input.autofocus = false;
+window.opener.document.body.appendChild(input);
+</script>
+</body>
diff --git a/testing/web-platform/tests/html/interaction/focus/the-autofocus-attribute/resources/utils.js b/testing/web-platform/tests/html/interaction/focus/the-autofocus-attribute/resources/utils.js
new file mode 100644
index 0000000000..e928e27540
--- /dev/null
+++ b/testing/web-platform/tests/html/interaction/focus/the-autofocus-attribute/resources/utils.js
@@ -0,0 +1,51 @@
+'use strict';
+
+function waitForEvent(target, type, options) {
+ return new Promise((resolve, reject) => {
+ target.addEventListener(type, resolve, options);
+ });
+}
+
+function waitForAnimationFrame(w) {
+ let targetWindow = w || window;
+ return new Promise((resolve, reject) => {
+ targetWindow.requestAnimationFrame(resolve);
+ });
+}
+
+function waitForEvent(target, type, options) {
+ return new Promise((resolve, reject) => {
+ target.addEventListener(type, resolve, options);
+ });
+}
+
+function waitForLoad(target) {
+ return waitForEvent(target, 'load');
+}
+
+function timeOut(test, ms) {
+ return new Promise((resolve, reject) => {
+ test.step_timeout(resolve, ms);
+ });
+}
+
+// If an element with autofocus is connected to a document and this function
+// is called, the autofocus result is deterministic after returning from the
+// function.
+// Exception: If the document has script-blocking style sheets, this function
+// doesn't work well.
+async function waitUntilStableAutofocusState(w) {
+ let targetWindow = w || window;
+ // Awaiting one animation frame is an easy way to determine autofocus state.
+ await waitForAnimationFrame(targetWindow);
+}
+
+async function waitForIframeLoad(src, w = window) {
+ const iframe = w.document.createElement("iframe");
+ let loadPromise = new Promise(resolve => {
+ iframe.addEventListener("load", () => resolve(iframe));
+ });
+ iframe.src = src;
+ w.document.body.appendChild(iframe);
+ return loadPromise;
+}
diff --git a/testing/web-platform/tests/html/interaction/focus/the-autofocus-attribute/same-origin-autofocus.html b/testing/web-platform/tests/html/interaction/focus/the-autofocus-attribute/same-origin-autofocus.html
new file mode 100644
index 0000000000..1497a7d658
--- /dev/null
+++ b/testing/web-platform/tests/html/interaction/focus/the-autofocus-attribute/same-origin-autofocus.html
@@ -0,0 +1,48 @@
+<!doctype html>
+<html>
+<head>
+<meta charset=utf-8>
+<meta name="assert" content="`autofocus` should not work in the same origin iframe if there is a cross-origin iframe between the parent and the same origin iframe">
+<title>autofocus in the same origin grand child iframe</title>
+<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="/common/get-host-info.sub.js"></script>
+<script src="resources/utils.js"></script>
+</head>
+<body>
+ <iframe id="child" width="200" height="100"></iframe>
+ <script>
+ let parent_loaded = false;
+ let grand_child_loaded = false;
+
+ async_test(function(t) {
+ async function pingChildIfBothFramesLoaded() {
+ if (parent_loaded && grand_child_loaded) {
+ await waitUntilStableAutofocusState();
+ frames[0].postMessage("report_focus_state", "*");
+ }
+ }
+
+ window.addEventListener("load", t.step_func(event => {
+ parent_loaded = true;
+ pingChildIfBothFramesLoaded();
+ }));
+
+ window.addEventListener("message", t.step_func(event => {
+ if (event.data == "ready") {
+ grand_child_loaded = true;
+ pingChildIfBothFramesLoaded();
+ } else if (event.data == "grand_child_is_focused") {
+ assert_unreached("The grandchild iframe shouldn't get focus");
+ } else if (event.data == "grand_child_is_not_focused") {
+ t.done();
+ }
+ }));
+ document.getElementById("child").src =
+ get_host_info().HTTP_NOTSAMESITE_ORIGIN + "/html/interaction/focus/the-autofocus-attribute/resources/child-iframe.html";
+ }, "Autofocus should not work in the same origin grand child iframe");
+ </script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/interaction/focus/the-autofocus-attribute/skip-another-top-level-browsing-context.html b/testing/web-platform/tests/html/interaction/focus/the-autofocus-attribute/skip-another-top-level-browsing-context.html
new file mode 100644
index 0000000000..d392b903f0
--- /dev/null
+++ b/testing/web-platform/tests/html/interaction/focus/the-autofocus-attribute/skip-another-top-level-browsing-context.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/utils.js"></script>
+<script>
+'use strict';
+
+promise_test(async () => {
+ let w = window.open('resources/moving-autofocus-to-parent.html');
+ await waitForLoad(w);
+ await waitUntilStableAutofocusState(w);
+ assert_equals(w.document.activeElement, w.document.body);
+ assert_equals(document.activeElement, document.body);
+ w.close();
+}, 'Autofocus elements queued in another top-level browsing context\'s ' +
+ 'documents should be skipped.');
+</script>
diff --git a/testing/web-platform/tests/html/interaction/focus/the-autofocus-attribute/skip-non-focusable.html b/testing/web-platform/tests/html/interaction/focus/the-autofocus-attribute/skip-non-focusable.html
new file mode 100644
index 0000000000..008371d8e1
--- /dev/null
+++ b/testing/web-platform/tests/html/interaction/focus/the-autofocus-attribute/skip-non-focusable.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/utils.js"></script>
+
+<textarea autofocus disabled></textarea>
+<select autofocus></select>
+
+<script>
+'use strict';
+
+promise_test(async () => {
+ const [textarea, select] = document.querySelectorAll('[autofocus]');
+
+ await waitUntilStableAutofocusState();
+ assert_not_equals(document.activeElement, textarea);
+ assert_equals(document.activeElement, select);
+}, 'Non-focusable autofocus element is skipped.');
+</script>
diff --git a/testing/web-platform/tests/html/interaction/focus/the-autofocus-attribute/skip-not-fully-active.html b/testing/web-platform/tests/html/interaction/focus/the-autofocus-attribute/skip-not-fully-active.html
new file mode 100644
index 0000000000..fa5b608d05
--- /dev/null
+++ b/testing/web-platform/tests/html/interaction/focus/the-autofocus-attribute/skip-not-fully-active.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/utils.js"></script>
+
+<iframe srcdoc="<input autofocus><script>window.frameElement.remove();</script>"></iframe>
+
+<script>
+'use strict';
+
+promise_test(async () => {
+ let iframe = document.querySelector('iframe');
+ let iframeDocument = iframe.contentDocument;
+ await waitForLoad(window);
+ assert_not_equals(document.activeElement, iframe);
+ assert_equals(iframeDocument.activeElement, iframeDocument.body);
+}, 'Autofocus element in not-fully-active document should be skipped while flusing.');
+</script>
diff --git a/testing/web-platform/tests/html/interaction/focus/the-autofocus-attribute/spin-by-blocking-style-sheet.html b/testing/web-platform/tests/html/interaction/focus/the-autofocus-attribute/spin-by-blocking-style-sheet.html
new file mode 100644
index 0000000000..fb87eca77a
--- /dev/null
+++ b/testing/web-platform/tests/html/interaction/focus/the-autofocus-attribute/spin-by-blocking-style-sheet.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/utils.js"></script>
+
+<input id="first" autofocus>
+<input id="second" autofocus>
+
+<link rel="stylesheet" href="resources/erase-first.css?pipe=trickle(d1)">
+
+<script>
+'use strict';
+
+promise_test(async () => {
+ await waitForLoad(window);
+ await waitForAnimationFrame();
+ assert_equals(document.activeElement.id, 'second');
+}, 'Script-blocking style sheet should pause flushing autofocus candidates.');
+</script>
diff --git a/testing/web-platform/tests/html/interaction/focus/the-autofocus-attribute/supported-elements.html b/testing/web-platform/tests/html/interaction/focus/the-autofocus-attribute/supported-elements.html
new file mode 100644
index 0000000000..29b3f3fb7b
--- /dev/null
+++ b/testing/web-platform/tests/html/interaction/focus/the-autofocus-attribute/supported-elements.html
@@ -0,0 +1,83 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/utils.js"></script>
+<script>
+"use strict";
+
+promise_test(async t => {
+ let w = window.open('/common/blank.html');
+ await waitForLoad(w);
+ t.add_cleanup(() => { w.close(); });
+ w.document.body.innerHTML = '<div contenteditable=true autofocus></div>';
+ await waitUntilStableAutofocusState(w);
+ assert_equals(w.document.activeElement.tagName, 'DIV');
+}, 'Contenteditable element should support autofocus');
+
+promise_test(async t => {
+ let w = window.open('/common/blank.html');
+ await waitForLoad(w);
+ t.add_cleanup(() => { w.close(); });
+ w.document.body.innerHTML = '<span tabindex=0 autofocus></span>';
+ await waitUntilStableAutofocusState(w);
+ assert_equals(w.document.activeElement.tagName, 'SPAN');
+}, 'Element with tabindex should support autofocus');
+
+promise_test(async t => {
+ let w = window.open('/common/blank.html');
+ await waitForLoad(w);
+ t.add_cleanup(() => { w.close(); });
+ let element = w.document.createElementNS('uri1', 'prefix:local');
+ element.setAttribute('autofocus', '');
+ w.document.body.appendChild(element);
+ await waitUntilStableAutofocusState(w);
+ assert_equals(w.document.activeElement.tagName, 'BODY');
+}, 'Non-HTMLElement should not support autofocus');
+
+promise_test(async t => {
+ let w = window.open('/common/blank.html');
+ await waitForLoad(w);
+ t.add_cleanup(() => { w.close(); });
+ const host = w.document.createElement('div');
+ host.autofocus = true;
+ const shadow = host.attachShadow({mode:'closed', delegatesFocus:true});
+ shadow.appendChild(w.document.createElement('input'));
+ w.document.body.appendChild(host);
+ await waitUntilStableAutofocusState(w);
+ assert_equals(w.document.activeElement, host);
+ assert_equals(shadow.activeElement.tagName, 'INPUT');
+}, 'Host element with delegatesFocus should support autofocus');
+
+promise_test(async t => {
+ let w = window.open('/common/blank.html');
+ await waitForLoad(w);
+ t.add_cleanup(() => { w.close(); });
+ const host = w.document.createElement('div');
+ host.autofocus = true;
+ host.attachShadow({mode:'closed', delegatesFocus:true});
+ w.document.body.appendChild(host);
+ const next = w.document.createElement('input');
+ next.autofocus = true;
+ w.document.body.appendChild(next);
+ await waitUntilStableAutofocusState(w);
+ assert_equals(w.document.activeElement, next);
+}, 'Host element with delegatesFocus including no focusable descendants should be skipped');
+
+promise_test(async t => {
+ let w = window.open('./resources/imagemap.html');
+ await waitForLoad(w);
+ t.add_cleanup(() => { w.close(); });
+ const area = w.document.createElement('area');
+ area.autofocus = true;
+ area.shape = 'rect';
+ area.coords = '1,1,99,99';
+ area.href = '/common/blank.html';
+ w.document.querySelector('map').appendChild(area);
+ await waitUntilStableAutofocusState(w);
+ // According to the specification, DOM anchor for an AREA shape is an IMG
+ // element, but major browsers don't follow it.
+ // See https://github.com/whatwg/html/issues/5054
+ assert_equals(w.document.activeElement, area);
+}, 'Area element should support autofocus');
+</script>
diff --git a/testing/web-platform/tests/html/interaction/focus/the-autofocus-attribute/update-the-rendering.html b/testing/web-platform/tests/html/interaction/focus/the-autofocus-attribute/update-the-rendering.html
new file mode 100644
index 0000000000..279f70d490
--- /dev/null
+++ b/testing/web-platform/tests/html/interaction/focus/the-autofocus-attribute/update-the-rendering.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<link rel="help" href="https://html.spec.whatwg.org/C/#update-the-rendering">
+
+<body>
+<script>
+'use strict';
+
+async_test(t => {
+ t.events = [];
+
+ let w = window.open('/common/blank.html', 'name',
+ 'width=100,height=100,menubar=no,toolbar=no,location=no');
+ t.add_cleanup(() => { w.close(); });
+ w.addEventListener('load', t.step_func(() => {
+ w.focus();
+ let element = w.document.createElement('input');
+ element.autofocus = true;
+ element.style.marginTop = '200px'; // Setting focus causes scrolling.
+ element.addEventListener('focus', t.step_func(() => {
+ t.events.push('autofocus');
+ }));
+
+ w.addEventListener('scroll', t.step_func(() => {
+ t.events.push('scroll');
+ }));
+
+ w.requestAnimationFrame(
+ () => w.requestAnimationFrame(t.step_func_done(() => {
+ t.events.push('animationFrame');
+ assert_array_equals(t.events, ['autofocus', 'scroll', 'animationFrame'], t.events);
+ })));
+
+ w.document.body.appendChild(element);
+ }));
+}, '"Flush autofocus candidates" should be happen before a scroll event and ' +
+ 'animation frame callbacks');
+</script>
+</body>