Skip to content

Commit

Permalink
HTML: Sequential focus navigation with shadow dom (#17523)
Browse files Browse the repository at this point in the history
* HTML: Sequential focus navigation with shadow dom

The delegatesFocus test will be in the delegatesFocus PR (#18035)
  • Loading branch information
rakina authored and rniwa committed Aug 15, 2019
1 parent a6c9c69 commit 888da5c
Show file tree
Hide file tree
Showing 8 changed files with 330 additions and 0 deletions.
34 changes: 34 additions & 0 deletions shadow-dom/focus/focus-tabindex-order-shadow-negative.html
@@ -0,0 +1,34 @@
<!DOCTYPE html>
<meta charset="utf-8">
<title>HTML Test: focus - the sequential focus navigation order with shadow dom and negative tabindex in shadow scope</title>
<link rel="help" href="https://html.spec.whatwg.org/multipage/interaction.html#sequential-focus-navigation">
<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/shadow-utils.js"></script>
<body>
<script>
// Structure:
// <div #aboveHost tabindex=0>
// <div #host tabindex=0>
// #shadowRoot
// <div #aboveSlot tabindex=-1>
// <slot #slotAbove tabindex=-1>
// (slotted) <div #slottedAbove tabindex=-1>
// <slot #slotBelow tabindex=-1>
// (slotted) <div #slottedBelow tabindex=-1>
// <div #belowSlot tabindex=-1>
// <div #belowHost tabindex=0>

promise_test(() => {
let elementsInFlatTreeOrder;
let [aboveHost, host, aboveSlot, slotAbove, slottedAbove, slotBelow, slottedBelow, belowSlot, belowHost] =
elementsInFlatTreeOrder = prepareDOM(document.body, false);
setTabIndex(elementsInFlatTreeOrder, -1);
setTabIndex([aboveHost, host, belowHost], 0);
resetFocus();
return assertFocusOrder([aboveHost, host, belowHost]);
}, "Order when all elements in shadow tree has negative tabindex");
</script>
</body>
37 changes: 37 additions & 0 deletions shadow-dom/focus/focus-tabindex-order-shadow-slot-one.html
@@ -0,0 +1,37 @@
<!DOCTYPE html>
<meta charset="utf-8">
<title>HTML Test: focus - the sequential focus navigation order with shadow dom and all tabindex=0 except for one of the slot</title>
<link rel="help" href="https://html.spec.whatwg.org/multipage/interaction.html#sequential-focus-navigation">
<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/shadow-utils.js"></script>
<body>
<script>
// Structure:
// <div #aboveHost tabindex=0>
// <div #host tabindex=0>
// #shadowRoot
// <div #aboveSlot tabindex=0>
// <slot #slotAbove tabindex=0>
// (slotted) <div #slottedAbove tabindex=0>
// <slot #slotBelow tabindex=1>
// (slotted) <div #slottedBelow tabindex=0>
// <div #belowSlot tabindex=0>
// <div #belowHost tabindex=0>

promise_test(() => {
let elementsInFlatTreeOrder;
let [aboveHost, host, aboveSlot, slotAbove, slottedAbove, slotBelow, slottedBelow, belowSlot, belowHost] =
elementsInFlatTreeOrder = prepareDOM(document.body, false);
setTabIndex(elementsInFlatTreeOrder, 0);
slotBelow.tabIndex = 1;
resetFocus();
// Focus should move first according to flat tree order to #aboveHost and #host, then into #host's focus scope.
// It will then move to #slottedBelow because #slotBelow has tabindex=1 (though we actually won't focus on the slot),
// and then back to #host's focus scope again, finally getting out to the document focus scope.
return assertFocusOrder([aboveHost, host, slottedBelow, aboveSlot, slottedAbove, belowSlot, belowHost]);
}, "Order when all tabindex=0, except for one slot that has tabindex=1");
</script>
</body>
36 changes: 36 additions & 0 deletions shadow-dom/focus/focus-tabindex-order-shadow-varying-tabindex.html
@@ -0,0 +1,36 @@
<!DOCTYPE html>
<meta charset="utf-8">
<title>HTML Test: focus - the sequential focus navigation order with shadow dom with varying tabindex values</title>
<link rel="help" href="https://html.spec.whatwg.org/multipage/interaction.html#sequential-focus-navigation">
<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/shadow-utils.js"></script>
<body>
<script>
// Structure:
// <div #aboveHost tabindex=3>
// <div #host tabindex=3>
// #shadowRoot
// <div #aboveSlot tabindex=2>
// <slot #slotAbove tabindex=1>
// (slotted) <div #slottedAbove tabindex=4>
// <slot #slotBelow tabindex=1>
// (slotted) <div #slottedBelow tabindex=4>
// <div #belowSlot tabindex=2>
// <div #belowHost tabindex=3>

promise_test(() => {
let elementsInFlatTreeOrder;
let [aboveHost, host, aboveSlot, slotAbove, slottedAbove, slotBelow, slottedBelow, belowSlot, belowHost] =
elementsInFlatTreeOrder = prepareDOM(document.body, false);
setTabIndex([slotAbove, slotBelow], 1);
setTabIndex([aboveSlot, belowSlot], 2);
setTabIndex([aboveHost, host, belowHost], 3);
setTabIndex([slottedAbove, slottedBelow], 4);
resetFocus();
return assertFocusOrder([aboveHost, host, slottedAbove, slottedBelow, aboveSlot, belowSlot, belowHost]);
}, "Order with various tabindex values");
</script>
</body>
@@ -0,0 +1,35 @@
<!DOCTYPE html>
<meta charset="utf-8">
<title>HTML Test: focus - the sequential focus navigation order with shadow dom and negative host tabindex</title>
<link rel="help" href="https://html.spec.whatwg.org/multipage/interaction.html#sequential-focus-navigation">
<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/shadow-utils.js"></script>
<body>
<script>
// Structure:
// <div #aboveHost tabindex=0>
// <div #host tabindex=-1>
// #shadowRoot
// <div #aboveSlot tabindex=0>
// <slot #slotAbove tabindex=0>
// (slotted) <div #slottedAbove tabindex=0>
// <slot #slotBelow tabindex=0>
// (slotted) <div #slottedBelow tabindex=0>
// <div #belowSlot tabindex=0>
// <div #belowHost tabindex=0>

promise_test(() => {
let elementsInFlatTreeOrder;
let [aboveHost, host, aboveSlot, slotAbove, slottedAbove, slotBelow, slottedBelow, belowSlot, belowHost] =
elementsInFlatTreeOrder = prepareDOM(document.body, false);
setTabIndex(elementsInFlatTreeOrder, 0);
host.tabIndex = -1;
resetFocus();
// Focus willl only move within the focus navigation scope of the document (not going to get into #host).
return assertFocusOrder([aboveHost, belowHost]);
}, "Order when all tabindex=0 except for host, which has tabindex=-1");
</script>
</body>
@@ -0,0 +1,37 @@
<!DOCTYPE html>
<meta charset="utf-8">
<title>HTML Test: focus - the sequential focus navigation order with shadow dom and non-focusable host</title>
<link rel="help" href="https://html.spec.whatwg.org/multipage/interaction.html#sequential-focus-navigation">
<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/shadow-utils.js"></script>
<body>
<script>
// Structure:
// <div #aboveHost tabindex=0>
// <div #host>
// #shadowRoot
// <div #aboveSlot tabindex=0>
// <slot #slotAbove tabindex=0>
// (slotted) <div #slottedAbove tabindex=0>
// <slot #slotBelow tabindex=0>
// (slotted) <div #slottedBelow tabindex=0>
// <div #belowSlot tabindex=0>
// <div #belowHost tabindex=0>

promise_test(() => {
let elementsInFlatTreeOrder;
let [aboveHost, host, aboveSlot, slotAbove, slottedAbove, slotBelow, slottedBelow, belowSlot, belowHost] =
elementsInFlatTreeOrder = prepareDOM(document.body, false);
setTabIndex(elementsInFlatTreeOrder, 0);
removeTabIndex([host]);
resetFocus();
// Focus should move in flat tree order since every one of them has tabindex ==0,
// but doesn't include #slot since it's not rendered and #host since its tabindex is not set
// (but #host is considered as 0 in focus scope navigation, keeping the flat tree order for the shadow root's descendants).
return assertFocusOrder(elementsInFlatTreeOrder.filter(el => (el !== slotAbove && el !== slotBelow && el !== host)));
}, "Order when all tabindex=0 except host (tabindex not set)");
</script>
</body>
37 changes: 37 additions & 0 deletions shadow-dom/focus/focus-tabindex-order-shadow-zero-host-one.html
@@ -0,0 +1,37 @@
<!DOCTYPE html>
<meta charset="utf-8">
<title>HTML Test: focus - the sequential focus navigation order with shadow dom and all tabindex=0 except host (tabindex=1)</title>
<link rel="help" href="https://html.spec.whatwg.org/multipage/interaction.html#sequential-focus-navigation">
<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/shadow-utils.js"></script>
<body>
<script>
// Structure:
// <div #aboveHost tabindex=0>
// <div #host tabindex=1>
// #shadowRoot
// <div #aboveSlot tabindex=0>
// <slot #slotAbove tabindex=0>
// (slotted) <div #slottedAbove tabindex=0>
// <slot #slotBelow tabindex=0>
// (slotted) <div #slottedBelow tabindex=0>
// <div #belowSlot tabindex=0>
// <div #belowHost tabindex=0>

promise_test(() => {
let elementsInFlatTreeOrder;
let [aboveHost, host, aboveSlot, slotAbove, slottedAbove, slotBelow, slottedBelow, belowSlot, belowHost] =
elementsInFlatTreeOrder = prepareDOM(document.body, false);
setTabIndex(elementsInFlatTreeOrder, 0);
host.tabIndex = 1;
resetFocus();
// Focus should move first to #host because it has tabindex=1, and then to the contents of its scope
// (e.g. the contents of its shadow tree) in flat tree order, and then outside to the document scope
// again (aboveHost & belowHost).
return assertFocusOrder([host, aboveSlot, slottedAbove, slottedBelow, belowSlot, aboveHost, belowHost]);
}, "Order when all tabindex=0 except for host, which has tabindex=1");
</script>
</body>
35 changes: 35 additions & 0 deletions shadow-dom/focus/focus-tabindex-order-shadow-zero.html
@@ -0,0 +1,35 @@
<!DOCTYPE html>
<meta charset="utf-8">
<title>HTML Test: focus - the sequential focus navigation order with shadow dom and all tabindex=0</title>
<link rel="help" href="https://html.spec.whatwg.org/multipage/interaction.html#sequential-focus-navigation">
<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/shadow-utils.js"></script>
<body>
<script>
// Structure:
// <div #aboveHost tabindex=0>
// <div #host tabindex=0>
// #shadowRoot
// <div #aboveSlot tabindex=0>
// <slot #slotAbove tabindex=0>
// (slotted) <div #slottedAbove tabindex=0>
// <slot #slotBelow tabindex=0>
// (slotted) <div #slottedBelow tabindex=0>
// <div #belowSlot tabindex=0>
// <div #belowHost tabindex=0>

promise_test(() => {
let elementsInFlatTreeOrder;
let [aboveHost, host, aboveSlot, slotAbove, slottedAbove, slotBelow, slottedBelow, belowSlot, belowHost] =
elementsInFlatTreeOrder = prepareDOM(document.body, false);
setTabIndex(elementsInFlatTreeOrder, 0);
resetFocus();
// Focus should move in flat tree order since every one of them has tabindex==0,
// but doesn't include slots.
return assertFocusOrder(elementsInFlatTreeOrder.filter(el => (el !== slotAbove && el !== slotBelow)));
}, "Order when all tabindex=0 is and delegatesFocus = false");
</script>
</body>
79 changes: 79 additions & 0 deletions shadow-dom/focus/resources/shadow-utils.js
@@ -0,0 +1,79 @@
// Structure:
// <div #aboveHost>
// <div #host>
// #shadowRoot
// <div #aboveSlot>
// <slot #slotAbove>
// (slotted) <div #slottedAbove>
// <slot #slotBelow>
// (slotted) <div #slottedBelow>
// <div #belowSlot>
// <div #belowHost>
function prepareDOM(container, delegatesFocus) {

const aboveHost = document.createElement("div");
aboveHost.innerText = "aboveHost";
const host = document.createElement("div");
host.id = "host";
const slottedBelow = document.createElement("div");
slottedBelow.innerText = "slotted below";
slottedBelow.slot = "below";
const slottedAbove = document.createElement("div");
slottedAbove.innerText = "slotted above";
slottedAbove.slot = "above";

const belowHost = document.createElement("div");
belowHost.innerText = "belowHost";
container.appendChild(aboveHost);
container.appendChild(host);
container.appendChild(belowHost);
host.appendChild(slottedBelow);
host.appendChild(slottedAbove);
const shadowRoot = host.attachShadow({ mode: "open", delegatesFocus: delegatesFocus});
const aboveSlot = document.createElement("div");
aboveSlot.innerText = "aboveSlot";

const slotAbove = document.createElement("slot");
slotAbove.name = "above";
const slotBelow = document.createElement("slot");
slotBelow.name = "below";

const belowSlot = document.createElement("div");
belowSlot.innerText = "belowSlot";
shadowRoot.appendChild(aboveSlot);
shadowRoot.appendChild(slotAbove);
shadowRoot.appendChild(slotBelow);
shadowRoot.appendChild(belowSlot);

return [aboveHost, host, aboveSlot, slotAbove, slottedAbove, slotBelow, slottedBelow, belowSlot, belowHost];
}

function setTabIndex(elements, value) {
for (const el of elements) {
el.tabIndex = value;
}
}

function removeTabIndex(elements) {
for (const el of elements) {
el.removeAttribute("tabindex");
}
}

function resetFocus() {
document.body.focus();
}

function navigateFocusForward() {
// TAB = '\ue004'
return test_driver.send_keys(document.body, "\ue004");
}

async function assertFocusOrder(expectedOrder) {
const shadowRoot = document.getElementById("host").shadowRoot;
for (const el of expectedOrder) {
await navigateFocusForward();
const focused = shadowRoot.activeElement ? shadowRoot.activeElement : document.activeElement;
assert_equals(focused, el);
}
}

0 comments on commit 888da5c

Please sign in to comment.