Focusing a shadow host which delegates focus should skip inner shadow hosts which...
authorrniwa@webkit.org <rniwa@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Mon, 18 Nov 2019 06:27:08 +0000 (06:27 +0000)
committerrniwa@webkit.org <rniwa@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Mon, 18 Nov 2019 06:27:08 +0000 (06:27 +0000)
https://bugs.webkit.org/show_bug.cgi?id=203869

Reviewed by Antti Koivisto.

LayoutTests/imported/w3c:

Imported the latest test from https://github.com/web-platform-tests/wpt/pull/20079.

* web-platform-tests/shadow-dom/focus/focus-method-delegatesFocus-expected.txt:
* web-platform-tests/shadow-dom/focus/focus-method-delegatesFocus.html:

Source/WebCore:

Fixed the bug that WebKit doesn't skip a shadow host with delegatesFocus set when looking for
the first programatically focusable element.

Test: imported/w3c/web-platform-tests/shadow-dom/focus/focus-method-delegatesFocus.html

* dom/Element.cpp:
(WebCore::shadowRootWithDelegatesFocus): Added.
(WebCore::isProgramaticallyFocusable): Updated to return false on a shadow host with delegatesFocus.
(WebCore::Element::focus): Don't move the focus if the shadow host only contains a focused element
per https://github.com/w3c/webcomponents/issues/840.

git-svn-id: http://svn.webkit.org/repository/webkit/trunk@252537 268f45cc-cd09-0410-ab3c-d52691b4dbfc

LayoutTests/imported/w3c/ChangeLog
LayoutTests/imported/w3c/web-platform-tests/shadow-dom/focus/focus-method-delegatesFocus-expected.txt
LayoutTests/imported/w3c/web-platform-tests/shadow-dom/focus/focus-method-delegatesFocus.html
Source/WebCore/ChangeLog
Source/WebCore/dom/Element.cpp

index b1cf9ad..2552859 100644 (file)
@@ -1,3 +1,15 @@
+2019-11-17  Ryosuke Niwa  <rniwa@webkit.org>
+
+        Focusing a shadow host which delegates focus should skip inner shadow hosts which delegate focus
+        https://bugs.webkit.org/show_bug.cgi?id=203869
+
+        Reviewed by Antti Koivisto.
+
+        Imported the latest test from https://github.com/web-platform-tests/wpt/pull/20079.
+
+        * web-platform-tests/shadow-dom/focus/focus-method-delegatesFocus-expected.txt:
+        * web-platform-tests/shadow-dom/focus/focus-method-delegatesFocus.html:
+
 2019-11-07  Youenn Fablet  <youenn@apple.com>
 
         Update libwebrtc to M78
index ac1770d..be8e4e2 100644 (file)
@@ -13,4 +13,7 @@ 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 
+PASS focus() on host with delegatesFocus and already-focused non-first shadow descendant 
+PASS focus() on host with delegatesFocus with another host with no delegatesFocus and a focusable child 
+PASS focus() on host with delegatesFocus with another host with delegatesFocus and a focusable child 
 
index 368604b..d89f99a 100644 (file)
@@ -208,7 +208,7 @@ test(() => {
   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);
@@ -228,4 +228,59 @@ test(() => {
   assert_equals(document.activeElement, slottedToFirstSlot);
 }, "focus() on host with delegatesFocus & tabindex=0, #slottedToFirstSlot, #slottedToSecondSlot, #belowSlots  with tabindex=0");
 
+test(() => {
+  resetTabIndexAndFocus();
+  setTabIndex([aboveSlots, belowSlots], 0);
+  belowSlots.focus();
+  host.focus();
+  assert_equals(shadowRoot.activeElement, belowSlots);
+}, "focus() on host with delegatesFocus and already-focused non-first shadow descendant");
+
+function createNestedHosts(innerDelegatesFocus) {
+  // Structure:
+  // <div> outerHost
+  //   <input> outerLightChild
+  //   #shadowRoot outerShadow delegatesFocus=true
+  //     <span> innerHost
+  //       #shadowRoot inneShadow delegatesFocus=true/false
+  //         <input> innerShadowChild
+  //     <input> outerShadowChild
+  const outerHost = document.createElement('div');
+  const outerLightChild = document.createElement('input');
+  outerHost.appendChild(outerLightChild);
+  const innerHost = document.createElement('span');
+  const outerShadow = outerHost.attachShadow({mode: 'closed', delegatesFocus:true});
+  outerShadow.appendChild(innerHost);
+  const outerShadowChild = document.createElement('input');
+  outerShadow.appendChild(outerShadowChild);
+
+  const innerShadow = innerHost.attachShadow({mode: 'closed', delegatesFocus:innerDelegatesFocus});
+  const innerShadowChild = document.createElement('input');
+  innerShadow.appendChild(innerShadowChild);
+
+  document.body.insertBefore(outerHost, document.body.firstChild);
+  return {outerHost: outerHost,
+      outerLightChild: outerLightChild,
+      outerShadow: outerShadow,
+      outerShadowChild: outerShadowChild,
+      innerHost: innerHost,
+      innerShadow: innerShadow,
+      innerShadowChild: innerShadowChild};
+}
+
+test(() => {
+  const dom = createNestedHosts(false);
+  dom.outerHost.focus();
+  assert_equals(document.activeElement, dom.outerHost);
+  assert_equals(dom.outerShadow.activeElement, dom.innerHost);
+  assert_equals(dom.innerShadow.activeElement, dom.innerShadowChild);
+}, 'focus() on host with delegatesFocus with another host with no delegatesFocus and a focusable child');
+
+test(() => {
+  const dom = createNestedHosts(true);
+  dom.outerHost.focus();
+  assert_equals(document.activeElement, dom.outerHost);
+  assert_equals(dom.outerShadow.activeElement, dom.innerHost);
+  assert_equals(dom.innerShadow.activeElement, dom.innerShadowChild);
+}, 'focus() on host with delegatesFocus with another host with delegatesFocus and a focusable child');
 </script>
index ce6143f..30f7f68 100644 (file)
@@ -1,3 +1,21 @@
+2019-11-17  Ryosuke Niwa  <rniwa@webkit.org>
+
+        Focusing a shadow host which delegates focus should skip inner shadow hosts which delegate focus
+        https://bugs.webkit.org/show_bug.cgi?id=203869
+
+        Reviewed by Antti Koivisto.
+
+        Fixed the bug that WebKit doesn't skip a shadow host with delegatesFocus set when looking for
+        the first programatically focusable element.
+
+        Test: imported/w3c/web-platform-tests/shadow-dom/focus/focus-method-delegatesFocus.html
+
+        * dom/Element.cpp:
+        (WebCore::shadowRootWithDelegatesFocus): Added.
+        (WebCore::isProgramaticallyFocusable): Updated to return false on a shadow host with delegatesFocus.
+        (WebCore::Element::focus): Don't move the focus if the shadow host only contains a focused element
+        per https://github.com/w3c/webcomponents/issues/840.
+
 2019-11-17  Zalan Bujtas  <zalan@apple.com>
 
         [LFC] Move layout state initialization out of LayoutContext
index f37c930..8e30cfa 100644 (file)
@@ -2902,9 +2902,22 @@ bool Element::hasAttributeNS(const AtomString& namespaceURI, const AtomString& l
     return elementData()->findAttributeByName(qName);
 }
 
+static RefPtr<ShadowRoot> shadowRootWithDelegatesFocus(const Element& element)
+{
+    if (auto* root = element.shadowRoot()) {
+        if (root->delegatesFocus())
+            return root;
+    }
+    return nullptr;
+}
+
 static bool isProgramaticallyFocusable(Element& element)
 {
     ScriptDisallowedScope::InMainThread scriptDisallowedScope;
+
+    if (shadowRootWithDelegatesFocus(element))
+        return false;
+
     // 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()) {
@@ -2946,21 +2959,18 @@ void Element::focus(bool restorePreviousSelection, FocusDirection direction)
     if (&newTarget->document() != document.ptr())
         return;
 
-    if (auto root = makeRefPtr(shadowRoot())) {
-        if (root->delegatesFocus()) {
-            newTarget = findFirstProgramaticallyFocusableElementInComposedTree(*this);            
-            if (!newTarget)
-                return;
+    if (auto root = shadowRootWithDelegatesFocus(*this)) {
+        auto currentlyFocusedElement = makeRefPtr(document->focusedElement());
+        if (root->containsIncludingShadowDOM(currentlyFocusedElement.get())) {
+            if (document->page())
+                document->page()->chrome().client().elementDidRefocus(*currentlyFocusedElement);
+            return;
         }
-    }
-
-    if (document->focusedElement() == newTarget) {
-        if (document->page())
-            document->page()->chrome().client().elementDidRefocus(*newTarget);
-        return;
-    }
 
-    if (!isProgramaticallyFocusable(*newTarget))
+        newTarget = findFirstProgramaticallyFocusableElementInComposedTree(*this);            
+        if (!newTarget)
+            return;
+    } else if (!isProgramaticallyFocusable(*newTarget))
         return;
 
     if (Page* page = document->page()) {