Title: [251043] trunk
Revision
251043
Author
[email protected]
Date
2019-10-11 23:09:04 -0700 (Fri, 11 Oct 2019)

Log Message

Add the support for ShadowRoot.delegateFocus
https://bugs.webkit.org/show_bug.cgi?id=166484
<rdar://problem/29816058>

Reviewed by Antti Koivisto.

LayoutTests/imported/w3c:

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.

Source/WebCore:

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:

Modified Paths

Added Paths

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;
     }
_______________________________________________
webkit-changes mailing list
[email protected]
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to