Changeset 200964 in webkit


Ignore:
Timestamp:
May 16, 2016 1:26:40 PM (8 years ago)
Author:
rniwa@webkit.org
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.
Location:
trunk
Files:
4 added
9 edited

Legend:

Unmodified
Added
Removed
  • trunk/LayoutTests/ChangeLog

    r200960 r200964  
     12016-05-16  Ryosuke Niwa  <rniwa@webkit.org>
     2
     3        Focus ordering should respect slot elements
     4        https://bugs.webkit.org/show_bug.cgi?id=151379
     5
     6        Reviewed by Antti Koivisto.
     7
     8        Added regression tests for moving focus by tab and shift+tab across
     9        user-defined shadow trees with slots and details element.
     10
     11        * fast/shadow-dom/focus-across-details-element-expected.txt: Added.
     12        * fast/shadow-dom/focus-across-details-element.html: Added.
     13        * fast/shadow-dom/focus-navigation-across-slots-expected.txt: Added.
     14        * fast/shadow-dom/focus-navigation-across-slots.html: Added.
     15
    1162016-05-16  Ryan Haddad  <ryanhaddad@apple.com>
    217
  • trunk/LayoutTests/platform/ios-simulator/TestExpectations

    r200924 r200964  
    257257
    258258# No tab navigation support on iOS
     259fast/shadow-dom/focus-across-details-element.html [ Failure ]
     260fast/shadow-dom/focus-navigation-across-slots.html [ Failure ]
    259261fast/shadow-dom/focus-on-iframe.html [ Failure ]
    260262fast/shadow-dom/negative-tabindex-on-shadow-host.html [ Failure ]
  • trunk/Source/WebCore/ChangeLog

    r200963 r200964  
     12016-05-16  Ryosuke Niwa  <rniwa@webkit.org>
     2
     3        Focus ordering should respect slot elements
     4        https://bugs.webkit.org/show_bug.cgi?id=151379
     5
     6        Reviewed by Antti Koivisto.
     7
     8        Implemented the sequential focus navigation ordering as discussed on
     9        https://github.com/w3c/webcomponents/issues/375
     10
     11        New behavior treats each shadow root and slot as a "focus scope". The focus navigation ordering
     12        is defined within each "focus scope" using tabindex, treating any "focus scope owner"
     13        (e.g. shadow host or a slot) as if it was having tabindex=0 if it wasn't itself focusable.
     14
     15        This patch modifies FocusNavigationScope to support a focus scope defined for a slot element in
     16        addition to the one defined for a shadow tree and a document as previously supported.
     17
     18        Tests: fast/shadow-dom/focus-across-details-element.html
     19               fast/shadow-dom/focus-navigation-across-slots.html
     20
     21        * dom/Node.cpp:
     22        (WebCore::parentShadowRoot): Extracted from assignedSlot.
     23        (WebCore::Node::assignedSlot):
     24        (WebCore::Node::assignedSlotForBindings): Added.
     25        * dom/Node.h:
     26        * dom/NonDocumentTypeChildNode.idl:
     27        * html/HTMLDetailsElement.h:
     28        (HTMLDetailsElement::hasCustomFocusLogic): Added. Don't treat details element as a "focus scope".
     29        * html/HTMLSummaryElement.h:
     30        (HTMLSummaryElement::hasCustomFocusLogic): Ditto for summary element.
     31        * page/FocusController.cpp:
     32        (WebCore::hasCustomFocusLogic): Moved.
     33        (WebCore::isFocusScopeOwner): Added. Returns true on a shadow host without a custom focus logic or
     34        on a slot inside a shadow tree whose shadow host doesn't have a custom focus logic.
     35        (WebCore::FocusNavigationScope::firstChildInScope): Now takes a reference. Call isFocusScopeOwner
     36        to check for both slots and shadow roots instead of just the latter. This fixes a subtle bug that
     37        focus may never get out of textarea in some cases due to its failure to check hasCustomFocusLogic.
     38        (WebCore::FocusNavigationScope::lastChildInScope): Ditto.
     39        (WebCore::FocusNavigationScope::parentInScope): Made this a member function since it needs to check
     40        against m_slotElement inside the focus scope of a slot.
     41        (WebCore::FocusNavigationScope::nextSiblingInScope): Added. Finds the next assigned node in a slot
     42        in the focus scope defined for a slot. Just calls nextSibling() in the focus scope for shadow tree
     43        and document.
     44        (WebCore::FocusNavigationScope::previousSiblingInScope): Ditto for finding the previous sibling.
     45        (WebCore::FocusNavigationScope::firstNodeInScope): Added. This function replaces rootNode() which
     46        doesn't exist for the focus scope of a slot element.
     47        (WebCore::FocusNavigationScope::lastNodeInScope): Ditto for the last node.
     48        (WebCore::FocusNavigationScope::nextInScope):
     49        (WebCore::FocusNavigationScope::previousInScope):
     50        (WebCore::FocusNavigationScope::FocusNavigationScope): Added a variant that takes HTMLSlotElement.
     51        (WebCore::FocusNavigationScope::owner): Added the support for slot elements.
     52        (WebCore::FocusNavigationScope::scopeOf): Ditto.
     53        (WebCore::FocusNavigationScope::scopeOwnedByScopeOwner): Ditto.
     54        (WebCore::isFocusableElementOrScopeOwner): Added the support for slot elements and renamed from
     55        isFocusableOrHasShadowTreeWithoutCustomFocusLogic.
     56        (WebCore::isNonFocusableScopeOwner): Ditto. Renamed from isNonFocusableShadowHost.
     57        (WebCore::isFocusableScopeOwner): Ditto. Renamed from isFocusableShadowHost.
     58        (WebCore::shadowAdjustedTabIndex): Added the support for slot elements.
     59        (WebCore::FocusController::findFocusableElementAcrossFocusScope):
     60        (WebCore::FocusController::nextFocusableElementWithinScope):
     61        (WebCore::FocusController::previousFocusableElementWithinScope):
     62        (WebCore::FocusController::findElementWithExactTabIndex):
     63        (WebCore::nextElementWithGreaterTabIndex): Call firstNodeInScope() instead of rootNode() here since
     64        there is no root node for the focus scope defined for a slot element.
     65        (WebCore::previousElementWithLowerTabIndex): Ditto for scope.lastNodeInScope().
     66        (WebCore::FocusController::nextFocusableElementOrScopeOwner):
     67        (WebCore::FocusController::previousFocusableElementOrScopeOwner):
     68        (WebCore::parentInScope): Deleted.
     69        (WebCore::FocusNavigationScope::rootNode): Deleted.
     70        (WebCore::FocusNavigationScope::scopeOwnedByShadowHost): Deleted.
     71        (WebCore::isNonFocusableShadowHost): Deleted.
     72        (WebCore::isFocusableShadowHost): Deleted.
     73        (WebCore::isFocusableOrHasShadowTreeWithoutCustomFocusLogic): Deleted.
     74
    1752016-05-16  Chris Dumez  <cdumez@apple.com>
    276
  • trunk/Source/WebCore/dom/Node.cpp

    r200923 r200964  
    11241124}
    11251125
    1126 #if ENABLE(SHADOW_DOM)
     1126#if ENABLE(SHADOW_DOM) || ENABLE(DETAILS_ELEMENT)
     1127static inline ShadowRoot* parentShadowRoot(const Node& node)
     1128{
     1129    if (auto* parent = node.parentElement())
     1130        return parent->shadowRoot();
     1131    return nullptr;
     1132}
     1133
    11271134HTMLSlotElement* Node::assignedSlot() const
    11281135{
    1129     auto* parent = parentElement();
    1130     if (!parent)
    1131         return nullptr;
    1132 
    1133     auto* shadowRoot = parent->shadowRoot();
    1134     if (!shadowRoot || shadowRoot->type() != ShadowRoot::Type::Open)
    1135         return nullptr;
    1136 
    1137     return shadowRoot->findAssignedSlot(*this);
     1136    if (auto* shadowRoot = parentShadowRoot(*this))
     1137        return shadowRoot->findAssignedSlot(*this);
     1138    return nullptr;
     1139}
     1140
     1141HTMLSlotElement* Node::assignedSlotForBindings() const
     1142{
     1143    auto* shadowRoot = parentShadowRoot(*this);
     1144    if (shadowRoot && shadowRoot->type() == ShadowRoot::Type::Open)
     1145        return shadowRoot->findAssignedSlot(*this);
     1146    return nullptr;
    11381147}
    11391148#endif
     
    11431152    ASSERT(isMainThreadOrGCThread());
    11441153#if ENABLE(SHADOW_DOM) || ENABLE(DETAILS_ELEMENT)
    1145     if (auto* parent = parentElement()) {
    1146         if (auto* shadowRoot = parent->shadowRoot()) {
    1147             if (auto* assignedSlot = shadowRoot->findAssignedSlot(*this))
    1148                 return assignedSlot;
    1149         }
    1150     }
     1154    if (auto* slot = assignedSlot())
     1155        return slot;
    11511156#endif
    11521157    if (is<ShadowRoot>(*this))
  • trunk/Source/WebCore/dom/Node.h

    r200923 r200964  
    262262    bool isUnclosedNode(const Node&) const;
    263263
    264 #if ENABLE(SHADOW_DOM)
     264#if ENABLE(SHADOW_DOM) || ENABLE(DETAILS_ELEMENT)
    265265    HTMLSlotElement* assignedSlot() const;
     266    HTMLSlotElement* assignedSlotForBindings() const;
    266267#endif
    267268
  • trunk/Source/WebCore/dom/NonDocumentTypeChildNode.idl

    r197921 r200964  
    3333
    3434#if defined(LANGUAGE_JAVASCRIPT) && LANGUAGE_JAVASCRIPT
    35     [Conditional=SHADOW_DOM, EnabledAtRuntime=ShadowDOM] readonly attribute HTMLSlotElement assignedSlot;
     35    [Conditional=SHADOW_DOM, EnabledAtRuntime=ShadowDOM, ImplementedAs=assignedSlotForBindings] readonly attribute HTMLSlotElement assignedSlot;
    3636#endif
    3737};
  • trunk/Source/WebCore/html/HTMLDetailsElement.h

    r200041 r200964  
    4444    void didAddUserAgentShadowRoot(ShadowRoot*) override;
    4545    bool canHaveUserAgentShadowRoot() const final { return true; }
     46    bool hasCustomFocusLogic() const final { return true; }
    4647
    4748    bool m_isOpen { false };
  • trunk/Source/WebCore/html/HTMLSummaryElement.h

    r200041 r200964  
    4545    // FIXME: Shadow DOM spec says we should be able to create shadow root on this element
    4646    bool canHaveUserAgentShadowRoot() const final { return true; }
     47    bool hasCustomFocusLogic() const final { return true; }
    4748
    4849    HTMLDetailsElement* detailsElement() const;
  • trunk/Source/WebCore/page/FocusController.cpp

    r200712 r200964  
    4747#include "HTMLNames.h"
    4848#include "HTMLPlugInElement.h"
     49#include "HTMLSlotElement.h"
    4950#include "HTMLTextAreaElement.h"
    5051#include "HitTestResult.h"
     
    6869using namespace HTMLNames;
    6970
     71static inline bool hasCustomFocusLogic(const Element& element)
     72{
     73    return is<HTMLElement>(element) && downcast<HTMLElement>(element).hasCustomFocusLogic();
     74}
     75
     76static inline bool isFocusScopeOwner(const Element& element)
     77{
     78    if (element.shadowRoot() && !hasCustomFocusLogic(element))
     79        return true;
     80#if ENABLE(SHADOW_DOM) || ENABLE(DETAILS_ELEMENT)
     81    if (is<HTMLSlotElement>(element) && downcast<HTMLSlotElement>(element).assignedNodes()) {
     82        ShadowRoot* root = element.containingShadowRoot();
     83        if (root && root->host() && !hasCustomFocusLogic(*root->host()))
     84            return true;
     85    }
     86#endif
     87    return false;
     88}
     89
    7090class FocusNavigationScope {
    7191public:
    72     ContainerNode& rootNode() const;
    7392    Element* owner() const;
    7493    WEBCORE_EXPORT static FocusNavigationScope scopeOf(Node&);
    75     static FocusNavigationScope scopeOwnedByShadowHost(Element&);
     94    static FocusNavigationScope scopeOwnedByScopeOwner(Element&);
    7695    static FocusNavigationScope scopeOwnedByIFrame(HTMLFrameOwnerElement&);
    7796
     97    Node* firstNodeInScope() const;
     98    Node* lastNodeInScope() const;
    7899    Node* nextInScope(const Node*) const;
    79100    Node* previousInScope(const Node*) const;
    80     Node* lastChildInScope(const Node*) const;
     101    Node* lastChildInScope(const Node&) const;
    81102
    82103private:
    83     Node* firstChildInScope(const Node*) const;
     104    Node* firstChildInScope(const Node&) const;
     105
     106    Node* parentInScope(const Node&) const;
     107
     108    Node* nextSiblingInScope(const Node&) const;
     109    Node* previousSiblingInScope(const Node&) const;
    84110
    85111    explicit FocusNavigationScope(TreeScope&);
    86     TreeScope& m_rootTreeScope;
     112
     113#if ENABLE(SHADOW_DOM) || ENABLE(DETAILS_ELEMENT)
     114    explicit FocusNavigationScope(HTMLSlotElement&);
     115#endif
     116
     117    TreeScope* m_rootTreeScope { nullptr };
     118#if ENABLE(SHADOW_DOM) || ENABLE(DETAILS_ELEMENT)
     119    HTMLSlotElement* m_slotElement { nullptr };
     120#endif
    87121};
    88122
    89123// FIXME: Focus navigation should work with shadow trees that have slots.
    90 Node* FocusNavigationScope::firstChildInScope(const Node* node) const
     124Node* FocusNavigationScope::firstChildInScope(const Node& node) const
     125{
     126    if (is<Element>(node) && isFocusScopeOwner(downcast<Element>(node)))
     127        return nullptr;
     128    return node.firstChild();
     129}
     130
     131Node* FocusNavigationScope::lastChildInScope(const Node& node) const
     132{
     133    if (is<Element>(node) && isFocusScopeOwner(downcast<Element>(node)))
     134        return nullptr;
     135    return node.lastChild();
     136}
     137
     138Node* FocusNavigationScope::parentInScope(const Node& node) const
     139{
     140    if (is<Element>(node) && isFocusScopeOwner(downcast<Element>(node)))
     141        return nullptr;
     142
     143#if ENABLE(SHADOW_DOM) || ENABLE(DETAILS_ELEMENT)
     144    if (UNLIKELY(m_slotElement && m_slotElement == node.assignedSlot()))
     145        return nullptr;
     146#endif
     147
     148    ContainerNode* parent = node.parentNode();
     149    if (parent && is<Element>(parent) && isFocusScopeOwner(downcast<Element>(*parent)))
     150        return nullptr;
     151
     152    return parent;
     153}
     154
     155Node* FocusNavigationScope::nextSiblingInScope(const Node& node) const
     156{
     157#if ENABLE(SHADOW_DOM) || ENABLE(DETAILS_ELEMENT)
     158    if (UNLIKELY(m_slotElement && m_slotElement == node.assignedSlot())) {
     159        for (Node* current = node.nextSibling(); current; current = current->nextSibling()) {
     160            if (current->assignedSlot() == m_slotElement)
     161                return current;
     162        }
     163        return nullptr;
     164    }
     165#endif
     166    return node.nextSibling();
     167}
     168
     169Node* FocusNavigationScope::previousSiblingInScope(const Node& node) const
     170{
     171#if ENABLE(SHADOW_DOM) || ENABLE(DETAILS_ELEMENT)
     172    if (UNLIKELY(m_slotElement && m_slotElement == node.assignedSlot())) {
     173        for (Node* current = node.previousSibling(); current; current = current->previousSibling()) {
     174            if (current->assignedSlot() == m_slotElement)
     175                return current;
     176        }
     177        return nullptr;
     178    }
     179#endif
     180    return node.previousSibling();
     181}
     182
     183Node* FocusNavigationScope::firstNodeInScope() const
     184{
     185#if ENABLE(SHADOW_DOM) || ENABLE(DETAILS_ELEMENT)
     186    if (UNLIKELY(m_slotElement)) {
     187        auto* assigneNodes = m_slotElement->assignedNodes();
     188        ASSERT(assigneNodes);
     189        return assigneNodes->first();
     190    }
     191#endif
     192    ASSERT(m_rootTreeScope);
     193    return &m_rootTreeScope->rootNode();
     194}
     195
     196Node* FocusNavigationScope::lastNodeInScope() const
     197{
     198#if ENABLE(SHADOW_DOM) || ENABLE(DETAILS_ELEMENT)
     199    if (UNLIKELY(m_slotElement)) {
     200        auto* assigneNodes = m_slotElement->assignedNodes();
     201        ASSERT(assigneNodes);
     202        return assigneNodes->last();
     203    }
     204#endif
     205    ASSERT(m_rootTreeScope);
     206    return &m_rootTreeScope->rootNode();
     207}
     208
     209Node* FocusNavigationScope::nextInScope(const Node* node) const
    91210{
    92211    ASSERT(node);
    93     if (node->shadowRoot())
    94         return nullptr;
    95     return node->firstChild();
    96 }
    97 
    98 Node* FocusNavigationScope::lastChildInScope(const Node* node) const
    99 {
    100     ASSERT(node);
    101     if (node->shadowRoot())
    102         return nullptr;
    103     return node->lastChild();
    104 }
    105 
    106 static Node* parentInScope(const Node* node)
    107 {
    108     if (node->isShadowRoot())
    109         return nullptr;
    110 
    111     ContainerNode* parent = node->parentNode();
    112     if (parent && parent->shadowRoot())
    113         return nullptr;
    114 
    115     return parent;
    116 }
    117 
    118 Node* FocusNavigationScope::nextInScope(const Node* node) const
    119 {
    120     if (Node* next = firstChildInScope(node))
     212    if (Node* next = firstChildInScope(*node))
    121213        return next;
    122     if (Node* next = node->nextSibling())
     214    if (Node* next = nextSiblingInScope(*node))
    123215        return next;
    124216    const Node* current = node;
    125     while (current && !current->nextSibling())
    126         current = parentInScope(current);
    127     return current ? current->nextSibling() : nullptr;
     217    while (current && !nextSiblingInScope(*current))
     218        current = parentInScope(*current);
     219    return current ? nextSiblingInScope(*current) : nullptr;
    128220}
    129221
    130222Node* FocusNavigationScope::previousInScope(const Node* node) const
    131223{
    132     if (Node* current = node->previousSibling()) {
    133         while (Node* child = lastChildInScope(current))
     224    ASSERT(node);
     225    if (Node* current = previousSiblingInScope(*node)) {
     226        while (Node* child = lastChildInScope(*current))
    134227            current = child;
    135228        return current;
    136229    }
    137     return parentInScope(node);
     230    return parentInScope(*node);
    138231}
    139232
    140233FocusNavigationScope::FocusNavigationScope(TreeScope& treeScope)
    141     : m_rootTreeScope(treeScope)
    142 {
    143 }
    144 
    145 ContainerNode& FocusNavigationScope::rootNode() const
    146 {
    147     return m_rootTreeScope.rootNode();
    148 }
     234    : m_rootTreeScope(&treeScope)
     235{
     236}
     237
     238#if ENABLE(SHADOW_DOM) || ENABLE(DETAILS_ELEMENT)
     239FocusNavigationScope::FocusNavigationScope(HTMLSlotElement& slotElement)
     240    : m_slotElement(&slotElement)
     241{
     242}
     243#endif
    149244
    150245Element* FocusNavigationScope::owner() const
    151246{
    152     ContainerNode& root = rootNode();
     247#if ENABLE(SHADOW_DOM) || ENABLE(DETAILS_ELEMENT)
     248    if (m_slotElement)
     249        return m_slotElement;
     250#endif
     251
     252    ASSERT(m_rootTreeScope);
     253    ContainerNode& root = m_rootTreeScope->rootNode();
    153254    if (is<ShadowRoot>(root))
    154255        return downcast<ShadowRoot>(root).host();
     
    160261FocusNavigationScope FocusNavigationScope::scopeOf(Node& startingNode)
    161262{
     263    ASSERT(startingNode.isInTreeScope());
    162264    Node* root = nullptr;
    163     for (Node* currentNode = &startingNode; currentNode; currentNode = parentInScope(currentNode))
     265    for (Node* currentNode = &startingNode; currentNode; currentNode = currentNode->parentNode()) {
    164266        root = currentNode;
    165     // The result is not always a ShadowRoot nor a DocumentNode since
    166     // a starting node is in an orphaned tree in composed shadow tree.
     267#if ENABLE(SHADOW_DOM) || ENABLE(DETAILS_ELEMENT)
     268        if (HTMLSlotElement* slot = currentNode->assignedSlot()) {
     269            if (isFocusScopeOwner(*slot))
     270                return FocusNavigationScope(*slot);
     271        }
     272#endif
     273        if (is<ShadowRoot>(currentNode))
     274            return FocusNavigationScope(downcast<ShadowRoot>(*currentNode));
     275    }
     276    ASSERT(root);
    167277    return FocusNavigationScope(root->treeScope());
    168278}
    169279
    170 FocusNavigationScope FocusNavigationScope::scopeOwnedByShadowHost(Element& element)
    171 {
    172     ASSERT(element.shadowRoot());
     280FocusNavigationScope FocusNavigationScope::scopeOwnedByScopeOwner(Element& element)
     281{
     282#if ENABLE(SHADOW_DOM) || ENABLE(DETAILS_ELEMENT)
     283    ASSERT(element.shadowRoot() || is<HTMLSlotElement>(element));
     284    if (is<HTMLSlotElement>(element))
     285        return FocusNavigationScope(downcast<HTMLSlotElement>(element));
     286#endif
    173287    return FocusNavigationScope(*element.shadowRoot());
    174288}
     
    200314}
    201315
    202 static inline bool hasCustomFocusLogic(Element& element)
    203 {
    204     return is<HTMLElement>(element) && downcast<HTMLElement>(element).hasCustomFocusLogic();
    205 }
    206 
    207 static inline bool isNonFocusableShadowHost(Element& element, KeyboardEvent& event)
    208 {
    209     return !element.isKeyboardFocusable(&event) && element.shadowRoot() && !hasCustomFocusLogic(element);
    210 }
    211 
    212 static inline bool isFocusableShadowHost(Node& node, KeyboardEvent& event)
    213 {
    214     return is<Element>(node) && downcast<Element>(node).isKeyboardFocusable(&event) && downcast<Element>(node).shadowRoot() && !hasCustomFocusLogic(downcast<Element>(node));
     316static inline bool isFocusableElementOrScopeOwner(Element& element, KeyboardEvent& event)
     317{
     318    return element.isKeyboardFocusable(&event) || isFocusScopeOwner(element);
     319}
     320
     321static inline bool isNonFocusableScopeOwner(Element& element, KeyboardEvent& event)
     322{
     323    return !element.isKeyboardFocusable(&event) && isFocusScopeOwner(element);
     324}
     325
     326static inline bool isFocusableScopeOwner(Element& element, KeyboardEvent& event)
     327{
     328    return element.isKeyboardFocusable(&event) && isFocusScopeOwner(element);
    215329}
    216330
    217331static inline int shadowAdjustedTabIndex(Element& element, KeyboardEvent& event)
    218332{
    219     if (isNonFocusableShadowHost(element, event)) {
     333    if (isNonFocusableScopeOwner(element, event)) {
    220334        if (!element.tabIndexSetExplicitly())
    221335            return 0; // Treat a shadow host without tabindex if it has tabindex=0 even though HTMLElement::tabIndex returns -1 on such an element.
    222336    }
    223337    return element.tabIndex();
    224 }
    225 
    226 static inline bool isFocusableOrHasShadowTreeWithoutCustomFocusLogic(Element& element, KeyboardEvent& event)
    227 {
    228     return element.isKeyboardFocusable(&event) || isNonFocusableShadowHost(element, event);
    229338}
    230339
     
    420529Element* FocusController::findFocusableElementAcrossFocusScope(FocusDirection direction, const FocusNavigationScope& scope, Node* currentNode, KeyboardEvent* event)
    421530{
    422     ASSERT(!is<Element>(currentNode) || !isNonFocusableShadowHost(*downcast<Element>(currentNode), *event));
    423 
    424     if (currentNode && direction == FocusDirectionForward && isFocusableShadowHost(*currentNode, *event)) {
    425         if (Element* candidateInInnerScope = findFocusableElementWithinScope(direction, FocusNavigationScope::scopeOwnedByShadowHost(downcast<Element>(*currentNode)), 0, event))
     531    ASSERT(!is<Element>(currentNode) || !isNonFocusableScopeOwner(downcast<Element>(*currentNode), *event));
     532
     533    if (currentNode && direction == FocusDirectionForward && is<Element>(currentNode) && isFocusableScopeOwner(downcast<Element>(*currentNode), *event)) {
     534        if (Element* candidateInInnerScope = findFocusableElementWithinScope(direction, FocusNavigationScope::scopeOwnedByScopeOwner(downcast<Element>(*currentNode)), 0, event))
    426535            return candidateInInnerScope;
    427536    }
     
    433542    Element* owner = scope.owner();
    434543    while (owner) {
    435         if (direction == FocusDirectionBackward && isFocusableShadowHost(*owner, *event))
     544        if (direction == FocusDirectionBackward && isFocusableScopeOwner(*owner, *event))
    436545            return findFocusableElementDescendingDownIntoFrameDocument(direction, owner, event);
    437546
     
    458567    if (!found)
    459568        return nullptr;
    460     if (isNonFocusableShadowHost(*found, *event)) {
    461         if (Element* foundInInnerFocusScope = nextFocusableElementWithinScope(FocusNavigationScope::scopeOwnedByShadowHost(*found), 0, event))
     569    if (isNonFocusableScopeOwner(*found, *event)) {
     570        if (Element* foundInInnerFocusScope = nextFocusableElementWithinScope(FocusNavigationScope::scopeOwnedByScopeOwner(*found), 0, event))
    462571            return foundInInnerFocusScope;
    463572        return nextFocusableElementWithinScope(scope, found, event);
     
    471580    if (!found)
    472581        return nullptr;
    473     if (isFocusableShadowHost(*found, *event)) {
     582    if (isFocusableScopeOwner(*found, *event)) {
    474583        // Search an inner focusable element in the shadow tree from the end.
    475         if (Element* foundInInnerFocusScope = previousFocusableElementWithinScope(FocusNavigationScope::scopeOwnedByShadowHost(*found), 0, event))
     584        if (Element* foundInInnerFocusScope = previousFocusableElementWithinScope(FocusNavigationScope::scopeOwnedByScopeOwner(*found), 0, event))
    476585            return foundInInnerFocusScope;
    477586        return found;
    478587    }
    479     if (isNonFocusableShadowHost(*found, *event)) {
    480         if (Element* foundInInnerFocusScope = previousFocusableElementWithinScope(FocusNavigationScope::scopeOwnedByShadowHost(*found), 0, event))
     588    if (isNonFocusableScopeOwner(*found, *event)) {
     589        if (Element* foundInInnerFocusScope = previousFocusableElementWithinScope(FocusNavigationScope::scopeOwnedByScopeOwner(*found), 0, event))
    481590            return foundInInnerFocusScope;
    482591        return previousFocusableElementWithinScope(scope, found, event);
     
    499608            continue;
    500609        Element& element = downcast<Element>(*node);
    501         if (isFocusableOrHasShadowTreeWithoutCustomFocusLogic(element, *event) && shadowAdjustedTabIndex(element, *event) == tabIndex)
     610        if (isFocusableElementOrScopeOwner(element, *event) && shadowAdjustedTabIndex(element, *event) == tabIndex)
    502611            return &element;
    503612    }
     
    510619    int winningTabIndex = std::numeric_limits<int>::max();
    511620    Element* winner = nullptr;
    512     for (Node* node = &scope.rootNode(); node; node = scope.nextInScope(node)) {
     621    for (Node* node = scope.firstNodeInScope(); node; node = scope.nextInScope(node)) {
    513622        if (!is<Element>(*node))
    514623            continue;
    515624        Element& candidate = downcast<Element>(*node);
    516625        int candidateTabIndex = candidate.tabIndex();
    517         if (isFocusableOrHasShadowTreeWithoutCustomFocusLogic(candidate, event) && candidateTabIndex > tabIndex && (!winner || candidateTabIndex < winningTabIndex)) {
     626        if (isFocusableElementOrScopeOwner(candidate, event) && candidateTabIndex > tabIndex && (!winner || candidateTabIndex < winningTabIndex)) {
    518627            winner = &candidate;
    519628            winningTabIndex = candidateTabIndex;
     
    534643        Element& element = downcast<Element>(*node);
    535644        int currentTabIndex = shadowAdjustedTabIndex(element, event);
    536         if (isFocusableOrHasShadowTreeWithoutCustomFocusLogic(element, event) && currentTabIndex < tabIndex && currentTabIndex > winningTabIndex) {
     645        if (isFocusableElementOrScopeOwner(element, event) && currentTabIndex < tabIndex && currentTabIndex > winningTabIndex) {
    537646            winner = &element;
    538647            winningTabIndex = currentTabIndex;
     
    569678                    continue;
    570679                Element& element = downcast<Element>(*node);
    571                 if (isFocusableOrHasShadowTreeWithoutCustomFocusLogic(element, *event) && shadowAdjustedTabIndex(element, *event) >= 0)
     680                if (isFocusableElementOrScopeOwner(element, *event) && shadowAdjustedTabIndex(element, *event) >= 0)
    572681                    return &element;
    573682            }
     
    590699    // There are no nodes with a tabindex greater than start's tabindex,
    591700    // so find the first node with a tabindex of 0.
    592     return findElementWithExactTabIndex(scope, &scope.rootNode(), 0, event, FocusDirectionForward);
     701    return findElementWithExactTabIndex(scope, scope.firstNodeInScope(), 0, event, FocusDirectionForward);
    593702}
    594703
     
    596705{
    597706    Node* last = nullptr;
    598     for (Node* node = &scope.rootNode(); node; node = scope.lastChildInScope(node))
     707    for (Node* node = scope.lastNodeInScope(); node; node = scope.lastChildInScope(*node))
    599708        last = node;
    600709    ASSERT(last);
     
    617726                continue;
    618727            Element& element = downcast<Element>(*node);
    619             if (isFocusableOrHasShadowTreeWithoutCustomFocusLogic(element, *event) && shadowAdjustedTabIndex(element, *event) >= 0)
     728            if (isFocusableElementOrScopeOwner(element, *event) && shadowAdjustedTabIndex(element, *event) >= 0)
    620729                return &element;
    621730        }
Note: See TracChangeset for help on using the changeset viewer.