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
- trunk/LayoutTests/ChangeLog
- trunk/LayoutTests/platform/ios-simulator/TestExpectations
- trunk/Source/WebCore/ChangeLog
- trunk/Source/WebCore/dom/Node.cpp
- trunk/Source/WebCore/dom/Node.h
- trunk/Source/WebCore/dom/NonDocumentTypeChildNode.idl
- trunk/Source/WebCore/html/HTMLDetailsElement.h
- trunk/Source/WebCore/html/HTMLSummaryElement.h
- trunk/Source/WebCore/page/FocusController.cpp
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