Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

HTML: Sequential focus navigation with shadow dom #17523

Merged
merged 4 commits into from Aug 15, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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);
}
}