<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head><meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>[200964] trunk</title>
</head>
<body>
<style type="text/css"><!--
#msg dl.meta { border: 1px #006 solid; background: #369; padding: 6px; color: #fff; }
#msg dl.meta dt { float: left; width: 6em; font-weight: bold; }
#msg dt:after { content:':';}
#msg dl, #msg dt, #msg ul, #msg li, #header, #footer, #logmsg { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; }
#msg dl a { font-weight: bold}
#msg dl a:link { color:#fc3; }
#msg dl a:active { color:#ff0; }
#msg dl a:visited { color:#cc6; }
h3 { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; font-weight: bold; }
#msg pre { overflow: auto; background: #ffc; border: 1px #fa0 solid; padding: 6px; }
#logmsg { background: #ffc; border: 1px #fa0 solid; padding: 1em 1em 0 1em; }
#logmsg p, #logmsg pre, #logmsg blockquote { margin: 0 0 1em 0; }
#logmsg p, #logmsg li, #logmsg dt, #logmsg dd { line-height: 14pt; }
#logmsg h1, #logmsg h2, #logmsg h3, #logmsg h4, #logmsg h5, #logmsg h6 { margin: .5em 0; }
#logmsg h1:first-child, #logmsg h2:first-child, #logmsg h3:first-child, #logmsg h4:first-child, #logmsg h5:first-child, #logmsg h6:first-child { margin-top: 0; }
#logmsg ul, #logmsg ol { padding: 0; list-style-position: inside; margin: 0 0 0 1em; }
#logmsg ul { text-indent: -1em; padding-left: 1em; }#logmsg ol { text-indent: -1.5em; padding-left: 1.5em; }
#logmsg > ul, #logmsg > ol { margin: 0 0 1em 0; }
#logmsg pre { background: #eee; padding: 1em; }
#logmsg blockquote { border: 1px solid #fa0; border-left-width: 10px; padding: 1em 1em 0 1em; background: white;}
#logmsg dl { margin: 0; }
#logmsg dt { font-weight: bold; }
#logmsg dd { margin: 0; padding: 0 0 0.5em 0; }
#logmsg dd:before { content:'\00bb';}
#logmsg table { border-spacing: 0px; border-collapse: collapse; border-top: 4px solid #fa0; border-bottom: 1px solid #fa0; background: #fff; }
#logmsg table th { text-align: left; font-weight: normal; padding: 0.2em 0.5em; border-top: 1px dotted #fa0; }
#logmsg table td { text-align: right; border-top: 1px dotted #fa0; padding: 0.2em 0.5em; }
#logmsg table thead th { text-align: center; border-bottom: 1px solid #fa0; }
#logmsg table th.Corner { text-align: left; }
#logmsg hr { border: none 0; border-top: 2px dashed #fa0; height: 1px; }
#header, #footer { color: #fff; background: #636; border: 1px #300 solid; padding: 6px; }
#patch { width: 100%; }
#patch h4 {font-family: verdana,arial,helvetica,sans-serif;font-size:10pt;padding:8px;background:#369;color:#fff;margin:0;}
#patch .propset h4, #patch .binary h4 {margin:0;}
#patch pre {padding:0;line-height:1.2em;margin:0;}
#patch .diff {width:100%;background:#eee;padding: 0 0 10px 0;overflow:auto;}
#patch .propset .diff, #patch .binary .diff {padding:10px 0;}
#patch span {display:block;padding:0 10px;}
#patch .modfile, #patch .addfile, #patch .delfile, #patch .propset, #patch .binary, #patch .copfile {border:1px solid #ccc;margin:10px 0;}
#patch ins {background:#dfd;text-decoration:none;display:block;padding:0 10px;}
#patch del {background:#fdd;text-decoration:none;display:block;padding:0 10px;}
#patch .lines, .info {color:#888;background:#fff;}
--></style>
<div id="msg">
<dl class="meta">
<dt>Revision</dt> <dd><a href="http://trac.webkit.org/projects/webkit/changeset/200964">200964</a></dd>
<dt>Author</dt> <dd>rniwa@webkit.org</dd>
<dt>Date</dt> <dd>2016-05-16 13:26:40 -0700 (Mon, 16 May 2016)</dd>
</dl>
<h3>Log Message</h3>
<pre>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.</pre>
<h3>Modified Paths</h3>
<ul>
<li><a href="#trunkLayoutTestsChangeLog">trunk/LayoutTests/ChangeLog</a></li>
<li><a href="#trunkLayoutTestsplatformiossimulatorTestExpectations">trunk/LayoutTests/platform/ios-simulator/TestExpectations</a></li>
<li><a href="#trunkSourceWebCoreChangeLog">trunk/Source/WebCore/ChangeLog</a></li>
<li><a href="#trunkSourceWebCoredomNodecpp">trunk/Source/WebCore/dom/Node.cpp</a></li>
<li><a href="#trunkSourceWebCoredomNodeh">trunk/Source/WebCore/dom/Node.h</a></li>
<li><a href="#trunkSourceWebCoredomNonDocumentTypeChildNodeidl">trunk/Source/WebCore/dom/NonDocumentTypeChildNode.idl</a></li>
<li><a href="#trunkSourceWebCorehtmlHTMLDetailsElementh">trunk/Source/WebCore/html/HTMLDetailsElement.h</a></li>
<li><a href="#trunkSourceWebCorehtmlHTMLSummaryElementh">trunk/Source/WebCore/html/HTMLSummaryElement.h</a></li>
<li><a href="#trunkSourceWebCorepageFocusControllercpp">trunk/Source/WebCore/page/FocusController.cpp</a></li>
</ul>
<h3>Added Paths</h3>
<ul>
<li><a href="#trunkLayoutTestsfastshadowdomfocusacrossdetailselementexpectedtxt">trunk/LayoutTests/fast/shadow-dom/focus-across-details-element-expected.txt</a></li>
<li><a href="#trunkLayoutTestsfastshadowdomfocusacrossdetailselementhtml">trunk/LayoutTests/fast/shadow-dom/focus-across-details-element.html</a></li>
<li><a href="#trunkLayoutTestsfastshadowdomfocusnavigationacrossslotsexpectedtxt">trunk/LayoutTests/fast/shadow-dom/focus-navigation-across-slots-expected.txt</a></li>
<li><a href="#trunkLayoutTestsfastshadowdomfocusnavigationacrossslotshtml">trunk/LayoutTests/fast/shadow-dom/focus-navigation-across-slots.html</a></li>
</ul>
</div>
<div id="patch">
<h3>Diff</h3>
<a id="trunkLayoutTestsChangeLog"></a>
<div class="modfile"><h4>Modified: trunk/LayoutTests/ChangeLog (200963 => 200964)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/LayoutTests/ChangeLog        2016-05-16 20:24:52 UTC (rev 200963)
+++ trunk/LayoutTests/ChangeLog        2016-05-16 20:26:40 UTC (rev 200964)
</span><span class="lines">@@ -1,3 +1,18 @@
</span><ins>+2016-05-16 Ryosuke Niwa <rniwa@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.
+
</ins><span class="cx"> 2016-05-16 Ryan Haddad <ryanhaddad@apple.com>
</span><span class="cx">
</span><span class="cx"> Rebaseline tests for ios-simulator
</span></span></pre></div>
<a id="trunkLayoutTestsfastshadowdomfocusacrossdetailselementexpectedtxt"></a>
<div class="addfile"><h4>Added: trunk/LayoutTests/fast/shadow-dom/focus-across-details-element-expected.txt (0 => 200964)</h4>
<pre class="diff"><span>
<span class="info">--- 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)
</span><span class="lines">@@ -0,0 +1,15 @@
</span><ins>+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
+
</ins></span></pre></div>
<a id="trunkLayoutTestsfastshadowdomfocusacrossdetailselementhtml"></a>
<div class="addfile"><h4>Added: trunk/LayoutTests/fast/shadow-dom/focus-across-details-element.html (0 => 200964)</h4>
<pre class="diff"><span>
<span class="info">--- 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)
</span><span class="lines">@@ -0,0 +1,49 @@
</span><ins>+<!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>
</ins></span></pre></div>
<a id="trunkLayoutTestsfastshadowdomfocusnavigationacrossslotsexpectedtxt"></a>
<div class="addfile"><h4>Added: trunk/LayoutTests/fast/shadow-dom/focus-navigation-across-slots-expected.txt (0 => 200964)</h4>
<pre class="diff"><span>
<span class="info">--- 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)
</span><span class="lines">@@ -0,0 +1,38 @@
</span><ins>+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
+
</ins></span></pre></div>
<a id="trunkLayoutTestsfastshadowdomfocusnavigationacrossslotshtml"></a>
<div class="addfile"><h4>Added: trunk/LayoutTests/fast/shadow-dom/focus-navigation-across-slots.html (0 => 200964)</h4>
<pre class="diff"><span>
<span class="info">--- 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)
</span><span class="lines">@@ -0,0 +1,150 @@
</span><ins>+<!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>
</ins></span></pre></div>
<a id="trunkLayoutTestsplatformiossimulatorTestExpectations"></a>
<div class="modfile"><h4>Modified: trunk/LayoutTests/platform/ios-simulator/TestExpectations (200963 => 200964)</h4>
<pre class="diff"><span>
<span class="info">--- 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)
</span><span class="lines">@@ -256,6 +256,8 @@
</span><span class="cx"> webkit.org/b/148695 fast/shadow-dom [ Pass ]
</span><span class="cx">
</span><span class="cx"> # No tab navigation support on iOS
</span><ins>+fast/shadow-dom/focus-across-details-element.html [ Failure ]
+fast/shadow-dom/focus-navigation-across-slots.html [ Failure ]
</ins><span class="cx"> fast/shadow-dom/focus-on-iframe.html [ Failure ]
</span><span class="cx"> fast/shadow-dom/negative-tabindex-on-shadow-host.html [ Failure ]
</span><span class="cx">
</span></span></pre></div>
<a id="trunkSourceWebCoreChangeLog"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebCore/ChangeLog (200963 => 200964)</h4>
<pre class="diff"><span>
<span class="info">--- 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)
</span><span class="lines">@@ -1,3 +1,77 @@
</span><ins>+2016-05-16 Ryosuke Niwa <rniwa@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.
+
</ins><span class="cx"> 2016-05-16 Chris Dumez <cdumez@apple.com>
</span><span class="cx">
</span><span class="cx"> Use WTF::Optional for ScrollView's m_deferredScrollDelta / m_deferredScrollOffsets
</span></span></pre></div>
<a id="trunkSourceWebCoredomNodecpp"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebCore/dom/Node.cpp (200963 => 200964)</h4>
<pre class="diff"><span>
<span class="info">--- 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)
</span><span class="lines">@@ -1123,18 +1123,27 @@
</span><span class="cx"> return false;
</span><span class="cx"> }
</span><span class="cx">
</span><del>-#if ENABLE(SHADOW_DOM)
</del><ins>+#if ENABLE(SHADOW_DOM) || ENABLE(DETAILS_ELEMENT)
+static inline ShadowRoot* parentShadowRoot(const Node& node)
+{
+ if (auto* parent = node.parentElement())
+ return parent->shadowRoot();
+ return nullptr;
+}
+
</ins><span class="cx"> HTMLSlotElement* Node::assignedSlot() const
</span><span class="cx"> {
</span><del>- auto* parent = parentElement();
- if (!parent)
- return nullptr;
</del><ins>+ if (auto* shadowRoot = parentShadowRoot(*this))
+ return shadowRoot->findAssignedSlot(*this);
+ return nullptr;
+}
</ins><span class="cx">
</span><del>- auto* shadowRoot = parent->shadowRoot();
- if (!shadowRoot || shadowRoot->type() != ShadowRoot::Type::Open)
- return nullptr;
-
- return shadowRoot->findAssignedSlot(*this);
</del><ins>+HTMLSlotElement* Node::assignedSlotForBindings() const
+{
+ auto* shadowRoot = parentShadowRoot(*this);
+ if (shadowRoot && shadowRoot->type() == ShadowRoot::Type::Open)
+ return shadowRoot->findAssignedSlot(*this);
+ return nullptr;
</ins><span class="cx"> }
</span><span class="cx"> #endif
</span><span class="cx">
</span><span class="lines">@@ -1142,12 +1151,8 @@
</span><span class="cx"> {
</span><span class="cx"> ASSERT(isMainThreadOrGCThread());
</span><span class="cx"> #if ENABLE(SHADOW_DOM) || ENABLE(DETAILS_ELEMENT)
</span><del>- if (auto* parent = parentElement()) {
- if (auto* shadowRoot = parent->shadowRoot()) {
- if (auto* assignedSlot = shadowRoot->findAssignedSlot(*this))
- return assignedSlot;
- }
- }
</del><ins>+ if (auto* slot = assignedSlot())
+ return slot;
</ins><span class="cx"> #endif
</span><span class="cx"> if (is<ShadowRoot>(*this))
</span><span class="cx"> return downcast<ShadowRoot>(*this).host();
</span></span></pre></div>
<a id="trunkSourceWebCoredomNodeh"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebCore/dom/Node.h (200963 => 200964)</h4>
<pre class="diff"><span>
<span class="info">--- 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)
</span><span class="lines">@@ -261,8 +261,9 @@
</span><span class="cx"> ShadowRoot* shadowRoot() const;
</span><span class="cx"> bool isUnclosedNode(const Node&) const;
</span><span class="cx">
</span><del>-#if ENABLE(SHADOW_DOM)
</del><ins>+#if ENABLE(SHADOW_DOM) || ENABLE(DETAILS_ELEMENT)
</ins><span class="cx"> HTMLSlotElement* assignedSlot() const;
</span><ins>+ HTMLSlotElement* assignedSlotForBindings() const;
</ins><span class="cx"> #endif
</span><span class="cx">
</span><span class="cx"> #if ENABLE(CUSTOM_ELEMENTS)
</span></span></pre></div>
<a id="trunkSourceWebCoredomNonDocumentTypeChildNodeidl"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebCore/dom/NonDocumentTypeChildNode.idl (200963 => 200964)</h4>
<pre class="diff"><span>
<span class="info">--- 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)
</span><span class="lines">@@ -32,6 +32,6 @@
</span><span class="cx"> readonly attribute Element nextElementSibling;
</span><span class="cx">
</span><span class="cx"> #if defined(LANGUAGE_JAVASCRIPT) && LANGUAGE_JAVASCRIPT
</span><del>- [Conditional=SHADOW_DOM, EnabledAtRuntime=ShadowDOM] readonly attribute HTMLSlotElement assignedSlot;
</del><ins>+ [Conditional=SHADOW_DOM, EnabledAtRuntime=ShadowDOM, ImplementedAs=assignedSlotForBindings] readonly attribute HTMLSlotElement assignedSlot;
</ins><span class="cx"> #endif
</span><span class="cx"> };
</span></span></pre></div>
<a id="trunkSourceWebCorehtmlHTMLDetailsElementh"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebCore/html/HTMLDetailsElement.h (200963 => 200964)</h4>
<pre class="diff"><span>
<span class="info">--- 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)
</span><span class="lines">@@ -43,6 +43,7 @@
</span><span class="cx">
</span><span class="cx"> void didAddUserAgentShadowRoot(ShadowRoot*) override;
</span><span class="cx"> bool canHaveUserAgentShadowRoot() const final { return true; }
</span><ins>+ bool hasCustomFocusLogic() const final { return true; }
</ins><span class="cx">
</span><span class="cx"> bool m_isOpen { false };
</span><span class="cx"> HTMLSlotElement* m_summarySlot { nullptr };
</span></span></pre></div>
<a id="trunkSourceWebCorehtmlHTMLSummaryElementh"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebCore/html/HTMLSummaryElement.h (200963 => 200964)</h4>
<pre class="diff"><span>
<span class="info">--- 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)
</span><span class="lines">@@ -44,6 +44,7 @@
</span><span class="cx">
</span><span class="cx"> // FIXME: Shadow DOM spec says we should be able to create shadow root on this element
</span><span class="cx"> bool canHaveUserAgentShadowRoot() const final { return true; }
</span><ins>+ bool hasCustomFocusLogic() const final { return true; }
</ins><span class="cx">
</span><span class="cx"> HTMLDetailsElement* detailsElement() const;
</span><span class="cx">
</span></span></pre></div>
<a id="trunkSourceWebCorepageFocusControllercpp"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebCore/page/FocusController.cpp (200963 => 200964)</h4>
<pre class="diff"><span>
<span class="info">--- 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)
</span><span class="lines">@@ -46,6 +46,7 @@
</span><span class="cx"> #include "HTMLInputElement.h"
</span><span class="cx"> #include "HTMLNames.h"
</span><span class="cx"> #include "HTMLPlugInElement.h"
</span><ins>+#include "HTMLSlotElement.h"
</ins><span class="cx"> #include "HTMLTextAreaElement.h"
</span><span class="cx"> #include "HitTestResult.h"
</span><span class="cx"> #include "KeyboardEvent.h"
</span><span class="lines">@@ -67,89 +68,189 @@
</span><span class="cx">
</span><span class="cx"> using namespace HTMLNames;
</span><span class="cx">
</span><ins>+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;
+}
+
</ins><span class="cx"> class FocusNavigationScope {
</span><span class="cx"> public:
</span><del>- ContainerNode& rootNode() const;
</del><span class="cx"> Element* owner() const;
</span><span class="cx"> WEBCORE_EXPORT static FocusNavigationScope scopeOf(Node&);
</span><del>- static FocusNavigationScope scopeOwnedByShadowHost(Element&);
</del><ins>+ static FocusNavigationScope scopeOwnedByScopeOwner(Element&);
</ins><span class="cx"> static FocusNavigationScope scopeOwnedByIFrame(HTMLFrameOwnerElement&);
</span><span class="cx">
</span><ins>+ Node* firstNodeInScope() const;
+ Node* lastNodeInScope() const;
</ins><span class="cx"> Node* nextInScope(const Node*) const;
</span><span class="cx"> Node* previousInScope(const Node*) const;
</span><del>- Node* lastChildInScope(const Node*) const;
</del><ins>+ Node* lastChildInScope(const Node&) const;
</ins><span class="cx">
</span><span class="cx"> private:
</span><del>- Node* firstChildInScope(const Node*) const;
</del><ins>+ Node* firstChildInScope(const Node&) const;
</ins><span class="cx">
</span><ins>+ Node* parentInScope(const Node&) const;
+
+ Node* nextSiblingInScope(const Node&) const;
+ Node* previousSiblingInScope(const Node&) const;
+
</ins><span class="cx"> explicit FocusNavigationScope(TreeScope&);
</span><del>- TreeScope& m_rootTreeScope;
</del><ins>+
+#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
</ins><span class="cx"> };
</span><span class="cx">
</span><span class="cx"> // FIXME: Focus navigation should work with shadow trees that have slots.
</span><del>-Node* FocusNavigationScope::firstChildInScope(const Node* node) const
</del><ins>+Node* FocusNavigationScope::firstChildInScope(const Node& node) const
</ins><span class="cx"> {
</span><del>- ASSERT(node);
- if (node->shadowRoot())
</del><ins>+ if (is<Element>(node) && isFocusScopeOwner(downcast<Element>(node)))
</ins><span class="cx"> return nullptr;
</span><del>- return node->firstChild();
</del><ins>+ return node.firstChild();
</ins><span class="cx"> }
</span><span class="cx">
</span><del>-Node* FocusNavigationScope::lastChildInScope(const Node* node) const
</del><ins>+Node* FocusNavigationScope::lastChildInScope(const Node& node) const
</ins><span class="cx"> {
</span><del>- ASSERT(node);
- if (node->shadowRoot())
</del><ins>+ if (is<Element>(node) && isFocusScopeOwner(downcast<Element>(node)))
</ins><span class="cx"> return nullptr;
</span><del>- return node->lastChild();
</del><ins>+ return node.lastChild();
</ins><span class="cx"> }
</span><span class="cx">
</span><del>-static Node* parentInScope(const Node* node)
</del><ins>+Node* FocusNavigationScope::parentInScope(const Node& node) const
</ins><span class="cx"> {
</span><del>- if (node->isShadowRoot())
</del><ins>+ if (is<Element>(node) && isFocusScopeOwner(downcast<Element>(node)))
</ins><span class="cx"> return nullptr;
</span><span class="cx">
</span><del>- ContainerNode* parent = node->parentNode();
- if (parent && parent->shadowRoot())
</del><ins>+#if ENABLE(SHADOW_DOM) || ENABLE(DETAILS_ELEMENT)
+ if (UNLIKELY(m_slotElement && m_slotElement == node.assignedSlot()))
</ins><span class="cx"> return nullptr;
</span><ins>+#endif
</ins><span class="cx">
</span><ins>+ ContainerNode* parent = node.parentNode();
+ if (parent && is<Element>(parent) && isFocusScopeOwner(downcast<Element>(*parent)))
+ return nullptr;
+
</ins><span class="cx"> return parent;
</span><span class="cx"> }
</span><span class="cx">
</span><ins>+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();
+}
+
</ins><span class="cx"> Node* FocusNavigationScope::nextInScope(const Node* node) const
</span><span class="cx"> {
</span><del>- if (Node* next = firstChildInScope(node))
</del><ins>+ ASSERT(node);
+ if (Node* next = firstChildInScope(*node))
</ins><span class="cx"> return next;
</span><del>- if (Node* next = node->nextSibling())
</del><ins>+ if (Node* next = nextSiblingInScope(*node))
</ins><span class="cx"> return next;
</span><span class="cx"> const Node* current = node;
</span><del>- while (current && !current->nextSibling())
- current = parentInScope(current);
- return current ? current->nextSibling() : nullptr;
</del><ins>+ while (current && !nextSiblingInScope(*current))
+ current = parentInScope(*current);
+ return current ? nextSiblingInScope(*current) : nullptr;
</ins><span class="cx"> }
</span><span class="cx">
</span><span class="cx"> Node* FocusNavigationScope::previousInScope(const Node* node) const
</span><span class="cx"> {
</span><del>- if (Node* current = node->previousSibling()) {
- while (Node* child = lastChildInScope(current))
</del><ins>+ ASSERT(node);
+ if (Node* current = previousSiblingInScope(*node)) {
+ while (Node* child = lastChildInScope(*current))
</ins><span class="cx"> current = child;
</span><span class="cx"> return current;
</span><span class="cx"> }
</span><del>- return parentInScope(node);
</del><ins>+ return parentInScope(*node);
</ins><span class="cx"> }
</span><span class="cx">
</span><span class="cx"> FocusNavigationScope::FocusNavigationScope(TreeScope& treeScope)
</span><del>- : m_rootTreeScope(treeScope)
</del><ins>+ : m_rootTreeScope(&treeScope)
</ins><span class="cx"> {
</span><span class="cx"> }
</span><span class="cx">
</span><del>-ContainerNode& FocusNavigationScope::rootNode() const
</del><ins>+#if ENABLE(SHADOW_DOM) || ENABLE(DETAILS_ELEMENT)
+FocusNavigationScope::FocusNavigationScope(HTMLSlotElement& slotElement)
+ : m_slotElement(&slotElement)
</ins><span class="cx"> {
</span><del>- return m_rootTreeScope.rootNode();
</del><span class="cx"> }
</span><ins>+#endif
</ins><span class="cx">
</span><span class="cx"> Element* FocusNavigationScope::owner() const
</span><span class="cx"> {
</span><del>- ContainerNode& root = rootNode();
</del><ins>+#if ENABLE(SHADOW_DOM) || ENABLE(DETAILS_ELEMENT)
+ if (m_slotElement)
+ return m_slotElement;
+#endif
+
+ ASSERT(m_rootTreeScope);
+ ContainerNode& root = m_rootTreeScope->rootNode();
</ins><span class="cx"> if (is<ShadowRoot>(root))
</span><span class="cx"> return downcast<ShadowRoot>(root).host();
</span><span class="cx"> if (Frame* frame = root.document().frame())
</span><span class="lines">@@ -159,17 +260,30 @@
</span><span class="cx">
</span><span class="cx"> FocusNavigationScope FocusNavigationScope::scopeOf(Node& startingNode)
</span><span class="cx"> {
</span><ins>+ ASSERT(startingNode.isInTreeScope());
</ins><span class="cx"> Node* root = nullptr;
</span><del>- for (Node* currentNode = &startingNode; currentNode; currentNode = parentInScope(currentNode))
</del><ins>+ for (Node* currentNode = &startingNode; currentNode; currentNode = currentNode->parentNode()) {
</ins><span class="cx"> root = currentNode;
</span><del>- // The result is not always a ShadowRoot nor a DocumentNode since
- // a starting node is in an orphaned tree in composed shadow tree.
</del><ins>+#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);
</ins><span class="cx"> return FocusNavigationScope(root->treeScope());
</span><span class="cx"> }
</span><span class="cx">
</span><del>-FocusNavigationScope FocusNavigationScope::scopeOwnedByShadowHost(Element& element)
</del><ins>+FocusNavigationScope FocusNavigationScope::scopeOwnedByScopeOwner(Element& element)
</ins><span class="cx"> {
</span><del>- ASSERT(element.shadowRoot());
</del><ins>+#if ENABLE(SHADOW_DOM) || ENABLE(DETAILS_ELEMENT)
+ ASSERT(element.shadowRoot() || is<HTMLSlotElement>(element));
+ if (is<HTMLSlotElement>(element))
+ return FocusNavigationScope(downcast<HTMLSlotElement>(element));
+#endif
</ins><span class="cx"> return FocusNavigationScope(*element.shadowRoot());
</span><span class="cx"> }
</span><span class="cx">
</span><span class="lines">@@ -199,35 +313,30 @@
</span><span class="cx"> document->focusedElement()->dispatchFocusEvent(nullptr, FocusDirectionNone);
</span><span class="cx"> }
</span><span class="cx">
</span><del>-static inline bool hasCustomFocusLogic(Element& element)
</del><ins>+static inline bool isFocusableElementOrScopeOwner(Element& element, KeyboardEvent& event)
</ins><span class="cx"> {
</span><del>- return is<HTMLElement>(element) && downcast<HTMLElement>(element).hasCustomFocusLogic();
</del><ins>+ return element.isKeyboardFocusable(&event) || isFocusScopeOwner(element);
</ins><span class="cx"> }
</span><span class="cx">
</span><del>-static inline bool isNonFocusableShadowHost(Element& element, KeyboardEvent& event)
</del><ins>+static inline bool isNonFocusableScopeOwner(Element& element, KeyboardEvent& event)
</ins><span class="cx"> {
</span><del>- return !element.isKeyboardFocusable(&event) && element.shadowRoot() && !hasCustomFocusLogic(element);
</del><ins>+ return !element.isKeyboardFocusable(&event) && isFocusScopeOwner(element);
</ins><span class="cx"> }
</span><span class="cx">
</span><del>-static inline bool isFocusableShadowHost(Node& node, KeyboardEvent& event)
</del><ins>+static inline bool isFocusableScopeOwner(Element& element, KeyboardEvent& event)
</ins><span class="cx"> {
</span><del>- return is<Element>(node) && downcast<Element>(node).isKeyboardFocusable(&event) && downcast<Element>(node).shadowRoot() && !hasCustomFocusLogic(downcast<Element>(node));
</del><ins>+ return element.isKeyboardFocusable(&event) && isFocusScopeOwner(element);
</ins><span class="cx"> }
</span><span class="cx">
</span><span class="cx"> static inline int shadowAdjustedTabIndex(Element& element, KeyboardEvent& event)
</span><span class="cx"> {
</span><del>- if (isNonFocusableShadowHost(element, event)) {
</del><ins>+ if (isNonFocusableScopeOwner(element, event)) {
</ins><span class="cx"> if (!element.tabIndexSetExplicitly())
</span><span class="cx"> return 0; // Treat a shadow host without tabindex if it has tabindex=0 even though HTMLElement::tabIndex returns -1 on such an element.
</span><span class="cx"> }
</span><span class="cx"> return element.tabIndex();
</span><span class="cx"> }
</span><span class="cx">
</span><del>-static inline bool isFocusableOrHasShadowTreeWithoutCustomFocusLogic(Element& element, KeyboardEvent& event)
-{
- return element.isKeyboardFocusable(&event) || isNonFocusableShadowHost(element, event);
-}
-
</del><span class="cx"> FocusController::FocusController(Page& page, ViewState::Flags viewState)
</span><span class="cx"> : m_page(page)
</span><span class="cx"> , m_isChangingFocusedFrame(false)
</span><span class="lines">@@ -419,10 +528,10 @@
</span><span class="cx">
</span><span class="cx"> Element* FocusController::findFocusableElementAcrossFocusScope(FocusDirection direction, const FocusNavigationScope& scope, Node* currentNode, KeyboardEvent* event)
</span><span class="cx"> {
</span><del>- ASSERT(!is<Element>(currentNode) || !isNonFocusableShadowHost(*downcast<Element>(currentNode), *event));
</del><ins>+ ASSERT(!is<Element>(currentNode) || !isNonFocusableScopeOwner(downcast<Element>(*currentNode), *event));
</ins><span class="cx">
</span><del>- if (currentNode && direction == FocusDirectionForward && isFocusableShadowHost(*currentNode, *event)) {
- if (Element* candidateInInnerScope = findFocusableElementWithinScope(direction, FocusNavigationScope::scopeOwnedByShadowHost(downcast<Element>(*currentNode)), 0, event))
</del><ins>+ if (currentNode && direction == FocusDirectionForward && is<Element>(currentNode) && isFocusableScopeOwner(downcast<Element>(*currentNode), *event)) {
+ if (Element* candidateInInnerScope = findFocusableElementWithinScope(direction, FocusNavigationScope::scopeOwnedByScopeOwner(downcast<Element>(*currentNode)), 0, event))
</ins><span class="cx"> return candidateInInnerScope;
</span><span class="cx"> }
</span><span class="cx">
</span><span class="lines">@@ -432,7 +541,7 @@
</span><span class="cx"> // If there's no focusable node to advance to, move up the focus scopes until we find one.
</span><span class="cx"> Element* owner = scope.owner();
</span><span class="cx"> while (owner) {
</span><del>- if (direction == FocusDirectionBackward && isFocusableShadowHost(*owner, *event))
</del><ins>+ if (direction == FocusDirectionBackward && isFocusableScopeOwner(*owner, *event))
</ins><span class="cx"> return findFocusableElementDescendingDownIntoFrameDocument(direction, owner, event);
</span><span class="cx">
</span><span class="cx"> auto outerScope = FocusNavigationScope::scopeOf(*owner);
</span><span class="lines">@@ -457,8 +566,8 @@
</span><span class="cx"> Element* found = nextFocusableElementOrScopeOwner(scope, start, event);
</span><span class="cx"> if (!found)
</span><span class="cx"> return nullptr;
</span><del>- if (isNonFocusableShadowHost(*found, *event)) {
- if (Element* foundInInnerFocusScope = nextFocusableElementWithinScope(FocusNavigationScope::scopeOwnedByShadowHost(*found), 0, event))
</del><ins>+ if (isNonFocusableScopeOwner(*found, *event)) {
+ if (Element* foundInInnerFocusScope = nextFocusableElementWithinScope(FocusNavigationScope::scopeOwnedByScopeOwner(*found), 0, event))
</ins><span class="cx"> return foundInInnerFocusScope;
</span><span class="cx"> return nextFocusableElementWithinScope(scope, found, event);
</span><span class="cx"> }
</span><span class="lines">@@ -470,14 +579,14 @@
</span><span class="cx"> Element* found = previousFocusableElementOrScopeOwner(scope, start, event);
</span><span class="cx"> if (!found)
</span><span class="cx"> return nullptr;
</span><del>- if (isFocusableShadowHost(*found, *event)) {
</del><ins>+ if (isFocusableScopeOwner(*found, *event)) {
</ins><span class="cx"> // Search an inner focusable element in the shadow tree from the end.
</span><del>- if (Element* foundInInnerFocusScope = previousFocusableElementWithinScope(FocusNavigationScope::scopeOwnedByShadowHost(*found), 0, event))
</del><ins>+ if (Element* foundInInnerFocusScope = previousFocusableElementWithinScope(FocusNavigationScope::scopeOwnedByScopeOwner(*found), 0, event))
</ins><span class="cx"> return foundInInnerFocusScope;
</span><span class="cx"> return found;
</span><span class="cx"> }
</span><del>- if (isNonFocusableShadowHost(*found, *event)) {
- if (Element* foundInInnerFocusScope = previousFocusableElementWithinScope(FocusNavigationScope::scopeOwnedByShadowHost(*found), 0, event))
</del><ins>+ if (isNonFocusableScopeOwner(*found, *event)) {
+ if (Element* foundInInnerFocusScope = previousFocusableElementWithinScope(FocusNavigationScope::scopeOwnedByScopeOwner(*found), 0, event))
</ins><span class="cx"> return foundInInnerFocusScope;
</span><span class="cx"> return previousFocusableElementWithinScope(scope, found, event);
</span><span class="cx"> }
</span><span class="lines">@@ -498,7 +607,7 @@
</span><span class="cx"> if (!is<Element>(*node))
</span><span class="cx"> continue;
</span><span class="cx"> Element& element = downcast<Element>(*node);
</span><del>- if (isFocusableOrHasShadowTreeWithoutCustomFocusLogic(element, *event) && shadowAdjustedTabIndex(element, *event) == tabIndex)
</del><ins>+ if (isFocusableElementOrScopeOwner(element, *event) && shadowAdjustedTabIndex(element, *event) == tabIndex)
</ins><span class="cx"> return &element;
</span><span class="cx"> }
</span><span class="cx"> return nullptr;
</span><span class="lines">@@ -509,12 +618,12 @@
</span><span class="cx"> // Search is inclusive of start
</span><span class="cx"> int winningTabIndex = std::numeric_limits<int>::max();
</span><span class="cx"> Element* winner = nullptr;
</span><del>- for (Node* node = &scope.rootNode(); node; node = scope.nextInScope(node)) {
</del><ins>+ for (Node* node = scope.firstNodeInScope(); node; node = scope.nextInScope(node)) {
</ins><span class="cx"> if (!is<Element>(*node))
</span><span class="cx"> continue;
</span><span class="cx"> Element& candidate = downcast<Element>(*node);
</span><span class="cx"> int candidateTabIndex = candidate.tabIndex();
</span><del>- if (isFocusableOrHasShadowTreeWithoutCustomFocusLogic(candidate, event) && candidateTabIndex > tabIndex && (!winner || candidateTabIndex < winningTabIndex)) {
</del><ins>+ if (isFocusableElementOrScopeOwner(candidate, event) && candidateTabIndex > tabIndex && (!winner || candidateTabIndex < winningTabIndex)) {
</ins><span class="cx"> winner = &candidate;
</span><span class="cx"> winningTabIndex = candidateTabIndex;
</span><span class="cx"> }
</span><span class="lines">@@ -533,7 +642,7 @@
</span><span class="cx"> continue;
</span><span class="cx"> Element& element = downcast<Element>(*node);
</span><span class="cx"> int currentTabIndex = shadowAdjustedTabIndex(element, event);
</span><del>- if (isFocusableOrHasShadowTreeWithoutCustomFocusLogic(element, event) && currentTabIndex < tabIndex && currentTabIndex > winningTabIndex) {
</del><ins>+ if (isFocusableElementOrScopeOwner(element, event) && currentTabIndex < tabIndex && currentTabIndex > winningTabIndex) {
</ins><span class="cx"> winner = &element;
</span><span class="cx"> winningTabIndex = currentTabIndex;
</span><span class="cx"> }
</span><span class="lines">@@ -568,7 +677,7 @@
</span><span class="cx"> if (!is<Element>(*node))
</span><span class="cx"> continue;
</span><span class="cx"> Element& element = downcast<Element>(*node);
</span><del>- if (isFocusableOrHasShadowTreeWithoutCustomFocusLogic(element, *event) && shadowAdjustedTabIndex(element, *event) >= 0)
</del><ins>+ if (isFocusableElementOrScopeOwner(element, *event) && shadowAdjustedTabIndex(element, *event) >= 0)
</ins><span class="cx"> return &element;
</span><span class="cx"> }
</span><span class="cx"> }
</span><span class="lines">@@ -589,13 +698,13 @@
</span><span class="cx">
</span><span class="cx"> // There are no nodes with a tabindex greater than start's tabindex,
</span><span class="cx"> // so find the first node with a tabindex of 0.
</span><del>- return findElementWithExactTabIndex(scope, &scope.rootNode(), 0, event, FocusDirectionForward);
</del><ins>+ return findElementWithExactTabIndex(scope, scope.firstNodeInScope(), 0, event, FocusDirectionForward);
</ins><span class="cx"> }
</span><span class="cx">
</span><span class="cx"> Element* FocusController::previousFocusableElementOrScopeOwner(const FocusNavigationScope& scope, Node* start, KeyboardEvent* event)
</span><span class="cx"> {
</span><span class="cx"> Node* last = nullptr;
</span><del>- for (Node* node = &scope.rootNode(); node; node = scope.lastChildInScope(node))
</del><ins>+ for (Node* node = scope.lastNodeInScope(); node; node = scope.lastChildInScope(*node))
</ins><span class="cx"> last = node;
</span><span class="cx"> ASSERT(last);
</span><span class="cx">
</span><span class="lines">@@ -616,7 +725,7 @@
</span><span class="cx"> if (!is<Element>(*node))
</span><span class="cx"> continue;
</span><span class="cx"> Element& element = downcast<Element>(*node);
</span><del>- if (isFocusableOrHasShadowTreeWithoutCustomFocusLogic(element, *event) && shadowAdjustedTabIndex(element, *event) >= 0)
</del><ins>+ if (isFocusableElementOrScopeOwner(element, *event) && shadowAdjustedTabIndex(element, *event) >= 0)
</ins><span class="cx"> return &element;
</span><span class="cx"> }
</span><span class="cx"> }
</span></span></pre>
</div>
</div>
</body>
</html>