Diff
Modified: trunk/LayoutTests/imported/w3c/ChangeLog (251042 => 251043)
--- trunk/LayoutTests/imported/w3c/ChangeLog 2019-10-12 05:43:46 UTC (rev 251042)
+++ trunk/LayoutTests/imported/w3c/ChangeLog 2019-10-12 06:09:04 UTC (rev 251043)
@@ -1,3 +1,29 @@
+2019-10-11 Ryosuke Niwa <[email protected]>
+
+ Add the support for ShadowRoot.delegateFocus
+ https://bugs.webkit.org/show_bug.cgi?id=166484
+ <rdar://problem/29816058>
+
+ Reviewed by Antti Koivisto.
+
+ Import W3C tests from https://github.com/web-platform-tests/wpt/pull/18035/commits/a8a89f224f2170723170a452cb18b46cafb723b6.
+
+ * web-platform-tests/resources/testdriver-vendor.js:
+ * web-platform-tests/shadow-dom/focus/click-focus-delegatesFocus-click-method-expected.txt: Added.
+ * web-platform-tests/shadow-dom/focus/click-focus-delegatesFocus-click-method.html: Added.
+ * web-platform-tests/shadow-dom/focus/click-focus-delegatesFocus-tabindex-varies-expected.txt: Added.
+ * web-platform-tests/shadow-dom/focus/click-focus-delegatesFocus-tabindex-varies.html: Added.
+ * web-platform-tests/shadow-dom/focus/click-focus-delegatesFocus-tabindex-zero-expected.txt: Added.
+ * web-platform-tests/shadow-dom/focus/click-focus-delegatesFocus-tabindex-zero.html: Added.
+ * web-platform-tests/shadow-dom/focus/focus-method-delegatesFocus-expected.txt: Added.
+ * web-platform-tests/shadow-dom/focus/focus-method-delegatesFocus.html: Added.
+ * web-platform-tests/shadow-dom/focus/focus-tabindex-order-shadow-negative-delegatesFocus-expected.txt: Added.
+ * web-platform-tests/shadow-dom/focus/focus-tabindex-order-shadow-negative-delegatesFocus.html: Added.
+ * web-platform-tests/shadow-dom/focus/focus-tabindex-order-shadow-varying-delegatesFocus-expected.txt: Added.
+ * web-platform-tests/shadow-dom/focus/focus-tabindex-order-shadow-varying-delegatesFocus.html: Added.
+ * web-platform-tests/shadow-dom/focus/focus-tabindex-order-shadow-zero-delegatesFocus-expected.txt: Added.
+ * web-platform-tests/shadow-dom/focus/focus-tabindex-order-shadow-zero-delegatesFocus.html: Added.
+
2019-10-10 Carlos Alberto Lopez Perez <[email protected]>
Import css/css-images WPT tests
Modified: trunk/LayoutTests/imported/w3c/web-platform-tests/resources/testdriver-vendor.js (251042 => 251043)
--- trunk/LayoutTests/imported/w3c/web-platform-tests/resources/testdriver-vendor.js 2019-10-12 05:43:46 UTC (rev 251042)
+++ trunk/LayoutTests/imported/w3c/web-platform-tests/resources/testdriver-vendor.js 2019-10-12 06:09:04 UTC (rev 251043)
@@ -178,6 +178,27 @@
return Promise.resolve();
}
+window.test_driver_internal.click = function (element, coords)
+{
+ if (!window.eventSender)
+ return Promise.reject(new Error("window.eventSender is undefined."));
+
+ if (testRunner.isIOSFamily && testRunner.isWebKit2) {
+ return new Promise((resolve) => {
+ testRunner.runUIScript(`
+ uiController.singleTapAtPoint(${coords.x}, ${coords.y}, function() {
+ uiController.uiScriptComplete();
+ });`, resolve);
+ });
+ }
+
+ eventSender.mouseMoveTo(coords.x, coords.y);
+ eventSender.mouseDown();
+ eventSender.mouseUp();
+
+ return Promise.resolve();
+}
+
window.test_driver_internal.action_sequence = function(sources)
{
// https://w3c.github.io/webdriver/#processing-actions
Added: trunk/LayoutTests/imported/w3c/web-platform-tests/shadow-dom/focus/click-focus-delegatesFocus-click-method-expected.txt (0 => 251043)
--- trunk/LayoutTests/imported/w3c/web-platform-tests/shadow-dom/focus/click-focus-delegatesFocus-click-method-expected.txt (rev 0)
+++ trunk/LayoutTests/imported/w3c/web-platform-tests/shadow-dom/focus/click-focus-delegatesFocus-click-method-expected.txt 2019-10-12 06:09:04 UTC (rev 251043)
@@ -0,0 +1,6 @@
+slotted
+outside
+
+PASS call click() on host with delegatesFocus, all tabindex=0
+PASS call click() on slotted element in delegatesFocus shadow tree, all tabindex=0
+
Added: trunk/LayoutTests/imported/w3c/web-platform-tests/shadow-dom/focus/click-focus-delegatesFocus-click-method.html (0 => 251043)
--- trunk/LayoutTests/imported/w3c/web-platform-tests/shadow-dom/focus/click-focus-delegatesFocus-click-method.html (rev 0)
+++ trunk/LayoutTests/imported/w3c/web-platform-tests/shadow-dom/focus/click-focus-delegatesFocus-click-method.html 2019-10-12 06:09:04 UTC (rev 251043)
@@ -0,0 +1,67 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>HTML Test: click on shadow host with delegatesFocus</title>
+<script src=""
+<script src=""
+<script src=""
+<script src=""
+<script src=""
+
+<body>
+<div id="host">
+ <div id="slotted">slotted</div>
+</div>
+<div id="outside">outside</div>
+</body>
+
+<script>
+const host = document.getElementById("host");
+const slotted = document.getElementById("slotted");
+
+const shadowRoot = host.attachShadow({ mode: "open", delegatesFocus: true });
+const aboveSlot = document.createElement("div");
+aboveSlot.innerText = "aboveSlot";
+const slot = document.createElement("slot");
+shadowRoot.appendChild(aboveSlot);
+shadowRoot.appendChild(slot);
+
+const elementsInFlatTreeOrder = [host, aboveSlot, slot, slotted, outside];
+
+// Final structure:
+// <div #host> (delegatesFocus=true)
+// #shadowRoot
+// <div #aboveSlot>
+// <slot #slot>
+// (slotted) <div #slotted>
+// <div #outside>
+
+function setAllTabIndex(value) {
+ setTabIndex(elementsInFlatTreeOrder, value);
+}
+
+function removeAllTabIndex() {
+ removeTabIndex(elementsInFlatTreeOrder);
+}
+
+function resetTabIndexAndFocus() {
+ removeAllTabIndex();
+ resetFocus(document);
+ resetFocus(shadowRoot);
+}
+
+test(() => {
+ resetTabIndexAndFocus();
+ setAllTabIndex(0);
+ host.click();
+ assert_equals(shadowRoot.activeElement, null);
+ assert_equals(document.activeElement, document.body);
+}, "call click() on host with delegatesFocus, all tabindex=0");
+
+test(() => {
+ resetTabIndexAndFocus();
+ setAllTabIndex(0);
+ slotted.click();
+ assert_equals(shadowRoot.activeElement, null);
+ assert_equals(document.activeElement, document.body);
+}, "call click() on slotted element in delegatesFocus shadow tree, all tabindex=0");
+</script>
Added: trunk/LayoutTests/imported/w3c/web-platform-tests/shadow-dom/focus/click-focus-delegatesFocus-tabindex-varies-expected.txt (0 => 251043)
--- trunk/LayoutTests/imported/w3c/web-platform-tests/shadow-dom/focus/click-focus-delegatesFocus-tabindex-varies-expected.txt (rev 0)
+++ trunk/LayoutTests/imported/w3c/web-platform-tests/shadow-dom/focus/click-focus-delegatesFocus-tabindex-varies-expected.txt 2019-10-12 06:09:04 UTC (rev 251043)
@@ -0,0 +1,5 @@
+slotted
+outside
+
+PASS click on host with delegatesFocus, #aboveSlot tabindex = 2, #slot and #slotted tabindex = 1
+
Added: trunk/LayoutTests/imported/w3c/web-platform-tests/shadow-dom/focus/click-focus-delegatesFocus-tabindex-varies.html (0 => 251043)
--- trunk/LayoutTests/imported/w3c/web-platform-tests/shadow-dom/focus/click-focus-delegatesFocus-tabindex-varies.html (rev 0)
+++ trunk/LayoutTests/imported/w3c/web-platform-tests/shadow-dom/focus/click-focus-delegatesFocus-tabindex-varies.html 2019-10-12 06:09:04 UTC (rev 251043)
@@ -0,0 +1,68 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>HTML Test: click on shadow host with delegatesFocus</title>
+<script src=""
+<script src=""
+<script src=""
+<script src=""
+<script src=""
+
+<body>
+<div id="host">
+ <div id="slotted">slotted</div>
+</div>
+<div id="outside">outside</div>
+</body>
+
+<script>
+const host = document.getElementById("host");
+const slotted = document.getElementById("slotted");
+
+const shadowRoot = host.attachShadow({ mode: "open", delegatesFocus: true });
+const aboveSlot = document.createElement("div");
+aboveSlot.innerText = "aboveSlot";
+const slot = document.createElement("slot");
+// Add an unfocusable spacer, because test_driver.click will click on the
+// center point of #host, and we don't want the click to land on #aboveSlot
+// or #slot.
+const spacer = document.createElement("div");
+spacer.style = "height: 1000px;";
+shadowRoot.appendChild(spacer);
+shadowRoot.appendChild(aboveSlot);
+shadowRoot.appendChild(slot);
+
+const elementsInFlatTreeOrder = [host, aboveSlot, spacer, slot, slotted, outside];
+
+// Final structure:
+// <div #host> (delegatesFocus=true)
+// #shadowRoot
+// <div #spacer>
+// <div #aboveSlot>
+// <slot #slot>
+// (slotted) <div #slotted>
+// <div #outside>
+
+function setAllTabIndex(value) {
+ setTabIndex(elementsInFlatTreeOrder, value);
+}
+
+function removeAllTabIndex() {
+ removeTabIndex(elementsInFlatTreeOrder);
+}
+
+function resetTabIndexAndFocus() {
+ removeAllTabIndex();
+ resetFocus(document);
+ resetFocus(shadowRoot);
+}
+
+promise_test(async () => {
+ resetTabIndexAndFocus();
+ setTabIndex([aboveSlot], 2);
+ setTabIndex([slot, slotted], 1);
+ await test_driver.click(host);
+ assert_equals(shadowRoot.activeElement, aboveSlot);
+ assert_equals(document.activeElement, host);
+}, "click on host with delegatesFocus, #aboveSlot tabindex = 2, #slot and #slotted tabindex = 1");
+
+</script>
Added: trunk/LayoutTests/imported/w3c/web-platform-tests/shadow-dom/focus/click-focus-delegatesFocus-tabindex-zero-expected.txt (0 => 251043)
--- trunk/LayoutTests/imported/w3c/web-platform-tests/shadow-dom/focus/click-focus-delegatesFocus-tabindex-zero-expected.txt (rev 0)
+++ trunk/LayoutTests/imported/w3c/web-platform-tests/shadow-dom/focus/click-focus-delegatesFocus-tabindex-zero-expected.txt 2019-10-12 06:09:04 UTC (rev 251043)
@@ -0,0 +1,5 @@
+slotted
+outside
+
+PASS click on host with delegatesFocus, all tabindex=0 except spacer
+
Added: trunk/LayoutTests/imported/w3c/web-platform-tests/shadow-dom/focus/click-focus-delegatesFocus-tabindex-zero.html (0 => 251043)
--- trunk/LayoutTests/imported/w3c/web-platform-tests/shadow-dom/focus/click-focus-delegatesFocus-tabindex-zero.html (rev 0)
+++ trunk/LayoutTests/imported/w3c/web-platform-tests/shadow-dom/focus/click-focus-delegatesFocus-tabindex-zero.html 2019-10-12 06:09:04 UTC (rev 251043)
@@ -0,0 +1,68 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>HTML Test: click on shadow host with delegatesFocus</title>
+<script src=""
+<script src=""
+<script src=""
+<script src=""
+<script src=""
+
+<body>
+<div id="host">
+ <div id="slotted">slotted</div>
+</div>
+<div id="outside">outside</div>
+</body>
+
+<script>
+const host = document.getElementById("host");
+const slotted = document.getElementById("slotted");
+
+const shadowRoot = host.attachShadow({ mode: "open", delegatesFocus: true });
+const aboveSlot = document.createElement("div");
+aboveSlot.innerText = "aboveSlot";
+const slot = document.createElement("slot");
+// Add an unfocusable spacer, because test_driver.click will click on the
+// center point of #host, and we don't want the click to land on #aboveSlot
+// or #slot.
+const spacer = document.createElement("div");
+spacer.style = "height: 1000px;";
+shadowRoot.appendChild(spacer);
+shadowRoot.appendChild(aboveSlot);
+shadowRoot.appendChild(slot);
+
+const elementsInFlatTreeOrder = [host, aboveSlot, spacer, slot, slotted, outside];
+
+// Final structure:
+// <div #host> (delegatesFocus=true)
+// #shadowRoot
+// <div #spacer>
+// <div #aboveSlot>
+// <slot #slot>
+// (slotted) <div #slotted>
+// <div #outside>
+
+function setAllTabIndex(value) {
+ setTabIndex(elementsInFlatTreeOrder, value);
+}
+
+function removeAllTabIndex() {
+ removeTabIndex(elementsInFlatTreeOrder);
+}
+
+function resetTabIndexAndFocus() {
+ removeAllTabIndex();
+ resetFocus(document);
+ resetFocus(shadowRoot);
+}
+
+promise_test(async () => {
+ resetTabIndexAndFocus();
+ setAllTabIndex(0);
+ removeTabIndex([spacer]);
+ await test_driver.click(host);
+ assert_equals(shadowRoot.activeElement, aboveSlot);
+ assert_equals(document.activeElement, host);
+}, "click on host with delegatesFocus, all tabindex=0 except spacer");
+
+</script>
Added: trunk/LayoutTests/imported/w3c/web-platform-tests/shadow-dom/focus/focus-method-delegatesFocus-expected.txt (0 => 251043)
--- trunk/LayoutTests/imported/w3c/web-platform-tests/shadow-dom/focus/focus-method-delegatesFocus-expected.txt (rev 0)
+++ trunk/LayoutTests/imported/w3c/web-platform-tests/shadow-dom/focus/focus-method-delegatesFocus-expected.txt 2019-10-12 06:09:04 UTC (rev 251043)
@@ -0,0 +1,16 @@
+slottedToSecondSlot
+slottedToFirstSlot
+outside
+
+PASS focus() on host with delegatesFocus, all tabindex=0
+PASS focus() on host with delegatesFocus & tabindex =-1, all other tabindex=0
+PASS focus() on host with delegatesFocus & no tabindex, all other tabindex=0
+PASS focus() on host with delegatesFocus & tabindex = 0, all other tabindex=-1
+PASS focus() on host with delegatesFocus, all without tabindex
+PASS focus() on host with delegatesFocus, all tabindex=-1
+PASS focus() on host with delegatesFocus & tabindex=0, #belowSlots with tabindex=0
+PASS focus() on host with delegatesFocus & tabindex=0, #outside with tabindex=0
+PASS focus() on host with delegatesFocus & tabindex=0, #aboveSlots and #belowSlots with tabindex=0
+PASS focus() on host with delegatesFocus & tabindex=0, #aboveSlots with tabindex=0 and #belowSlots with tabindex=1
+PASS focus() on host with delegatesFocus & tabindex=0, #slottedToFirstSlot, #slottedToSecondSlot, #belowSlots with tabindex=0
+
Added: trunk/LayoutTests/imported/w3c/web-platform-tests/shadow-dom/focus/focus-method-delegatesFocus.html (0 => 251043)
--- trunk/LayoutTests/imported/w3c/web-platform-tests/shadow-dom/focus/focus-method-delegatesFocus.html (rev 0)
+++ trunk/LayoutTests/imported/w3c/web-platform-tests/shadow-dom/focus/focus-method-delegatesFocus.html 2019-10-12 06:09:04 UTC (rev 251043)
@@ -0,0 +1,231 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>HTML Test: focus() on shadow host with delegatesFocus</title>
+<script src=""
+<script src=""
+<script src=""
+<script src=""
+<script src=""
+
+<body>
+<div id="host">
+ <div id="slottedToSecondSlot" slot="secondSlot">slottedToSecondSlot</div>
+ <div id="slottedToFirstSlot" slot="firstSlot">slottedToFirstSlot</div>
+</div>
+<div id="outside">outside</div>
+</body>
+
+<script>
+const host = document.getElementById("host");
+const slottedToSecondSlot = document.getElementById("slottedToSecondSlot");
+const slottedToFirstSlot = document.getElementById("slottedToFirstSlot");
+const outside = document.getElementById("outside");
+
+const shadowRoot = host.attachShadow({ mode: "open", delegatesFocus: true });
+const aboveSlots = document.createElement("div");
+aboveSlots.innerText = "aboveSlots";
+const firstSlot = document.createElement("slot");
+firstSlot.name = "firstSlot";
+const secondSlot = document.createElement("slot");
+secondSlot.name = "secondSlot";
+const belowSlots = document.createElement("div");
+belowSlots.innerText = "belowSlots";
+shadowRoot.appendChild(aboveSlots);
+shadowRoot.appendChild(firstSlot);
+shadowRoot.appendChild(secondSlot);
+shadowRoot.appendChild(belowSlots);
+
+const elementsInFlatTreeOrder = [host, aboveSlots, firstSlot,
+ slottedToFirstSlot, secondSlot, slottedToSecondSlot, belowSlots, outside];
+
+// Final structure:
+// <div #host> (delegatesFocus=true)
+// #shadowRoot
+// <div #aboveSlots>
+// <slot #firstSlot>
+// (slotted) <div #slottedToFirstSlot>
+// <slot #secondSlot>
+// (slotted) <div #slottedToSecondSlot>
+// <div #belowSlots>
+// <div #outside>
+
+
+function setAllTabIndex(value) {
+ setTabIndex(elementsInFlatTreeOrder, value);
+}
+
+function removeAllTabIndex() {
+ removeTabIndex(elementsInFlatTreeOrder);
+}
+
+function resetTabIndexAndFocus() {
+ removeAllTabIndex();
+ resetFocus(document);
+ resetFocus(shadowRoot);
+}
+
+test(() => {
+ resetTabIndexAndFocus();
+ setAllTabIndex(0);
+ // Structure:
+ // <div #host> (delegatesFocus=true) tabindex=0
+ // #shadowRoot
+ // <div #aboveSlots> tabindex=0
+ // <slot #firstSlot> tabindex=0
+ // (slotted) <div #slottedToFirstSlot> tabindex=0
+ // <slot #secondSlot> tabindex=0
+ // (slotted) <div #slottedToSecondSlot> tabindex=0
+ // <div #belowSlots> tabindex=0
+ // <div #outside> tabindex=0
+ // First focusable = #aboveSlots
+ host.focus();
+ assert_equals(shadowRoot.activeElement, aboveSlots);
+ assert_equals(document.activeElement, host);
+}, "focus() on host with delegatesFocus, all tabindex=0");
+
+test(() => {
+ resetTabIndexAndFocus();
+ setAllTabIndex(0);
+ setTabIndex([host], -1);
+ // First focusable = #aboveSlots
+ host.focus();
+ assert_equals(shadowRoot.activeElement, aboveSlots);
+ assert_equals(document.activeElement, host);
+}, "focus() on host with delegatesFocus & tabindex =-1, all other tabindex=0");
+
+test(() => {
+ resetTabIndexAndFocus();
+ setTabIndex([aboveSlots, slottedToFirstSlot, slottedToSecondSlot, belowSlots], 0);
+ // First focusable = #aboveSlots
+ host.focus();
+ assert_equals(shadowRoot.activeElement, aboveSlots);
+ assert_equals(document.activeElement, host);
+}, "focus() on host with delegatesFocus & no tabindex, all other tabindex=0");
+
+test(() => {
+ resetTabIndexAndFocus();
+ setAllTabIndex(-1);
+ setTabIndex([host], 0);
+ // First focusable = #aboveSlots
+ host.focus();
+ assert_equals(shadowRoot.activeElement, aboveSlots);
+ assert_equals(document.activeElement, host);
+}, "focus() on host with delegatesFocus & tabindex = 0, all other tabindex=-1");
+
+test(() => {
+ resetTabIndexAndFocus();
+ removeAllTabIndex();
+ // No focusable element under #host in the flat tree.
+ host.focus();
+ assert_equals(shadowRoot.activeElement, null);
+ assert_equals(document.activeElement, document.body);
+}, "focus() on host with delegatesFocus, all without tabindex");
+
+test(() => {
+ resetTabIndexAndFocus();
+ // First focusable = #aboveSlots
+ setAllTabIndex(-1);
+ host.focus();
+ assert_equals(shadowRoot.activeElement, aboveSlots);
+ assert_equals(document.activeElement, host);
+}, "focus() on host with delegatesFocus, all tabindex=-1");
+
+test(() => {
+ resetTabIndexAndFocus();
+ removeAllTabIndex();
+ setTabIndex([host, belowSlots], 0);
+ // Structure:
+ // <div #host> (delegatesFocus=true) tabindex=0
+ // #shadowRoot
+ // <div #aboveSlots>
+ // <slot #firstSlot>
+ // (slotted) <div #slottedToFirstSlot>
+ // <slot #secondSlot>
+ // (slotted) <div #slottedToSecondSlot>
+ // <div #belowSlots> tabindex=0
+ // <div #outside>
+ // First focusable = #belowSlots
+ host.focus();
+ assert_equals(shadowRoot.activeElement, belowSlots);
+ assert_equals(document.activeElement, host);
+}, "focus() on host with delegatesFocus & tabindex=0, #belowSlots with tabindex=0");
+
+test(() => {
+ resetTabIndexAndFocus();
+ removeAllTabIndex();
+ setTabIndex([host, outside], 0);
+ // Structure:
+ // <div #host> (delegatesFocus=true) tabindex=0
+ // #shadowRoot
+ // <div #aboveSlots>
+ // <slot #firstSlot>
+ // (slotted) <div #slottedToFirstSlot>
+ // <slot #secondSlot>
+ // (slotted) <div #slottedToSecondSlot>
+ // <div #belowSlots>
+ // <div #outside> tabindex=0
+ // No focusable element under #host in the flat tree.
+ host.focus();
+ assert_equals(shadowRoot.activeElement, null);
+ assert_equals(document.activeElement, document.body);
+}, "focus() on host with delegatesFocus & tabindex=0, #outside with tabindex=0");
+
+test(() => {
+ resetTabIndexAndFocus();
+ setTabIndex([host, aboveSlots, belowSlots], 0);
+ // Structure:
+ // <div #host> (delegatesFocus=true) tabindex=0
+ // #shadowRoot
+ // <div #aboveSlots> tabindex=0
+ // <slot #firstSlot>
+ // (slotted) <div #slottedToFirstSlot>
+ // <slot #secondSlot>
+ // (slotted) <div #slottedToSecondSlot>
+ // <div #belowSlots> tabindex=0
+ // <div #outside>
+ // First focusable = #aboveSlots
+ host.focus();
+ assert_equals(shadowRoot.activeElement, aboveSlots);
+ assert_equals(document.activeElement, host);
+}, "focus() on host with delegatesFocus & tabindex=0, #aboveSlots and #belowSlots with tabindex=0");
+
+test(() => {
+ resetTabIndexAndFocus();
+ setTabIndex([host, aboveSlots], 0);
+ setTabIndex([belowSlots], 1);
+ // Structure:
+ // <div #host> (delegatesFocus=true) tabindex=0
+ // #shadowRoot
+ // <div #aboveSlots> tabindex=0
+ // <slot #firstSlot>
+ // (slotted) <div #slottedToFirstSlot>
+ // <slot #secondSlot>
+ // (slotted) <div #slottedToSecondSlot>
+ // <div #belowSlots> tabindex=1
+ // <div #outside>
+ // First focusable = #aboveSlots
+ host.focus();
+ assert_equals(shadowRoot.activeElement, aboveSlots);
+ assert_equals(document.activeElement, host);
+}, "focus() on host with delegatesFocus & tabindex=0, #aboveSlots with tabindex=0 and #belowSlots with tabindex=1");
+
+test(() => {
+ resetTabIndexAndFocus();
+ setTabIndex([host, slottedToFirstSlot, slottedToSecondSlot, belowSlots], 0);
+ // Structure:
+ // <div #host> (delegatesFocus=true) tabindex=0
+ // #shadowRoot
+ // <div #aboveSlots>
+ // <slot #firstSlot>
+ // (slotted) <div #slottedToFirstSlot> tabindex=0
+ // <slot #secondSlot>
+ // (slotted) <div #slottedToSecondSlot> tabindex=0
+ // <div #belowSlots> tabindex=0
+ // <div #outside>
+ // First focusable = #slottedToFirstSlot
+ host.focus();
+ assert_equals(shadowRoot.activeElement, null);
+ assert_equals(document.activeElement, slottedToFirstSlot);
+}, "focus() on host with delegatesFocus & tabindex=0, #slottedToFirstSlot, #slottedToSecondSlot, #belowSlots with tabindex=0");
+
+</script>
Added: trunk/LayoutTests/imported/w3c/web-platform-tests/shadow-dom/focus/focus-tabindex-order-shadow-negative-delegatesFocus-expected.txt (0 => 251043)
--- trunk/LayoutTests/imported/w3c/web-platform-tests/shadow-dom/focus/focus-tabindex-order-shadow-negative-delegatesFocus-expected.txt (rev 0)
+++ trunk/LayoutTests/imported/w3c/web-platform-tests/shadow-dom/focus/focus-tabindex-order-shadow-negative-delegatesFocus-expected.txt 2019-10-12 06:09:04 UTC (rev 251043)
@@ -0,0 +1,7 @@
+aboveHost
+slotted below
+slotted above
+belowHost
+
+PASS Order when all tabindex=-1 is and delegatesFocus = true
+
Added: trunk/LayoutTests/imported/w3c/web-platform-tests/shadow-dom/focus/focus-tabindex-order-shadow-negative-delegatesFocus.html (0 => 251043)
--- trunk/LayoutTests/imported/w3c/web-platform-tests/shadow-dom/focus/focus-tabindex-order-shadow-negative-delegatesFocus.html (rev 0)
+++ trunk/LayoutTests/imported/w3c/web-platform-tests/shadow-dom/focus/focus-tabindex-order-shadow-negative-delegatesFocus.html 2019-10-12 06:09:04 UTC (rev 251043)
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>HTML Test: focus - the sequential focus navigation order with shadow dom that delegates focus and all tabindex=-1 in shadow tree</title>
+<link rel="help" href=""
+<script src=""
+<script src=""
+<script src=""
+<script src=""
+<script src=""
+<body>
+<script>
+// Structure:
+// <div #aboveHost tabindex=0>
+// <div #host tabindex=0>
+// #shadowRoot (delegatesFocus)
+// <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, true);
+ setTabIndex(elementsInFlatTreeOrder, -1);
+ setTabIndex([aboveHost, host, belowHost], 0);
+ resetFocus();
+ // Focus should only land on #aboveHost and #belowHost (all others are non-sequentially focusable).
+ return assertFocusOrder([aboveHost, belowHost]);
+}, "Order when all tabindex=-1 is and delegatesFocus = true");
+</script>
+</body>
Added: trunk/LayoutTests/imported/w3c/web-platform-tests/shadow-dom/focus/focus-tabindex-order-shadow-varying-delegatesFocus-expected.txt (0 => 251043)
--- trunk/LayoutTests/imported/w3c/web-platform-tests/shadow-dom/focus/focus-tabindex-order-shadow-varying-delegatesFocus-expected.txt (rev 0)
+++ trunk/LayoutTests/imported/w3c/web-platform-tests/shadow-dom/focus/focus-tabindex-order-shadow-varying-delegatesFocus-expected.txt 2019-10-12 06:09:04 UTC (rev 251043)
@@ -0,0 +1,7 @@
+aboveHost
+slotted below
+slotted above
+belowHost
+
+PASS Order when tabindex varies and delegatesFocus = true
+
Added: trunk/LayoutTests/imported/w3c/web-platform-tests/shadow-dom/focus/focus-tabindex-order-shadow-varying-delegatesFocus.html (0 => 251043)
--- trunk/LayoutTests/imported/w3c/web-platform-tests/shadow-dom/focus/focus-tabindex-order-shadow-varying-delegatesFocus.html (rev 0)
+++ trunk/LayoutTests/imported/w3c/web-platform-tests/shadow-dom/focus/focus-tabindex-order-shadow-varying-delegatesFocus.html 2019-10-12 06:09:04 UTC (rev 251043)
@@ -0,0 +1,36 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>HTML Test: focus - the sequential focus navigation order with shadow dom that delegates focus and tabindex in shadow tree varies</title>
+<link rel="help" href=""
+<script src=""
+<script src=""
+<script src=""
+<script src=""
+<script src=""
+<body>
+<script>
+// Structure:
+// <div #aboveHost tabindex=0>
+// <div #host tabindex=0>
+// #shadowRoot (delegatesFocus)
+// <div #aboveSlot tabindex=0>
+// <slot #slotAbove tabindex=1>
+// (slotted) <div #slottedAbove tabindex=3>
+// <slot #slotBelow tabindex=2>
+// (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, true);
+ setTabIndex(elementsInFlatTreeOrder, -1);
+ setTabIndex([aboveHost, host, aboveSlot, belowHost], 0);
+ setTabIndex([slotAbove, slottedBelow], 1);
+ setTabIndex([slotBelow], 2);
+ setTabIndex([slottedAbove], 3);
+ resetFocus();
+ return assertFocusOrder([aboveHost, slottedAbove, slottedBelow, aboveSlot, belowHost]);
+}, "Order when tabindex varies and delegatesFocus = true");
+</script>
+</body>
Added: trunk/LayoutTests/imported/w3c/web-platform-tests/shadow-dom/focus/focus-tabindex-order-shadow-zero-delegatesFocus-expected.txt (0 => 251043)
--- trunk/LayoutTests/imported/w3c/web-platform-tests/shadow-dom/focus/focus-tabindex-order-shadow-zero-delegatesFocus-expected.txt (rev 0)
+++ trunk/LayoutTests/imported/w3c/web-platform-tests/shadow-dom/focus/focus-tabindex-order-shadow-zero-delegatesFocus-expected.txt 2019-10-12 06:09:04 UTC (rev 251043)
@@ -0,0 +1,7 @@
+aboveHost
+slotted below
+slotted above
+belowHost
+
+PASS Order when all tabindex=0 is and delegatesFocus = true
+
Added: trunk/LayoutTests/imported/w3c/web-platform-tests/shadow-dom/focus/focus-tabindex-order-shadow-zero-delegatesFocus.html (0 => 251043)
--- trunk/LayoutTests/imported/w3c/web-platform-tests/shadow-dom/focus/focus-tabindex-order-shadow-zero-delegatesFocus.html (rev 0)
+++ trunk/LayoutTests/imported/w3c/web-platform-tests/shadow-dom/focus/focus-tabindex-order-shadow-zero-delegatesFocus.html 2019-10-12 06:09:04 UTC (rev 251043)
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>HTML Test: focus - the sequential focus navigation order with shadow dom that delegates focus and all tabindex=0</title>
+<link rel="help" href=""
+<script src=""
+<script src=""
+<script src=""
+<script src=""
+<script src=""
+<body>
+<script>
+// Structure:
+// <div #aboveHost tabindex=0>
+// <div #host tabindex=0>
+// #shadowRoot (delegatesFocus)
+// <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, true);
+ setTabIndex(elementsInFlatTreeOrder, 0);
+ resetFocus();
+ // Focus should move in flat tree order since every one of them has tabindex ==0,
+ // but doesn't include slots and #host.
+ return assertFocusOrder([aboveHost, aboveSlot, slottedAbove, slottedBelow, belowSlot, belowHost]);
+}, "Order when all tabindex=0 is and delegatesFocus = true");
+</script>
+</body>
Modified: trunk/Source/WebCore/ChangeLog (251042 => 251043)
--- trunk/Source/WebCore/ChangeLog 2019-10-12 05:43:46 UTC (rev 251042)
+++ trunk/Source/WebCore/ChangeLog 2019-10-12 06:09:04 UTC (rev 251043)
@@ -1,3 +1,49 @@
+2019-10-11 Ryosuke Niwa <[email protected]>
+
+ Add the support for ShadowRoot.delegateFocus
+ https://bugs.webkit.org/show_bug.cgi?id=166484
+ <rdar://problem/29816058>
+
+ Reviewed by Antti Koivisto.
+
+ Implement delegatesFocus as specified in https://github.com/whatwg/html/pull/4796
+
+ When the shadow root of an element has delegates focus flag set, focusing on the shadow host would automatically
+ "delegates" focus to the first focusable element in the shadow tree instead.
+
+ The first focusable element is determined as the first element that is programatically focusable or mouse focusable
+ in the flat tree (composed tree in WebKit's terminology) in the case of the element getting focused via DOM API,
+ Element.prototype.focus, by via mouse down. In the case of sequential focus navigation (via tab key), it's the
+ first keyboard focusable element in the tabIndex order.
+
+ Tests: imported/w3c/web-platform-tests/shadow-dom/focus/click-focus-delegatesFocus-click-method.html
+ imported/w3c/web-platform-tests/shadow-dom/focus/click-focus-delegatesFocus-tabindex-varies.html
+ imported/w3c/web-platform-tests/shadow-dom/focus/click-focus-delegatesFocus-tabindex-zero.html
+ imported/w3c/web-platform-tests/shadow-dom/focus/focus-method-delegatesFocus.html
+ imported/w3c/web-platform-tests/shadow-dom/focus/focus-tabindex-order-shadow-negative-delegatesFocus.html
+ imported/w3c/web-platform-tests/shadow-dom/focus/focus-tabindex-order-shadow-varying-delegatesFocus.html
+ imported/w3c/web-platform-tests/shadow-dom/focus/focus-tabindex-order-shadow-zero-delegatesFocus.html
+
+ * dom/Element.cpp:
+ (WebCore::Element::isKeyboardFocusable const): A shadow host with the delegates focus flag is not considered as
+ keyboard focusable. The rest is taken care of by the existing logic in FocusController.
+ (WebCore::isProgramaticallyFocusable): Extracted from Element::focus.
+ (WebCore::findFirstProgramaticallyFocusableElementInComposedTree): Added.
+ (WebCore::Element::focus): Added the support for delegatesFocus.
+ * dom/Element.h:
+ (WebCore::ShadowRootInit::delegatesFocus): Added.
+ * dom/Element.idl: Ditto.
+ * dom/ShadowRoot.cpp:
+ (WebCore::ShadowRoot::ShadowRoot): Added delegatesFocus to the constructor.
+ * dom/ShadowRoot.h:
+ * page/EventHandler.cpp:
+ (WebCore::findFirstMouseFocusableElementInComposedTree): Added.
+ (WebCore::EventHandler::dispatchMouseEvent): Added the support for delegatesFocus. Uses the first mouse focusable
+ element in the flat tree (composed tree) order.
+ * page/FocusController.cpp:
+ (WebCore::FocusController::findFirstFocusableElementInShadowRoot):
+ * page/FocusController.h:
+
2019-10-11 Rob Buis <[email protected]>
Cleanup RuntimeEnabledFeatures includes
Modified: trunk/Source/WebCore/dom/Element.cpp (251042 => 251043)
--- trunk/Source/WebCore/dom/Element.cpp 2019-10-12 05:43:46 UTC (rev 251042)
+++ trunk/Source/WebCore/dom/Element.cpp 2019-10-12 06:09:04 UTC (rev 251043)
@@ -35,6 +35,7 @@
#include "ChromeClient.h"
#include "ClassChangeInvalidation.h"
#include "ComposedTreeAncestorIterator.h"
+#include "ComposedTreeIterator.h"
#include "ContainerNodeAlgorithms.h"
#include "CustomElementReactionQueue.h"
#include "CustomElementRegistry.h"
@@ -278,7 +279,13 @@
bool Element::isKeyboardFocusable(KeyboardEvent*) const
{
- return isFocusable() && !shouldBeIgnoredInSequentialFocusNavigation() && tabIndexSetExplicitly().valueOr(0) >= 0;
+ if (!(isFocusable() && !shouldBeIgnoredInSequentialFocusNavigation() && tabIndexSetExplicitly().valueOr(0) >= 0))
+ return false;
+ if (auto* root = shadowRoot()) {
+ if (root->delegatesFocus())
+ return false;
+ }
+ return true;
}
bool Element::isMouseFocusable() const
@@ -2333,7 +2340,7 @@
return Exception { NotSupportedError };
if (init.mode == ShadowRootMode::UserAgent)
return Exception { TypeError };
- auto shadow = ShadowRoot::create(document(), init.mode);
+ auto shadow = ShadowRoot::create(document(), init.mode, init.delegatesFocus ? ShadowRoot::DelegatesFocus::Yes : ShadowRoot::DelegatesFocus::No);
auto& result = shadow.get();
addShadowRoot(WTFMove(shadow));
return result;
@@ -2881,41 +2888,76 @@
return elementData()->findAttributeByName(qName);
}
+static bool isProgramaticallyFocusable(Element& element)
+{
+ ScriptDisallowedScope::InMainThread scriptDisallowedScope;
+ // If the stylesheets have already been loaded we can reliably check isFocusable.
+ // If not, we continue and set the focused node on the focus controller below so that it can be updated soon after attach.
+ if (element.document().haveStylesheetsLoaded()) {
+ if (!element.isFocusable())
+ return false;
+ }
+ return element.supportsFocus();
+}
+
+static RefPtr<Element> findFirstProgramaticallyFocusableElementInComposedTree(Element& host)
+{
+ ASSERT(host.shadowRoot());
+ for (auto& node : composedTreeDescendants(host)) {
+ if (!is<Element>(node))
+ continue;
+ auto& element = downcast<Element>(node);
+ if (isProgramaticallyFocusable(element))
+ return &element;
+ }
+ return nullptr;
+}
+
void Element::focus(bool restorePreviousSelection, FocusDirection direction)
{
if (!isConnected())
return;
- if (document().focusedElement() == this) {
- if (document().page())
- document().page()->chrome().client().elementDidRefocus(*this);
+ auto document = makeRef(this->document());
+ if (document->focusedElement() == this) {
+ if (document->page())
+ document->page()->chrome().client().elementDidRefocus(*this);
+ return;
+ }
+ RefPtr<Element> newTarget = this;
+ if (document->haveStylesheetsLoaded())
+ document->updateStyleIfNeeded();
+
+ if (&newTarget->document() != document.ptr())
return;
+
+ if (auto root = makeRefPtr(shadowRoot())) {
+ if (root->delegatesFocus()) {
+ newTarget = findFirstProgramaticallyFocusableElementInComposedTree(*this);
+ if (!newTarget)
+ return;
+ }
}
- // If the stylesheets have already been loaded we can reliably check isFocusable.
- // If not, we continue and set the focused node on the focus controller below so
- // that it can be updated soon after attach.
- if (document().haveStylesheetsLoaded()) {
- document().updateStyleIfNeeded();
- if (!isFocusable())
- return;
+ if (document->focusedElement() == newTarget) {
+ if (document->page())
+ document->page()->chrome().client().elementDidRefocus(*newTarget);
+ return;
}
- if (!supportsFocus())
+ if (!isProgramaticallyFocusable(*newTarget))
return;
- RefPtr<Node> protect;
- if (Page* page = document().page()) {
- auto& frame = *document().frame();
- if (!frame.hasHadUserInteraction() && !frame.isMainFrame() && !document().topDocument().securityOrigin().canAccess(document().securityOrigin()))
+ if (Page* page = document->page()) {
+ auto& frame = *document->frame();
+ if (!frame.hasHadUserInteraction() && !frame.isMainFrame() && !document->topDocument().securityOrigin().canAccess(document->securityOrigin()))
return;
// Focus and change event handlers can cause us to lose our last ref.
// If a focus event handler changes the focus to a different node it
// does not make sense to continue and update appearence.
- protect = this;
- if (!page->focusController().setFocusedElement(this, *document().frame(), direction))
+ if (!page->focusController().setFocusedElement(newTarget.get(), *document->frame(), direction))
return;
}
@@ -2924,7 +2966,7 @@
// Focusing a form element triggers animation in UIKit to scroll to the right position.
// Calling updateFocusAppearance() would generate an unnecessary call to ScrollView::setScrollPosition(),
// which would jump us around during this animation. See <rdar://problem/6699741>.
- bool isFormControl = is<HTMLFormControlElement>(*this);
+ bool isFormControl = is<HTMLFormControlElement>(newTarget);
if (isFormControl)
revealMode = SelectionRevealMode::RevealUpToMainFrame;
#endif
@@ -2936,6 +2978,7 @@
target->updateFocusAppearance(restorePreviousSelection ? SelectionRestorationMode::Restore : SelectionRestorationMode::SetDefault, revealMode);
}
+// https://html.spec.whatwg.org/#focus-processing-model
RefPtr<Element> Element::focusAppearanceUpdateTarget()
{
return this;
Modified: trunk/Source/WebCore/dom/Element.h (251042 => 251043)
--- trunk/Source/WebCore/dom/Element.h 2019-10-12 05:43:46 UTC (rev 251042)
+++ trunk/Source/WebCore/dom/Element.h 2019-10-12 06:09:04 UTC (rev 251043)
@@ -290,6 +290,7 @@
struct ShadowRootInit {
ShadowRootMode mode;
+ bool delegatesFocus { false };
};
ExceptionOr<ShadowRoot&> attachShadow(const ShadowRootInit&);
Modified: trunk/Source/WebCore/dom/Element.idl (251042 => 251043)
--- trunk/Source/WebCore/dom/Element.idl 2019-10-12 05:43:46 UTC (rev 251042)
+++ trunk/Source/WebCore/dom/Element.idl 2019-10-12 06:09:04 UTC (rev 251043)
@@ -151,6 +151,7 @@
dictionary ShadowRootInit {
required ShadowRootMode mode;
+ boolean delegatesFocus = false;
};
Element implements AccessibilityRole;
Modified: trunk/Source/WebCore/dom/ShadowRoot.cpp (251042 => 251043)
--- trunk/Source/WebCore/dom/ShadowRoot.cpp 2019-10-12 05:43:46 UTC (rev 251042)
+++ trunk/Source/WebCore/dom/ShadowRoot.cpp 2019-10-12 06:09:04 UTC (rev 251043)
@@ -56,9 +56,10 @@
COMPILE_ASSERT(sizeof(ShadowRoot) == sizeof(SameSizeAsShadowRoot), shadowroot_should_stay_small);
-ShadowRoot::ShadowRoot(Document& document, ShadowRootMode type)
+ShadowRoot::ShadowRoot(Document& document, ShadowRootMode type, DelegatesFocus delegatesFocus)
: DocumentFragment(document, CreateShadowRoot)
, TreeScope(*this, document)
+ , m_delegatesFocus(delegatesFocus == DelegatesFocus::Yes)
, m_type(type)
, m_styleScope(makeUnique<Style::Scope>(*this))
{
Modified: trunk/Source/WebCore/dom/ShadowRoot.h (251042 => 251043)
--- trunk/Source/WebCore/dom/ShadowRoot.h 2019-10-12 05:43:46 UTC (rev 251042)
+++ trunk/Source/WebCore/dom/ShadowRoot.h 2019-10-12 06:09:04 UTC (rev 251043)
@@ -41,9 +41,12 @@
class ShadowRoot final : public DocumentFragment, public TreeScope {
WTF_MAKE_ISO_ALLOCATED(ShadowRoot);
public:
- static Ref<ShadowRoot> create(Document& document, ShadowRootMode type)
+
+ enum class DelegatesFocus : uint8_t { Yes, No };
+
+ static Ref<ShadowRoot> create(Document& document, ShadowRootMode type, DelegatesFocus delegatesFocus = DelegatesFocus::No)
{
- return adoptRef(*new ShadowRoot(document, type));
+ return adoptRef(*new ShadowRoot(document, type, delegatesFocus));
}
static Ref<ShadowRoot> create(Document& document, std::unique_ptr<SlotAssignment>&& assignment)
@@ -61,6 +64,7 @@
bool resetStyleInheritance() const { return m_resetStyleInheritance; }
void setResetStyleInheritance(bool);
+ bool delegatesFocus() const { return m_delegatesFocus; }
bool containsFocusedElement() const { return m_containsFocusedElement; }
void setContainsFocusedElement(bool flag) { m_containsFocusedElement = flag; }
@@ -100,12 +104,10 @@
const PartMappings& partMappings() const;
void invalidatePartMappings();
-protected:
- ShadowRoot(Document&, ShadowRootMode);
-
+private:
+ ShadowRoot(Document&, ShadowRootMode, DelegatesFocus);
ShadowRoot(Document&, std::unique_ptr<SlotAssignment>&&);
-private:
bool childTypeAllowed(NodeType) const override;
Ref<Node> cloneNodeInternal(Document&, CloningOperation) override;
@@ -117,6 +119,7 @@
bool m_resetStyleInheritance { false };
bool m_hasBegunDeletingDetachedChildren { false };
+ bool m_delegatesFocus { false };
bool m_containsFocusedElement { false };
ShadowRootMode m_type { ShadowRootMode::UserAgent };
Modified: trunk/Source/WebCore/page/EventHandler.cpp (251042 => 251043)
--- trunk/Source/WebCore/page/EventHandler.cpp 2019-10-12 05:43:46 UTC (rev 251042)
+++ trunk/Source/WebCore/page/EventHandler.cpp 2019-10-12 06:09:04 UTC (rev 251043)
@@ -34,6 +34,7 @@
#include "Chrome.h"
#include "ChromeClient.h"
#include "ComposedTreeAncestorIterator.h"
+#include "ComposedTreeIterator.h"
#include "CursorList.h"
#include "DocumentMarkerController.h"
#include "DragController.h"
@@ -2619,6 +2620,19 @@
}
}
+static RefPtr<Element> findFirstMouseFocusableElementInComposedTree(Element& host)
+{
+ ASSERT(host.shadowRoot());
+ for (auto& node : composedTreeDescendants(host)) {
+ if (!is<Element>(node))
+ continue;
+ auto& element = downcast<Element>(node);
+ if (element.isMouseFocusable())
+ return &element;
+ }
+ return nullptr;
+}
+
bool EventHandler::dispatchMouseEvent(const AtomString& eventType, Node* targetNode, bool /*cancelable*/, int clickCount, const PlatformMouseEvent& platformMouseEvent, bool setUnder)
{
Ref<Frame> protectedFrame(m_frame);
@@ -2650,6 +2664,12 @@
// Walk up the DOM tree to search for an element to focus.
RefPtr<Element> element;
for (element = m_elementUnderMouse.get(); element; element = element->parentElementInComposedTree()) {
+ if (auto* shadowRoot = element->shadowRoot()) {
+ if (shadowRoot->delegatesFocus()) {
+ element = findFirstMouseFocusableElementInComposedTree(*element);
+ break;
+ }
+ }
if (element->isMouseFocusable())
break;
}