Title: [200964] trunk
Revision
200964
Author
rn...@webkit.org
Date
2016-05-16 13:26:40 -0700 (Mon, 16 May 2016)

Log Message

Focus ordering should respect slot elements
https://bugs.webkit.org/show_bug.cgi?id=151379

Reviewed by Antti Koivisto.

Source/WebCore:

Implemented the sequential focus navigation ordering as discussed on
https://github.com/w3c/webcomponents/issues/375

New behavior treats each shadow root and slot as a "focus scope". The focus navigation ordering
is defined within each "focus scope" using tabindex, treating any "focus scope owner"
(e.g. shadow host or a slot) as if it was having tabindex=0 if it wasn't itself focusable.

This patch modifies FocusNavigationScope to support a focus scope defined for a slot element in
addition to the one defined for a shadow tree and a document as previously supported.

Tests: fast/shadow-dom/focus-across-details-element.html
       fast/shadow-dom/focus-navigation-across-slots.html

* dom/Node.cpp:
(WebCore::parentShadowRoot): Extracted from assignedSlot.
(WebCore::Node::assignedSlot):
(WebCore::Node::assignedSlotForBindings): Added.
* dom/Node.h:
* dom/NonDocumentTypeChildNode.idl:
* html/HTMLDetailsElement.h:
(HTMLDetailsElement::hasCustomFocusLogic): Added. Don't treat details element as a "focus scope".
* html/HTMLSummaryElement.h:
(HTMLSummaryElement::hasCustomFocusLogic): Ditto for summary element.
* page/FocusController.cpp:
(WebCore::hasCustomFocusLogic): Moved.
(WebCore::isFocusScopeOwner): Added. Returns true on a shadow host without a custom focus logic or
on a slot inside a shadow tree whose shadow host doesn't have a custom focus logic.
(WebCore::FocusNavigationScope::firstChildInScope): Now takes a reference. Call isFocusScopeOwner
to check for both slots and shadow roots instead of just the latter. This fixes a subtle bug that
focus may never get out of textarea in some cases due to its failure to check hasCustomFocusLogic.
(WebCore::FocusNavigationScope::lastChildInScope): Ditto.
(WebCore::FocusNavigationScope::parentInScope): Made this a member function since it needs to check
against m_slotElement inside the focus scope of a slot.
(WebCore::FocusNavigationScope::nextSiblingInScope): Added. Finds the next assigned node in a slot
in the focus scope defined for a slot. Just calls nextSibling() in the focus scope for shadow tree
and document.
(WebCore::FocusNavigationScope::previousSiblingInScope): Ditto for finding the previous sibling.
(WebCore::FocusNavigationScope::firstNodeInScope): Added. This function replaces rootNode() which
doesn't exist for the focus scope of a slot element.
(WebCore::FocusNavigationScope::lastNodeInScope): Ditto for the last node.
(WebCore::FocusNavigationScope::nextInScope):
(WebCore::FocusNavigationScope::previousInScope):
(WebCore::FocusNavigationScope::FocusNavigationScope): Added a variant that takes HTMLSlotElement.
(WebCore::FocusNavigationScope::owner): Added the support for slot elements.
(WebCore::FocusNavigationScope::scopeOf): Ditto.
(WebCore::FocusNavigationScope::scopeOwnedByScopeOwner): Ditto.
(WebCore::isFocusableElementOrScopeOwner): Added the support for slot elements and renamed from
isFocusableOrHasShadowTreeWithoutCustomFocusLogic.
(WebCore::isNonFocusableScopeOwner): Ditto. Renamed from isNonFocusableShadowHost.
(WebCore::isFocusableScopeOwner): Ditto. Renamed from isFocusableShadowHost.
(WebCore::shadowAdjustedTabIndex): Added the support for slot elements.
(WebCore::FocusController::findFocusableElementAcrossFocusScope):
(WebCore::FocusController::nextFocusableElementWithinScope):
(WebCore::FocusController::previousFocusableElementWithinScope):
(WebCore::FocusController::findElementWithExactTabIndex):
(WebCore::nextElementWithGreaterTabIndex): Call firstNodeInScope() instead of rootNode() here since
there is no root node for the focus scope defined for a slot element.
(WebCore::previousElementWithLowerTabIndex): Ditto for scope.lastNodeInScope().
(WebCore::FocusController::nextFocusableElementOrScopeOwner):
(WebCore::FocusController::previousFocusableElementOrScopeOwner):
(WebCore::parentInScope): Deleted.
(WebCore::FocusNavigationScope::rootNode): Deleted.
(WebCore::FocusNavigationScope::scopeOwnedByShadowHost): Deleted.
(WebCore::isNonFocusableShadowHost): Deleted.
(WebCore::isFocusableShadowHost): Deleted.
(WebCore::isFocusableOrHasShadowTreeWithoutCustomFocusLogic): Deleted.

LayoutTests:

Added regression tests for moving focus by tab and shift+tab across
user-defined shadow trees with slots and details element.

* fast/shadow-dom/focus-across-details-element-expected.txt: Added.
* fast/shadow-dom/focus-across-details-element.html: Added.
* fast/shadow-dom/focus-navigation-across-slots-expected.txt: Added.
* fast/shadow-dom/focus-navigation-across-slots.html: Added.

Modified Paths

Added Paths

Diff

Modified: trunk/LayoutTests/ChangeLog (200963 => 200964)


--- trunk/LayoutTests/ChangeLog	2016-05-16 20:24:52 UTC (rev 200963)
+++ trunk/LayoutTests/ChangeLog	2016-05-16 20:26:40 UTC (rev 200964)
@@ -1,3 +1,18 @@
+2016-05-16  Ryosuke Niwa  <rn...@webkit.org>
+
+        Focus ordering should respect slot elements
+        https://bugs.webkit.org/show_bug.cgi?id=151379
+
+        Reviewed by Antti Koivisto.
+
+        Added regression tests for moving focus by tab and shift+tab across
+        user-defined shadow trees with slots and details element.
+
+        * fast/shadow-dom/focus-across-details-element-expected.txt: Added.
+        * fast/shadow-dom/focus-across-details-element.html: Added.
+        * fast/shadow-dom/focus-navigation-across-slots-expected.txt: Added.
+        * fast/shadow-dom/focus-navigation-across-slots.html: Added.
+
 2016-05-16  Ryan Haddad  <ryanhad...@apple.com>
 
         Rebaseline tests for ios-simulator

Added: trunk/LayoutTests/fast/shadow-dom/focus-across-details-element-expected.txt (0 => 200964)


--- trunk/LayoutTests/fast/shadow-dom/focus-across-details-element-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/fast/shadow-dom/focus-across-details-element-expected.txt	2016-05-16 20:26:40 UTC (rev 200964)
@@ -0,0 +1,15 @@
+Tests for moving focus across details element. The existence of shadow tree on details element should not affect the focus sequential navigation ordering.
+To manually test, press tab key five times and then shift+tab five times. It should traverse focusable elements in the increasing numerical order and then in the reverse order.
+
+1. Focusable element with tabindex=1
+2. Focusable element in details with tabindex=2
+3. Focusable element in details with tabindex=3
+4. Focusable element in summary with tabindex=4
+5. Focusable element in details with tabindex=5
+6. Focusable element in details with tabindex=6
+5. Focusable element in details with tabindex=5
+4. Focusable element in summary with tabindex=4
+3. Focusable element in details with tabindex=3
+2. Focusable element in details with tabindex=2
+1. Focusable element with tabindex=1
+

Added: trunk/LayoutTests/fast/shadow-dom/focus-across-details-element.html (0 => 200964)


--- trunk/LayoutTests/fast/shadow-dom/focus-across-details-element.html	                        (rev 0)
+++ trunk/LayoutTests/fast/shadow-dom/focus-across-details-element.html	2016-05-16 20:26:40 UTC (rev 200964)
@@ -0,0 +1,49 @@
+<!DOCTYPE html>
+<html>
+<body>
+<p>Tests for moving focus across details element.
+The existence of shadow tree on details element should not affect the focus sequential navigation ordering.<br>
+To manually test, press tab key five times and then shift+tab five times.
+It should traverse focusable elements in the increasing numerical order and then in the reverse order.</p>
+<div id="test-content">
+<div id="first" tabindex="1" _onfocus_="log(this)">1. Focusable element with tabindex=1</div>
+<details open>
+    <div tabindex="6" _onfocus_="log(this)">6. Focusable element in details with tabindex=6</div>
+    <summary><div tabindex="4" _onfocus_="log(this)">4. Focusable element in summary with tabindex=4</div></summary>
+    <div tabindex="2" _onfocus_="log(this)">2. Focusable element in details with tabindex=2</div>
+</details>
+<details open>
+    <div tabindex="5" _onfocus_="log(this)">5. Focusable element in details with tabindex=5</div>
+    <div tabindex="3" _onfocus_="log(this)">3. Focusable element in details with tabindex=3</div>
+</details>
+</div>
+<pre></pre>
+<script>
+
+function log(element) {
+    document.querySelector('pre').textContent += element.textContent + '\n';
+}
+
+if (window.testRunner)
+    testRunner.dumpAsText();
+
+document.getElementById('first').focus();
+
+if (window.eventSender) {
+    eventSender.keyDown('\t');
+    eventSender.keyDown('\t');
+    eventSender.keyDown('\t');
+    eventSender.keyDown('\t');
+    eventSender.keyDown('\t');
+
+    eventSender.keyDown('\t', ['shiftKey']);
+    eventSender.keyDown('\t', ['shiftKey']);
+    eventSender.keyDown('\t', ['shiftKey']);
+    eventSender.keyDown('\t', ['shiftKey']);
+    eventSender.keyDown('\t', ['shiftKey']);
+    document.getElementById('test-content').style.display = 'none';
+}
+
+</script>
+</body>
+</html>

Added: trunk/LayoutTests/fast/shadow-dom/focus-navigation-across-slots-expected.txt (0 => 200964)


--- trunk/LayoutTests/fast/shadow-dom/focus-navigation-across-slots-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/fast/shadow-dom/focus-navigation-across-slots-expected.txt	2016-05-16 20:26:40 UTC (rev 200964)
@@ -0,0 +1,38 @@
+Tests for moving focus by pressing tab key across shadow boundaries.
+To manually test, press tab key sixteen times then shift+tab sixteen times.
+It should traverse focusable elements in the increasing numerical order and then in the reverse order.
+
+1. First sequentially focusable element
+2. The focusable element in shadow tree with the higehst tabindex
+3. The focusable element in shadow tree with the lowest tabindex
+4. Slotted content with tabindex=4
+5. Slotted content with tabindex=0
+6. The focusable element in shadow tree with the higehst tabindex
+7. Focusable slot 1
+8. Content in slot 1 with tabindex=7
+9. Content in slot 1 with tabindex=0
+10. Content in slot 2 with tabindex=1
+11. Content in slot 2 with tabindex=1
+12. Content in slot 2 with tabindex=0
+13. Non-focusable slot fallback with tabindex=1
+14. Focusable slot element.
+15. Shadow content with tabindex=2
+16. Non-focusable slot fallback with tabindex=0
+17. Focusable slot fallback content with tabindex=0
+16. Non-focusable slot fallback with tabindex=0
+15. Shadow content with tabindex=2
+14. Focusable slot element.
+13. Non-focusable slot fallback with tabindex=1
+12. Content in slot 2 with tabindex=0
+11. Content in slot 2 with tabindex=1
+10. Content in slot 2 with tabindex=1
+9. Content in slot 1 with tabindex=0
+8. Content in slot 1 with tabindex=7
+7. Focusable slot 1
+6. The focusable element in shadow tree with the higehst tabindex
+5. Slotted content with tabindex=0
+4. Slotted content with tabindex=4
+3. The focusable element in shadow tree with the lowest tabindex
+2. The focusable element in shadow tree with the higehst tabindex
+1. First sequentially focusable element
+

Added: trunk/LayoutTests/fast/shadow-dom/focus-navigation-across-slots.html (0 => 200964)


--- trunk/LayoutTests/fast/shadow-dom/focus-navigation-across-slots.html	                        (rev 0)
+++ trunk/LayoutTests/fast/shadow-dom/focus-navigation-across-slots.html	2016-05-16 20:26:40 UTC (rev 200964)
@@ -0,0 +1,150 @@
+<!DOCTYPE html>
+<html>
+<body>
+<p>Tests for moving focus by pressing tab key across shadow boundaries.<br>
+To manually test, press tab key sixteen times then shift+tab sixteen times.<br>
+It should traverse focusable elements in the increasing numerical order and then in the reverse order.</p>
+<style>
+
+#test-content > * {
+    display: block;
+    width: 350px;
+    height: 150px;
+    border: solid 1px black;
+    margin: 10px;
+    float: left;
+}
+
+pre {
+    padding-top: 1rem;
+    clear: both;
+}
+
+</style>
+<div id="test-content">
+
+<div id="first" tabindex="1">1. First sequentially focusable element</div>
+
+<div id="shadow-without-tabindex">
+A non-focusable shadow host should not be focused.
+<div slot="slot" tabindex="0">5. Slotted content with tabindex=0</div>
+<div slot="slot" tabindex="3">4. Slotted content with tabindex=4</div>
+</div>
+
+<div id="shadow-with-multiple-slots">
+A non-focusable shadow host should not be focused.
+<div slot="slot2"><div tabindex="2">11. Content in slot 2 with tabindex=1</div></div>
+<div slot="slot2" tabindex="1">10. Content in slot 2 with tabindex=1</div>
+<div slot="slot2" tabindex="0">12. Content in slot 2 with tabindex=0</div>
+<div slot="slot1" tabindex="0">9. Content in slot 1 with tabindex=0</div>
+<div slot="slot1" tabindex="7">8. Content in slot 1 with tabindex=7</div>
+</div>
+
+<div id="shadow-with-slot-fallback">
+A non-focusable shadow host should not be focused.
+</div>
+
+</div>
+<pre></pre>
+<script>
+
+var oldActiveElement = null;
+function log()
+{
+    setTimeout(function () {
+        var newActiveElement = document.activeElement;
+
+        var shadowRoot = newActiveElement.shadowRoot || newActiveElement.closedShadowRoot;
+        if (shadowRoot) {
+            var activeElementInShadow = shadowRoot.activeElement;
+            if (activeElementInShadow)
+                newActiveElement = activeElementInShadow;
+        }
+
+        if (oldActiveElement == newActiveElement || newActiveElement == document.body)
+            return;
+        oldActiveElement = newActiveElement;
+        document.querySelector('pre').textContent += newActiveElement.firstChild.textContent.trim() + '\n';
+    }, 0);
+}
+
+if (window.testRunner) {
+    testRunner.dumpAsText();
+    testRunner.waitUntilDone();
+}
+
+window._onload_ = function () {
+    document._onkeydown_ = log;
+
+    for (var element of document.querySelectorAll('#test-content *')) {
+        if (element instanceof HTMLIFrameElement)
+            element.contentDocument._onkeydown_ = log;
+        else
+            element._onfocus_ = log;
+    }
+
+    var hostWithoutTabIndex = document.getElementById('shadow-without-tabindex');
+    hostWithoutTabIndex.closedShadowRoot = hostWithoutTabIndex.attachShadow({mode: 'closed'});
+    hostWithoutTabIndex.closedShadowRoot.innerHTML = `
+        <div tabindex="2" _onfocus_="log(this)">3. The focusable element in shadow tree with the lowest tabindex</div>
+        <slot name="slot" _onfocus_="log(this)">Non-focusable slot element should not be focused</slot>
+        <div tabindex="1" _onfocus_="log(this)">2. The focusable element in shadow tree with the higehst tabindex</div>`;
+
+    var hostWithMultipleSlots = document.getElementById('shadow-with-multiple-slots');
+    hostWithMultipleSlots.closedShadowRoot = hostWithMultipleSlots.attachShadow({mode: 'closed'});
+    hostWithMultipleSlots.closedShadowRoot.innerHTML = `
+        <slot name="slot1" tabindex="2" _onfocus_="log(this)" style="display:block;">7. Focusable slot 1</slot>
+        <div tabindex="1" _onfocus_="log(this)">6. The focusable element in shadow tree with the higehst tabindex</div>
+        <slot name="slot2" _onfocus_="log(this)">Non-focusable slot 2 should not be focused</slot>`;
+
+    var shadowWithSlotFallback = document.getElementById('shadow-with-slot-fallback');
+    shadowWithSlotFallback.closedShadowRoot = shadowWithSlotFallback.attachShadow({mode: 'closed'});
+    shadowWithSlotFallback.closedShadowRoot.innerHTML = `
+        <slot name="slot1" _onfocus_="log(this)">
+            Non-focusable slot should not be focused.
+            <div tabindex="0">16. Non-focusable slot fallback with tabindex=0</div>
+            <div tabindex="1">13. Non-focusable slot fallback with tabindex=1</div>
+        </slot>
+        <div tabindex="2" _onfocus_="log(this)">15. Shadow content with tabindex=2</div>
+        <slot name="slot2" tabindex="1" style="display:block;" _onfocus_="log(this)">
+            14. Focusable slot element.
+            <div tabindex="0">17. Focusable slot fallback content with tabindex=0</div>
+        </slot>`;
+
+    document.getElementById('first').focus();
+    document.onkeydown();
+
+    if (window.eventSender)
+        moveFocusForward(16);
+}
+
+function moveFocusForward(focusCount)
+{
+    setTimeout(function () {
+        if (!focusCount)
+            return moveFocusBackward(16);
+        eventSender.keyDown('\t');
+        setTimeout(function () {
+            moveFocusForward(focusCount - 1);            
+        }, 1);
+    }, 1);
+}
+
+function moveFocusBackward(focusCount)
+{
+    setTimeout(function () {
+        if (!focusCount) {
+            document.getElementById('test-content').style.display = 'none';
+            testRunner.notifyDone();
+            return;
+        }
+        eventSender.keyDown('\t', ['shiftKey']);
+        setTimeout(function () {
+            moveFocusBackward(focusCount - 1);            
+        }, 1);
+    }, 1);
+}
+
+</script>
+</body>
+</html>

Modified: trunk/LayoutTests/platform/ios-simulator/TestExpectations (200963 => 200964)


--- trunk/LayoutTests/platform/ios-simulator/TestExpectations	2016-05-16 20:24:52 UTC (rev 200963)
+++ trunk/LayoutTests/platform/ios-simulator/TestExpectations	2016-05-16 20:26:40 UTC (rev 200964)
@@ -256,6 +256,8 @@
 webkit.org/b/148695 fast/shadow-dom [ Pass ]
 
 # No tab navigation support on iOS
+fast/shadow-dom/focus-across-details-element.html [ Failure ]
+fast/shadow-dom/focus-navigation-across-slots.html [ Failure ]
 fast/shadow-dom/focus-on-iframe.html [ Failure ]
 fast/shadow-dom/negative-tabindex-on-shadow-host.html [ Failure ]
 

Modified: trunk/Source/WebCore/ChangeLog (200963 => 200964)


--- trunk/Source/WebCore/ChangeLog	2016-05-16 20:24:52 UTC (rev 200963)
+++ trunk/Source/WebCore/ChangeLog	2016-05-16 20:26:40 UTC (rev 200964)
@@ -1,3 +1,77 @@
+2016-05-16  Ryosuke Niwa  <rn...@webkit.org>
+
+        Focus ordering should respect slot elements
+        https://bugs.webkit.org/show_bug.cgi?id=151379
+
+        Reviewed by Antti Koivisto.
+
+        Implemented the sequential focus navigation ordering as discussed on
+        https://github.com/w3c/webcomponents/issues/375
+
+        New behavior treats each shadow root and slot as a "focus scope". The focus navigation ordering
+        is defined within each "focus scope" using tabindex, treating any "focus scope owner"
+        (e.g. shadow host or a slot) as if it was having tabindex=0 if it wasn't itself focusable.
+
+        This patch modifies FocusNavigationScope to support a focus scope defined for a slot element in
+        addition to the one defined for a shadow tree and a document as previously supported.
+
+        Tests: fast/shadow-dom/focus-across-details-element.html
+               fast/shadow-dom/focus-navigation-across-slots.html
+
+        * dom/Node.cpp:
+        (WebCore::parentShadowRoot): Extracted from assignedSlot.
+        (WebCore::Node::assignedSlot):
+        (WebCore::Node::assignedSlotForBindings): Added.
+        * dom/Node.h:
+        * dom/NonDocumentTypeChildNode.idl:
+        * html/HTMLDetailsElement.h:
+        (HTMLDetailsElement::hasCustomFocusLogic): Added. Don't treat details element as a "focus scope".
+        * html/HTMLSummaryElement.h:
+        (HTMLSummaryElement::hasCustomFocusLogic): Ditto for summary element.
+        * page/FocusController.cpp:
+        (WebCore::hasCustomFocusLogic): Moved.
+        (WebCore::isFocusScopeOwner): Added. Returns true on a shadow host without a custom focus logic or
+        on a slot inside a shadow tree whose shadow host doesn't have a custom focus logic.
+        (WebCore::FocusNavigationScope::firstChildInScope): Now takes a reference. Call isFocusScopeOwner
+        to check for both slots and shadow roots instead of just the latter. This fixes a subtle bug that
+        focus may never get out of textarea in some cases due to its failure to check hasCustomFocusLogic.
+        (WebCore::FocusNavigationScope::lastChildInScope): Ditto.
+        (WebCore::FocusNavigationScope::parentInScope): Made this a member function since it needs to check
+        against m_slotElement inside the focus scope of a slot.
+        (WebCore::FocusNavigationScope::nextSiblingInScope): Added. Finds the next assigned node in a slot
+        in the focus scope defined for a slot. Just calls nextSibling() in the focus scope for shadow tree
+        and document.
+        (WebCore::FocusNavigationScope::previousSiblingInScope): Ditto for finding the previous sibling.
+        (WebCore::FocusNavigationScope::firstNodeInScope): Added. This function replaces rootNode() which
+        doesn't exist for the focus scope of a slot element.
+        (WebCore::FocusNavigationScope::lastNodeInScope): Ditto for the last node.
+        (WebCore::FocusNavigationScope::nextInScope):
+        (WebCore::FocusNavigationScope::previousInScope):
+        (WebCore::FocusNavigationScope::FocusNavigationScope): Added a variant that takes HTMLSlotElement.
+        (WebCore::FocusNavigationScope::owner): Added the support for slot elements.
+        (WebCore::FocusNavigationScope::scopeOf): Ditto.
+        (WebCore::FocusNavigationScope::scopeOwnedByScopeOwner): Ditto.
+        (WebCore::isFocusableElementOrScopeOwner): Added the support for slot elements and renamed from
+        isFocusableOrHasShadowTreeWithoutCustomFocusLogic.
+        (WebCore::isNonFocusableScopeOwner): Ditto. Renamed from isNonFocusableShadowHost.
+        (WebCore::isFocusableScopeOwner): Ditto. Renamed from isFocusableShadowHost.
+        (WebCore::shadowAdjustedTabIndex): Added the support for slot elements.
+        (WebCore::FocusController::findFocusableElementAcrossFocusScope):
+        (WebCore::FocusController::nextFocusableElementWithinScope):
+        (WebCore::FocusController::previousFocusableElementWithinScope):
+        (WebCore::FocusController::findElementWithExactTabIndex):
+        (WebCore::nextElementWithGreaterTabIndex): Call firstNodeInScope() instead of rootNode() here since
+        there is no root node for the focus scope defined for a slot element.
+        (WebCore::previousElementWithLowerTabIndex): Ditto for scope.lastNodeInScope().
+        (WebCore::FocusController::nextFocusableElementOrScopeOwner):
+        (WebCore::FocusController::previousFocusableElementOrScopeOwner):
+        (WebCore::parentInScope): Deleted.
+        (WebCore::FocusNavigationScope::rootNode): Deleted.
+        (WebCore::FocusNavigationScope::scopeOwnedByShadowHost): Deleted.
+        (WebCore::isNonFocusableShadowHost): Deleted.
+        (WebCore::isFocusableShadowHost): Deleted.
+        (WebCore::isFocusableOrHasShadowTreeWithoutCustomFocusLogic): Deleted.
+
 2016-05-16  Chris Dumez  <cdu...@apple.com>
 
         Use WTF::Optional for ScrollView's m_deferredScrollDelta / m_deferredScrollOffsets

Modified: trunk/Source/WebCore/dom/Node.cpp (200963 => 200964)


--- trunk/Source/WebCore/dom/Node.cpp	2016-05-16 20:24:52 UTC (rev 200963)
+++ trunk/Source/WebCore/dom/Node.cpp	2016-05-16 20:26:40 UTC (rev 200964)
@@ -1123,18 +1123,27 @@
     return false;
 }
 
-#if ENABLE(SHADOW_DOM)
+#if ENABLE(SHADOW_DOM) || ENABLE(DETAILS_ELEMENT)
+static inline ShadowRoot* parentShadowRoot(const Node& node)
+{
+    if (auto* parent = node.parentElement())
+        return parent->shadowRoot();
+    return nullptr;
+}
+
 HTMLSlotElement* Node::assignedSlot() const
 {
-    auto* parent = parentElement();
-    if (!parent)
-        return nullptr;
+    if (auto* shadowRoot = parentShadowRoot(*this))
+        return shadowRoot->findAssignedSlot(*this);
+    return nullptr;
+}
 
-    auto* shadowRoot = parent->shadowRoot();
-    if (!shadowRoot || shadowRoot->type() != ShadowRoot::Type::Open)
-        return nullptr;
-
-    return shadowRoot->findAssignedSlot(*this);
+HTMLSlotElement* Node::assignedSlotForBindings() const
+{
+    auto* shadowRoot = parentShadowRoot(*this);
+    if (shadowRoot && shadowRoot->type() == ShadowRoot::Type::Open)
+        return shadowRoot->findAssignedSlot(*this);
+    return nullptr;
 }
 #endif
 
@@ -1142,12 +1151,8 @@
 {
     ASSERT(isMainThreadOrGCThread());
 #if ENABLE(SHADOW_DOM) || ENABLE(DETAILS_ELEMENT)
-    if (auto* parent = parentElement()) {
-        if (auto* shadowRoot = parent->shadowRoot()) {
-            if (auto* assignedSlot = shadowRoot->findAssignedSlot(*this))
-                return assignedSlot;
-        }
-    }
+    if (auto* slot = assignedSlot())
+        return slot;
 #endif
     if (is<ShadowRoot>(*this))
         return downcast<ShadowRoot>(*this).host();

Modified: trunk/Source/WebCore/dom/Node.h (200963 => 200964)


--- trunk/Source/WebCore/dom/Node.h	2016-05-16 20:24:52 UTC (rev 200963)
+++ trunk/Source/WebCore/dom/Node.h	2016-05-16 20:26:40 UTC (rev 200964)
@@ -261,8 +261,9 @@
     ShadowRoot* shadowRoot() const;
     bool isUnclosedNode(const Node&) const;
 
-#if ENABLE(SHADOW_DOM)
+#if ENABLE(SHADOW_DOM) || ENABLE(DETAILS_ELEMENT)
     HTMLSlotElement* assignedSlot() const;
+    HTMLSlotElement* assignedSlotForBindings() const;
 #endif
 
 #if ENABLE(CUSTOM_ELEMENTS)

Modified: trunk/Source/WebCore/dom/NonDocumentTypeChildNode.idl (200963 => 200964)


--- trunk/Source/WebCore/dom/NonDocumentTypeChildNode.idl	2016-05-16 20:24:52 UTC (rev 200963)
+++ trunk/Source/WebCore/dom/NonDocumentTypeChildNode.idl	2016-05-16 20:26:40 UTC (rev 200964)
@@ -32,6 +32,6 @@
     readonly attribute Element nextElementSibling;
 
 #if defined(LANGUAGE_JAVASCRIPT) && LANGUAGE_JAVASCRIPT
-    [Conditional=SHADOW_DOM, EnabledAtRuntime=ShadowDOM] readonly attribute HTMLSlotElement assignedSlot;
+    [Conditional=SHADOW_DOM, EnabledAtRuntime=ShadowDOM, ImplementedAs=assignedSlotForBindings] readonly attribute HTMLSlotElement assignedSlot;
 #endif
 };

Modified: trunk/Source/WebCore/html/HTMLDetailsElement.h (200963 => 200964)


--- trunk/Source/WebCore/html/HTMLDetailsElement.h	2016-05-16 20:24:52 UTC (rev 200963)
+++ trunk/Source/WebCore/html/HTMLDetailsElement.h	2016-05-16 20:26:40 UTC (rev 200964)
@@ -43,6 +43,7 @@
 
     void didAddUserAgentShadowRoot(ShadowRoot*) override;
     bool canHaveUserAgentShadowRoot() const final { return true; }
+    bool hasCustomFocusLogic() const final { return true; }
 
     bool m_isOpen { false };
     HTMLSlotElement* m_summarySlot { nullptr };

Modified: trunk/Source/WebCore/html/HTMLSummaryElement.h (200963 => 200964)


--- trunk/Source/WebCore/html/HTMLSummaryElement.h	2016-05-16 20:24:52 UTC (rev 200963)
+++ trunk/Source/WebCore/html/HTMLSummaryElement.h	2016-05-16 20:26:40 UTC (rev 200964)
@@ -44,6 +44,7 @@
 
     // FIXME: Shadow DOM spec says we should be able to create shadow root on this element
     bool canHaveUserAgentShadowRoot() const final { return true; }
+    bool hasCustomFocusLogic() const final { return true; }
 
     HTMLDetailsElement* detailsElement() const;
 

Modified: trunk/Source/WebCore/page/FocusController.cpp (200963 => 200964)


--- trunk/Source/WebCore/page/FocusController.cpp	2016-05-16 20:24:52 UTC (rev 200963)
+++ trunk/Source/WebCore/page/FocusController.cpp	2016-05-16 20:26:40 UTC (rev 200964)
@@ -46,6 +46,7 @@
 #include "HTMLInputElement.h"
 #include "HTMLNames.h"
 #include "HTMLPlugInElement.h"
+#include "HTMLSlotElement.h"
 #include "HTMLTextAreaElement.h"
 #include "HitTestResult.h"
 #include "KeyboardEvent.h"
@@ -67,89 +68,189 @@
 
 using namespace HTMLNames;
 
+static inline bool hasCustomFocusLogic(const Element& element)
+{
+    return is<HTMLElement>(element) && downcast<HTMLElement>(element).hasCustomFocusLogic();
+}
+
+static inline bool isFocusScopeOwner(const Element& element)
+{
+    if (element.shadowRoot() && !hasCustomFocusLogic(element))
+        return true;
+#if ENABLE(SHADOW_DOM) || ENABLE(DETAILS_ELEMENT)
+    if (is<HTMLSlotElement>(element) && downcast<HTMLSlotElement>(element).assignedNodes()) {
+        ShadowRoot* root = element.containingShadowRoot();
+        if (root && root->host() && !hasCustomFocusLogic(*root->host()))
+            return true;
+    }
+#endif
+    return false;
+}
+
 class FocusNavigationScope {
 public:
-    ContainerNode& rootNode() const;
     Element* owner() const;
     WEBCORE_EXPORT static FocusNavigationScope scopeOf(Node&);
-    static FocusNavigationScope scopeOwnedByShadowHost(Element&);
+    static FocusNavigationScope scopeOwnedByScopeOwner(Element&);
     static FocusNavigationScope scopeOwnedByIFrame(HTMLFrameOwnerElement&);
 
+    Node* firstNodeInScope() const;
+    Node* lastNodeInScope() const;
     Node* nextInScope(const Node*) const;
     Node* previousInScope(const Node*) const;
-    Node* lastChildInScope(const Node*) const;
+    Node* lastChildInScope(const Node&) const;
 
 private:
-    Node* firstChildInScope(const Node*) const;
+    Node* firstChildInScope(const Node&) const;
 
+    Node* parentInScope(const Node&) const;
+
+    Node* nextSiblingInScope(const Node&) const;
+    Node* previousSiblingInScope(const Node&) const;
+
     explicit FocusNavigationScope(TreeScope&);
-    TreeScope& m_rootTreeScope;
+
+#if ENABLE(SHADOW_DOM) || ENABLE(DETAILS_ELEMENT)
+    explicit FocusNavigationScope(HTMLSlotElement&);
+#endif
+
+    TreeScope* m_rootTreeScope { nullptr };
+#if ENABLE(SHADOW_DOM) || ENABLE(DETAILS_ELEMENT)
+    HTMLSlotElement* m_slotElement { nullptr };
+#endif
 };
 
 // FIXME: Focus navigation should work with shadow trees that have slots.
-Node* FocusNavigationScope::firstChildInScope(const Node* node) const
+Node* FocusNavigationScope::firstChildInScope(const Node& node) const
 {
-    ASSERT(node);
-    if (node->shadowRoot())
+    if (is<Element>(node) && isFocusScopeOwner(downcast<Element>(node)))
         return nullptr;
-    return node->firstChild();
+    return node.firstChild();
 }
 
-Node* FocusNavigationScope::lastChildInScope(const Node* node) const
+Node* FocusNavigationScope::lastChildInScope(const Node& node) const
 {
-    ASSERT(node);
-    if (node->shadowRoot())
+    if (is<Element>(node) && isFocusScopeOwner(downcast<Element>(node)))
         return nullptr;
-    return node->lastChild();
+    return node.lastChild();
 }
 
-static Node* parentInScope(const Node* node)
+Node* FocusNavigationScope::parentInScope(const Node& node) const
 {
-    if (node->isShadowRoot())
+    if (is<Element>(node) && isFocusScopeOwner(downcast<Element>(node)))
         return nullptr;
 
-    ContainerNode* parent = node->parentNode();
-    if (parent && parent->shadowRoot())
+#if ENABLE(SHADOW_DOM) || ENABLE(DETAILS_ELEMENT)
+    if (UNLIKELY(m_slotElement && m_slotElement == node.assignedSlot()))
         return nullptr;
+#endif
 
+    ContainerNode* parent = node.parentNode();
+    if (parent && is<Element>(parent) && isFocusScopeOwner(downcast<Element>(*parent)))
+        return nullptr;
+
     return parent;
 }
 
+Node* FocusNavigationScope::nextSiblingInScope(const Node& node) const
+{
+#if ENABLE(SHADOW_DOM) || ENABLE(DETAILS_ELEMENT)
+    if (UNLIKELY(m_slotElement && m_slotElement == node.assignedSlot())) {
+        for (Node* current = node.nextSibling(); current; current = current->nextSibling()) {
+            if (current->assignedSlot() == m_slotElement)
+                return current;
+        }
+        return nullptr;
+    }
+#endif
+    return node.nextSibling();
+}
+
+Node* FocusNavigationScope::previousSiblingInScope(const Node& node) const
+{
+#if ENABLE(SHADOW_DOM) || ENABLE(DETAILS_ELEMENT)
+    if (UNLIKELY(m_slotElement && m_slotElement == node.assignedSlot())) {
+        for (Node* current = node.previousSibling(); current; current = current->previousSibling()) {
+            if (current->assignedSlot() == m_slotElement)
+                return current;
+        }
+        return nullptr;
+    }
+#endif
+    return node.previousSibling();
+}
+
+Node* FocusNavigationScope::firstNodeInScope() const
+{
+#if ENABLE(SHADOW_DOM) || ENABLE(DETAILS_ELEMENT)
+    if (UNLIKELY(m_slotElement)) {
+        auto* assigneNodes = m_slotElement->assignedNodes();
+        ASSERT(assigneNodes);
+        return assigneNodes->first();
+    }
+#endif
+    ASSERT(m_rootTreeScope);
+    return &m_rootTreeScope->rootNode();
+}
+
+Node* FocusNavigationScope::lastNodeInScope() const
+{
+#if ENABLE(SHADOW_DOM) || ENABLE(DETAILS_ELEMENT)
+    if (UNLIKELY(m_slotElement)) {
+        auto* assigneNodes = m_slotElement->assignedNodes();
+        ASSERT(assigneNodes);
+        return assigneNodes->last();
+    }
+#endif
+    ASSERT(m_rootTreeScope);
+    return &m_rootTreeScope->rootNode();
+}
+
 Node* FocusNavigationScope::nextInScope(const Node* node) const
 {
-    if (Node* next = firstChildInScope(node))
+    ASSERT(node);
+    if (Node* next = firstChildInScope(*node))
         return next;
-    if (Node* next = node->nextSibling())
+    if (Node* next = nextSiblingInScope(*node))
         return next;
     const Node* current = node;
-    while (current && !current->nextSibling())
-        current = parentInScope(current);
-    return current ? current->nextSibling() : nullptr;
+    while (current && !nextSiblingInScope(*current))
+        current = parentInScope(*current);
+    return current ? nextSiblingInScope(*current) : nullptr;
 }
 
 Node* FocusNavigationScope::previousInScope(const Node* node) const
 {
-    if (Node* current = node->previousSibling()) {
-        while (Node* child = lastChildInScope(current))
+    ASSERT(node);
+    if (Node* current = previousSiblingInScope(*node)) {
+        while (Node* child = lastChildInScope(*current))
             current = child;
         return current;
     }
-    return parentInScope(node);
+    return parentInScope(*node);
 }
 
 FocusNavigationScope::FocusNavigationScope(TreeScope& treeScope)
-    : m_rootTreeScope(treeScope)
+    : m_rootTreeScope(&treeScope)
 {
 }
 
-ContainerNode& FocusNavigationScope::rootNode() const
+#if ENABLE(SHADOW_DOM) || ENABLE(DETAILS_ELEMENT)
+FocusNavigationScope::FocusNavigationScope(HTMLSlotElement& slotElement)
+    : m_slotElement(&slotElement)
 {
-    return m_rootTreeScope.rootNode();
 }
+#endif
 
 Element* FocusNavigationScope::owner() const
 {
-    ContainerNode& root = rootNode();
+#if ENABLE(SHADOW_DOM) || ENABLE(DETAILS_ELEMENT)
+    if (m_slotElement)
+        return m_slotElement;
+#endif
+
+    ASSERT(m_rootTreeScope);
+    ContainerNode& root = m_rootTreeScope->rootNode();
     if (is<ShadowRoot>(root))
         return downcast<ShadowRoot>(root).host();
     if (Frame* frame = root.document().frame())
@@ -159,17 +260,30 @@
 
 FocusNavigationScope FocusNavigationScope::scopeOf(Node& startingNode)
 {
+    ASSERT(startingNode.isInTreeScope());
     Node* root = nullptr;
-    for (Node* currentNode = &startingNode; currentNode; currentNode = parentInScope(currentNode))
+    for (Node* currentNode = &startingNode; currentNode; currentNode = currentNode->parentNode()) {
         root = currentNode;
-    // The result is not always a ShadowRoot nor a DocumentNode since
-    // a starting node is in an orphaned tree in composed shadow tree.
+#if ENABLE(SHADOW_DOM) || ENABLE(DETAILS_ELEMENT)
+        if (HTMLSlotElement* slot = currentNode->assignedSlot()) {
+            if (isFocusScopeOwner(*slot))
+                return FocusNavigationScope(*slot);
+        }
+#endif
+        if (is<ShadowRoot>(currentNode))
+            return FocusNavigationScope(downcast<ShadowRoot>(*currentNode));
+    }
+    ASSERT(root);
     return FocusNavigationScope(root->treeScope());
 }
 
-FocusNavigationScope FocusNavigationScope::scopeOwnedByShadowHost(Element& element)
+FocusNavigationScope FocusNavigationScope::scopeOwnedByScopeOwner(Element& element)
 {
-    ASSERT(element.shadowRoot());
+#if ENABLE(SHADOW_DOM) || ENABLE(DETAILS_ELEMENT)
+    ASSERT(element.shadowRoot() || is<HTMLSlotElement>(element));
+    if (is<HTMLSlotElement>(element))
+        return FocusNavigationScope(downcast<HTMLSlotElement>(element));
+#endif
     return FocusNavigationScope(*element.shadowRoot());
 }
 
@@ -199,35 +313,30 @@
         document->focusedElement()->dispatchFocusEvent(nullptr, FocusDirectionNone);
 }
 
-static inline bool hasCustomFocusLogic(Element& element)
+static inline bool isFocusableElementOrScopeOwner(Element& element, KeyboardEvent& event)
 {
-    return is<HTMLElement>(element) && downcast<HTMLElement>(element).hasCustomFocusLogic();
+    return element.isKeyboardFocusable(&event) || isFocusScopeOwner(element);
 }
 
-static inline bool isNonFocusableShadowHost(Element& element, KeyboardEvent& event)
+static inline bool isNonFocusableScopeOwner(Element& element, KeyboardEvent& event)
 {
-    return !element.isKeyboardFocusable(&event) && element.shadowRoot() && !hasCustomFocusLogic(element);
+    return !element.isKeyboardFocusable(&event) && isFocusScopeOwner(element);
 }
 
-static inline bool isFocusableShadowHost(Node& node, KeyboardEvent& event)
+static inline bool isFocusableScopeOwner(Element& element, KeyboardEvent& event)
 {
-    return is<Element>(node) && downcast<Element>(node).isKeyboardFocusable(&event) && downcast<Element>(node).shadowRoot() && !hasCustomFocusLogic(downcast<Element>(node));
+    return element.isKeyboardFocusable(&event) && isFocusScopeOwner(element);
 }
 
 static inline int shadowAdjustedTabIndex(Element& element, KeyboardEvent& event)
 {
-    if (isNonFocusableShadowHost(element, event)) {
+    if (isNonFocusableScopeOwner(element, event)) {
         if (!element.tabIndexSetExplicitly())
             return 0; // Treat a shadow host without tabindex if it has tabindex=0 even though HTMLElement::tabIndex returns -1 on such an element.
     }
     return element.tabIndex();
 }
 
-static inline bool isFocusableOrHasShadowTreeWithoutCustomFocusLogic(Element& element, KeyboardEvent& event)
-{
-    return element.isKeyboardFocusable(&event) || isNonFocusableShadowHost(element, event);
-}
-
 FocusController::FocusController(Page& page, ViewState::Flags viewState)
     : m_page(page)
     , m_isChangingFocusedFrame(false)
@@ -419,10 +528,10 @@
 
 Element* FocusController::findFocusableElementAcrossFocusScope(FocusDirection direction, const FocusNavigationScope& scope, Node* currentNode, KeyboardEvent* event)
 {
-    ASSERT(!is<Element>(currentNode) || !isNonFocusableShadowHost(*downcast<Element>(currentNode), *event));
+    ASSERT(!is<Element>(currentNode) || !isNonFocusableScopeOwner(downcast<Element>(*currentNode), *event));
 
-    if (currentNode && direction == FocusDirectionForward && isFocusableShadowHost(*currentNode, *event)) {
-        if (Element* candidateInInnerScope = findFocusableElementWithinScope(direction, FocusNavigationScope::scopeOwnedByShadowHost(downcast<Element>(*currentNode)), 0, event))
+    if (currentNode && direction == FocusDirectionForward && is<Element>(currentNode) && isFocusableScopeOwner(downcast<Element>(*currentNode), *event)) {
+        if (Element* candidateInInnerScope = findFocusableElementWithinScope(direction, FocusNavigationScope::scopeOwnedByScopeOwner(downcast<Element>(*currentNode)), 0, event))
             return candidateInInnerScope;
     }
 
@@ -432,7 +541,7 @@
     // If there's no focusable node to advance to, move up the focus scopes until we find one.
     Element* owner = scope.owner();
     while (owner) {
-        if (direction == FocusDirectionBackward && isFocusableShadowHost(*owner, *event))
+        if (direction == FocusDirectionBackward && isFocusableScopeOwner(*owner, *event))
             return findFocusableElementDescendingDownIntoFrameDocument(direction, owner, event);
 
         auto outerScope = FocusNavigationScope::scopeOf(*owner);
@@ -457,8 +566,8 @@
     Element* found = nextFocusableElementOrScopeOwner(scope, start, event);
     if (!found)
         return nullptr;
-    if (isNonFocusableShadowHost(*found, *event)) {
-        if (Element* foundInInnerFocusScope = nextFocusableElementWithinScope(FocusNavigationScope::scopeOwnedByShadowHost(*found), 0, event))
+    if (isNonFocusableScopeOwner(*found, *event)) {
+        if (Element* foundInInnerFocusScope = nextFocusableElementWithinScope(FocusNavigationScope::scopeOwnedByScopeOwner(*found), 0, event))
             return foundInInnerFocusScope;
         return nextFocusableElementWithinScope(scope, found, event);
     }
@@ -470,14 +579,14 @@
     Element* found = previousFocusableElementOrScopeOwner(scope, start, event);
     if (!found)
         return nullptr;
-    if (isFocusableShadowHost(*found, *event)) {
+    if (isFocusableScopeOwner(*found, *event)) {
         // Search an inner focusable element in the shadow tree from the end.
-        if (Element* foundInInnerFocusScope = previousFocusableElementWithinScope(FocusNavigationScope::scopeOwnedByShadowHost(*found), 0, event))
+        if (Element* foundInInnerFocusScope = previousFocusableElementWithinScope(FocusNavigationScope::scopeOwnedByScopeOwner(*found), 0, event))
             return foundInInnerFocusScope;
         return found;
     }
-    if (isNonFocusableShadowHost(*found, *event)) {
-        if (Element* foundInInnerFocusScope = previousFocusableElementWithinScope(FocusNavigationScope::scopeOwnedByShadowHost(*found), 0, event))
+    if (isNonFocusableScopeOwner(*found, *event)) {
+        if (Element* foundInInnerFocusScope = previousFocusableElementWithinScope(FocusNavigationScope::scopeOwnedByScopeOwner(*found), 0, event))
             return foundInInnerFocusScope;
         return previousFocusableElementWithinScope(scope, found, event);
     }
@@ -498,7 +607,7 @@
         if (!is<Element>(*node))
             continue;
         Element& element = downcast<Element>(*node);
-        if (isFocusableOrHasShadowTreeWithoutCustomFocusLogic(element, *event) && shadowAdjustedTabIndex(element, *event) == tabIndex)
+        if (isFocusableElementOrScopeOwner(element, *event) && shadowAdjustedTabIndex(element, *event) == tabIndex)
             return &element;
     }
     return nullptr;
@@ -509,12 +618,12 @@
     // Search is inclusive of start
     int winningTabIndex = std::numeric_limits<int>::max();
     Element* winner = nullptr;
-    for (Node* node = &scope.rootNode(); node; node = scope.nextInScope(node)) {
+    for (Node* node = scope.firstNodeInScope(); node; node = scope.nextInScope(node)) {
         if (!is<Element>(*node))
             continue;
         Element& candidate = downcast<Element>(*node);
         int candidateTabIndex = candidate.tabIndex();
-        if (isFocusableOrHasShadowTreeWithoutCustomFocusLogic(candidate, event) && candidateTabIndex > tabIndex && (!winner || candidateTabIndex < winningTabIndex)) {
+        if (isFocusableElementOrScopeOwner(candidate, event) && candidateTabIndex > tabIndex && (!winner || candidateTabIndex < winningTabIndex)) {
             winner = &candidate;
             winningTabIndex = candidateTabIndex;
         }
@@ -533,7 +642,7 @@
             continue;
         Element& element = downcast<Element>(*node);
         int currentTabIndex = shadowAdjustedTabIndex(element, event);
-        if (isFocusableOrHasShadowTreeWithoutCustomFocusLogic(element, event) && currentTabIndex < tabIndex && currentTabIndex > winningTabIndex) {
+        if (isFocusableElementOrScopeOwner(element, event) && currentTabIndex < tabIndex && currentTabIndex > winningTabIndex) {
             winner = &element;
             winningTabIndex = currentTabIndex;
         }
@@ -568,7 +677,7 @@
                 if (!is<Element>(*node))
                     continue;
                 Element& element = downcast<Element>(*node);
-                if (isFocusableOrHasShadowTreeWithoutCustomFocusLogic(element, *event) && shadowAdjustedTabIndex(element, *event) >= 0)
+                if (isFocusableElementOrScopeOwner(element, *event) && shadowAdjustedTabIndex(element, *event) >= 0)
                     return &element;
             }
         }
@@ -589,13 +698,13 @@
 
     // There are no nodes with a tabindex greater than start's tabindex,
     // so find the first node with a tabindex of 0.
-    return findElementWithExactTabIndex(scope, &scope.rootNode(), 0, event, FocusDirectionForward);
+    return findElementWithExactTabIndex(scope, scope.firstNodeInScope(), 0, event, FocusDirectionForward);
 }
 
 Element* FocusController::previousFocusableElementOrScopeOwner(const FocusNavigationScope& scope, Node* start, KeyboardEvent* event)
 {
     Node* last = nullptr;
-    for (Node* node = &scope.rootNode(); node; node = scope.lastChildInScope(node))
+    for (Node* node = scope.lastNodeInScope(); node; node = scope.lastChildInScope(*node))
         last = node;
     ASSERT(last);
 
@@ -616,7 +725,7 @@
             if (!is<Element>(*node))
                 continue;
             Element& element = downcast<Element>(*node);
-            if (isFocusableOrHasShadowTreeWithoutCustomFocusLogic(element, *event) && shadowAdjustedTabIndex(element, *event) >= 0)
+            if (isFocusableElementOrScopeOwner(element, *event) && shadowAdjustedTabIndex(element, *event) >= 0)
                 return &element;
         }
     }
_______________________________________________
webkit-changes mailing list
webkit-changes@lists.webkit.org
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to