<!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 &quot;focus scope&quot;. The focus navigation ordering
is defined within each &quot;focus scope&quot; using tabindex, treating any &quot;focus scope owner&quot;
(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 &quot;focus scope&quot;.
* 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  &lt;rniwa@webkit.org&gt;
+
+        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  &lt;ryanhaddad@apple.com&gt;
</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>+&lt;!DOCTYPE html&gt;
+&lt;html&gt;
+&lt;body&gt;
+&lt;p&gt;Tests for moving focus across details element.
+The existence of shadow tree on details element should not affect the focus sequential navigation ordering.&lt;br&gt;
+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.&lt;/p&gt;
+&lt;div id=&quot;test-content&quot;&gt;
+&lt;div id=&quot;first&quot; tabindex=&quot;1&quot; onfocus=&quot;log(this)&quot;&gt;1. Focusable element with tabindex=1&lt;/div&gt;
+&lt;details open&gt;
+    &lt;div tabindex=&quot;6&quot; onfocus=&quot;log(this)&quot;&gt;6. Focusable element in details with tabindex=6&lt;/div&gt;
+    &lt;summary&gt;&lt;div tabindex=&quot;4&quot; onfocus=&quot;log(this)&quot;&gt;4. Focusable element in summary with tabindex=4&lt;/div&gt;&lt;/summary&gt;
+    &lt;div tabindex=&quot;2&quot; onfocus=&quot;log(this)&quot;&gt;2. Focusable element in details with tabindex=2&lt;/div&gt;
+&lt;/details&gt;
+&lt;details open&gt;
+    &lt;div tabindex=&quot;5&quot; onfocus=&quot;log(this)&quot;&gt;5. Focusable element in details with tabindex=5&lt;/div&gt;
+    &lt;div tabindex=&quot;3&quot; onfocus=&quot;log(this)&quot;&gt;3. Focusable element in details with tabindex=3&lt;/div&gt;
+&lt;/details&gt;
+&lt;/div&gt;
+&lt;pre&gt;&lt;/pre&gt;
+&lt;script&gt;
+
+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';
+}
+
+&lt;/script&gt;
+&lt;/body&gt;
+&lt;/html&gt;
</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>+&lt;!DOCTYPE html&gt;
+&lt;html&gt;
+&lt;body&gt;
+&lt;p&gt;Tests for moving focus by pressing tab key across shadow boundaries.&lt;br&gt;
+To manually test, press tab key sixteen times then shift+tab sixteen times.&lt;br&gt;
+It should traverse focusable elements in the increasing numerical order and then in the reverse order.&lt;/p&gt;
+&lt;style&gt;
+
+#test-content &gt; * {
+    display: block;
+    width: 350px;
+    height: 150px;
+    border: solid 1px black;
+    margin: 10px;
+    float: left;
+}
+
+pre {
+    padding-top: 1rem;
+    clear: both;
+}
+
+&lt;/style&gt;
+&lt;div id=&quot;test-content&quot;&gt;
+
+&lt;div id=&quot;first&quot; tabindex=&quot;1&quot;&gt;1. First sequentially focusable element&lt;/div&gt;
+
+&lt;div id=&quot;shadow-without-tabindex&quot;&gt;
+A non-focusable shadow host should not be focused.
+&lt;div slot=&quot;slot&quot; tabindex=&quot;0&quot;&gt;5. Slotted content with tabindex=0&lt;/div&gt;
+&lt;div slot=&quot;slot&quot; tabindex=&quot;3&quot;&gt;4. Slotted content with tabindex=4&lt;/div&gt;
+&lt;/div&gt;
+
+&lt;div id=&quot;shadow-with-multiple-slots&quot;&gt;
+A non-focusable shadow host should not be focused.
+&lt;div slot=&quot;slot2&quot;&gt;&lt;div tabindex=&quot;2&quot;&gt;11. Content in slot 2 with tabindex=1&lt;/div&gt;&lt;/div&gt;
+&lt;div slot=&quot;slot2&quot; tabindex=&quot;1&quot;&gt;10. Content in slot 2 with tabindex=1&lt;/div&gt;
+&lt;div slot=&quot;slot2&quot; tabindex=&quot;0&quot;&gt;12. Content in slot 2 with tabindex=0&lt;/div&gt;
+&lt;div slot=&quot;slot1&quot; tabindex=&quot;0&quot;&gt;9. Content in slot 1 with tabindex=0&lt;/div&gt;
+&lt;div slot=&quot;slot1&quot; tabindex=&quot;7&quot;&gt;8. Content in slot 1 with tabindex=7&lt;/div&gt;
+&lt;/div&gt;
+
+&lt;div id=&quot;shadow-with-slot-fallback&quot;&gt;
+A non-focusable shadow host should not be focused.
+&lt;/div&gt;
+
+&lt;/div&gt;
+&lt;pre&gt;&lt;/pre&gt;
+&lt;script&gt;
+
+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 = `
+        &lt;div tabindex=&quot;2&quot; onfocus=&quot;log(this)&quot;&gt;3. The focusable element in shadow tree with the lowest tabindex&lt;/div&gt;
+        &lt;slot name=&quot;slot&quot; onfocus=&quot;log(this)&quot;&gt;Non-focusable slot element should not be focused&lt;/slot&gt;
+        &lt;div tabindex=&quot;1&quot; onfocus=&quot;log(this)&quot;&gt;2. The focusable element in shadow tree with the higehst tabindex&lt;/div&gt;`;
+
+    var hostWithMultipleSlots = document.getElementById('shadow-with-multiple-slots');
+    hostWithMultipleSlots.closedShadowRoot = hostWithMultipleSlots.attachShadow({mode: 'closed'});
+    hostWithMultipleSlots.closedShadowRoot.innerHTML = `
+        &lt;slot name=&quot;slot1&quot; tabindex=&quot;2&quot; onfocus=&quot;log(this)&quot; style=&quot;display:block;&quot;&gt;7. Focusable slot 1&lt;/slot&gt;
+        &lt;div tabindex=&quot;1&quot; onfocus=&quot;log(this)&quot;&gt;6. The focusable element in shadow tree with the higehst tabindex&lt;/div&gt;
+        &lt;slot name=&quot;slot2&quot; onfocus=&quot;log(this)&quot;&gt;Non-focusable slot 2 should not be focused&lt;/slot&gt;`;
+
+    var shadowWithSlotFallback = document.getElementById('shadow-with-slot-fallback');
+    shadowWithSlotFallback.closedShadowRoot = shadowWithSlotFallback.attachShadow({mode: 'closed'});
+    shadowWithSlotFallback.closedShadowRoot.innerHTML = `
+        &lt;slot name=&quot;slot1&quot; onfocus=&quot;log(this)&quot;&gt;
+            Non-focusable slot should not be focused.
+            &lt;div tabindex=&quot;0&quot;&gt;16. Non-focusable slot fallback with tabindex=0&lt;/div&gt;
+            &lt;div tabindex=&quot;1&quot;&gt;13. Non-focusable slot fallback with tabindex=1&lt;/div&gt;
+        &lt;/slot&gt;
+        &lt;div tabindex=&quot;2&quot; onfocus=&quot;log(this)&quot;&gt;15. Shadow content with tabindex=2&lt;/div&gt;
+        &lt;slot name=&quot;slot2&quot; tabindex=&quot;1&quot; style=&quot;display:block;&quot; onfocus=&quot;log(this)&quot;&gt;
+            14. Focusable slot element.
+            &lt;div tabindex=&quot;0&quot;&gt;17. Focusable slot fallback content with tabindex=0&lt;/div&gt;
+        &lt;/slot&gt;`;
+
+    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);
+}
+
+&lt;/script&gt;
+&lt;/body&gt;
+&lt;/html&gt;
</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  &lt;rniwa@webkit.org&gt;
+
+        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 &quot;focus scope&quot;. The focus navigation ordering
+        is defined within each &quot;focus scope&quot; using tabindex, treating any &quot;focus scope owner&quot;
+        (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 &quot;focus scope&quot;.
+        * 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  &lt;cdumez@apple.com&gt;
</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&amp; node)
+{
+    if (auto* parent = node.parentElement())
+        return parent-&gt;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-&gt;findAssignedSlot(*this);
+    return nullptr;
+}
</ins><span class="cx"> 
</span><del>-    auto* shadowRoot = parent-&gt;shadowRoot();
-    if (!shadowRoot || shadowRoot-&gt;type() != ShadowRoot::Type::Open)
-        return nullptr;
-
-    return shadowRoot-&gt;findAssignedSlot(*this);
</del><ins>+HTMLSlotElement* Node::assignedSlotForBindings() const
+{
+    auto* shadowRoot = parentShadowRoot(*this);
+    if (shadowRoot &amp;&amp; shadowRoot-&gt;type() == ShadowRoot::Type::Open)
+        return shadowRoot-&gt;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-&gt;shadowRoot()) {
-            if (auto* assignedSlot = shadowRoot-&gt;findAssignedSlot(*this))
-                return assignedSlot;
-        }
-    }
</del><ins>+    if (auto* slot = assignedSlot())
+        return slot;
</ins><span class="cx"> #endif
</span><span class="cx">     if (is&lt;ShadowRoot&gt;(*this))
</span><span class="cx">         return downcast&lt;ShadowRoot&gt;(*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&amp;) 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) &amp;&amp; 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 &quot;HTMLInputElement.h&quot;
</span><span class="cx"> #include &quot;HTMLNames.h&quot;
</span><span class="cx"> #include &quot;HTMLPlugInElement.h&quot;
</span><ins>+#include &quot;HTMLSlotElement.h&quot;
</ins><span class="cx"> #include &quot;HTMLTextAreaElement.h&quot;
</span><span class="cx"> #include &quot;HitTestResult.h&quot;
</span><span class="cx"> #include &quot;KeyboardEvent.h&quot;
</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&amp; element)
+{
+    return is&lt;HTMLElement&gt;(element) &amp;&amp; downcast&lt;HTMLElement&gt;(element).hasCustomFocusLogic();
+}
+
+static inline bool isFocusScopeOwner(const Element&amp; element)
+{
+    if (element.shadowRoot() &amp;&amp; !hasCustomFocusLogic(element))
+        return true;
+#if ENABLE(SHADOW_DOM) || ENABLE(DETAILS_ELEMENT)
+    if (is&lt;HTMLSlotElement&gt;(element) &amp;&amp; downcast&lt;HTMLSlotElement&gt;(element).assignedNodes()) {
+        ShadowRoot* root = element.containingShadowRoot();
+        if (root &amp;&amp; root-&gt;host() &amp;&amp; !hasCustomFocusLogic(*root-&gt;host()))
+            return true;
+    }
+#endif
+    return false;
+}
+
</ins><span class="cx"> class FocusNavigationScope {
</span><span class="cx"> public:
</span><del>-    ContainerNode&amp; rootNode() const;
</del><span class="cx">     Element* owner() const;
</span><span class="cx">     WEBCORE_EXPORT static FocusNavigationScope scopeOf(Node&amp;);
</span><del>-    static FocusNavigationScope scopeOwnedByShadowHost(Element&amp;);
</del><ins>+    static FocusNavigationScope scopeOwnedByScopeOwner(Element&amp;);
</ins><span class="cx">     static FocusNavigationScope scopeOwnedByIFrame(HTMLFrameOwnerElement&amp;);
</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&amp;) const;
</ins><span class="cx"> 
</span><span class="cx"> private:
</span><del>-    Node* firstChildInScope(const Node*) const;
</del><ins>+    Node* firstChildInScope(const Node&amp;) const;
</ins><span class="cx"> 
</span><ins>+    Node* parentInScope(const Node&amp;) const;
+
+    Node* nextSiblingInScope(const Node&amp;) const;
+    Node* previousSiblingInScope(const Node&amp;) const;
+
</ins><span class="cx">     explicit FocusNavigationScope(TreeScope&amp;);
</span><del>-    TreeScope&amp; m_rootTreeScope;
</del><ins>+
+#if ENABLE(SHADOW_DOM) || ENABLE(DETAILS_ELEMENT)
+    explicit FocusNavigationScope(HTMLSlotElement&amp;);
+#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&amp; node) const
</ins><span class="cx"> {
</span><del>-    ASSERT(node);
-    if (node-&gt;shadowRoot())
</del><ins>+    if (is&lt;Element&gt;(node) &amp;&amp; isFocusScopeOwner(downcast&lt;Element&gt;(node)))
</ins><span class="cx">         return nullptr;
</span><del>-    return node-&gt;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&amp; node) const
</ins><span class="cx"> {
</span><del>-    ASSERT(node);
-    if (node-&gt;shadowRoot())
</del><ins>+    if (is&lt;Element&gt;(node) &amp;&amp; isFocusScopeOwner(downcast&lt;Element&gt;(node)))
</ins><span class="cx">         return nullptr;
</span><del>-    return node-&gt;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&amp; node) const
</ins><span class="cx"> {
</span><del>-    if (node-&gt;isShadowRoot())
</del><ins>+    if (is&lt;Element&gt;(node) &amp;&amp; isFocusScopeOwner(downcast&lt;Element&gt;(node)))
</ins><span class="cx">         return nullptr;
</span><span class="cx"> 
</span><del>-    ContainerNode* parent = node-&gt;parentNode();
-    if (parent &amp;&amp; parent-&gt;shadowRoot())
</del><ins>+#if ENABLE(SHADOW_DOM) || ENABLE(DETAILS_ELEMENT)
+    if (UNLIKELY(m_slotElement &amp;&amp; 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 &amp;&amp; is&lt;Element&gt;(parent) &amp;&amp; isFocusScopeOwner(downcast&lt;Element&gt;(*parent)))
+        return nullptr;
+
</ins><span class="cx">     return parent;
</span><span class="cx"> }
</span><span class="cx"> 
</span><ins>+Node* FocusNavigationScope::nextSiblingInScope(const Node&amp; node) const
+{
+#if ENABLE(SHADOW_DOM) || ENABLE(DETAILS_ELEMENT)
+    if (UNLIKELY(m_slotElement &amp;&amp; m_slotElement == node.assignedSlot())) {
+        for (Node* current = node.nextSibling(); current; current = current-&gt;nextSibling()) {
+            if (current-&gt;assignedSlot() == m_slotElement)
+                return current;
+        }
+        return nullptr;
+    }
+#endif
+    return node.nextSibling();
+}
+
+Node* FocusNavigationScope::previousSiblingInScope(const Node&amp; node) const
+{
+#if ENABLE(SHADOW_DOM) || ENABLE(DETAILS_ELEMENT)
+    if (UNLIKELY(m_slotElement &amp;&amp; m_slotElement == node.assignedSlot())) {
+        for (Node* current = node.previousSibling(); current; current = current-&gt;previousSibling()) {
+            if (current-&gt;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-&gt;assignedNodes();
+        ASSERT(assigneNodes);
+        return assigneNodes-&gt;first();
+    }
+#endif
+    ASSERT(m_rootTreeScope);
+    return &amp;m_rootTreeScope-&gt;rootNode();
+}
+
+Node* FocusNavigationScope::lastNodeInScope() const
+{
+#if ENABLE(SHADOW_DOM) || ENABLE(DETAILS_ELEMENT)
+    if (UNLIKELY(m_slotElement)) {
+        auto* assigneNodes = m_slotElement-&gt;assignedNodes();
+        ASSERT(assigneNodes);
+        return assigneNodes-&gt;last();
+    }
+#endif
+    ASSERT(m_rootTreeScope);
+    return &amp;m_rootTreeScope-&gt;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-&gt;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 &amp;&amp; !current-&gt;nextSibling())
-        current = parentInScope(current);
-    return current ? current-&gt;nextSibling() : nullptr;
</del><ins>+    while (current &amp;&amp; !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-&gt;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&amp; treeScope)
</span><del>-    : m_rootTreeScope(treeScope)
</del><ins>+    : m_rootTreeScope(&amp;treeScope)
</ins><span class="cx"> {
</span><span class="cx"> }
</span><span class="cx"> 
</span><del>-ContainerNode&amp; FocusNavigationScope::rootNode() const
</del><ins>+#if ENABLE(SHADOW_DOM) || ENABLE(DETAILS_ELEMENT)
+FocusNavigationScope::FocusNavigationScope(HTMLSlotElement&amp; slotElement)
+    : m_slotElement(&amp;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&amp; root = rootNode();
</del><ins>+#if ENABLE(SHADOW_DOM) || ENABLE(DETAILS_ELEMENT)
+    if (m_slotElement)
+        return m_slotElement;
+#endif
+
+    ASSERT(m_rootTreeScope);
+    ContainerNode&amp; root = m_rootTreeScope-&gt;rootNode();
</ins><span class="cx">     if (is&lt;ShadowRoot&gt;(root))
</span><span class="cx">         return downcast&lt;ShadowRoot&gt;(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&amp; startingNode)
</span><span class="cx"> {
</span><ins>+    ASSERT(startingNode.isInTreeScope());
</ins><span class="cx">     Node* root = nullptr;
</span><del>-    for (Node* currentNode = &amp;startingNode; currentNode; currentNode = parentInScope(currentNode))
</del><ins>+    for (Node* currentNode = &amp;startingNode; currentNode; currentNode = currentNode-&gt;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-&gt;assignedSlot()) {
+            if (isFocusScopeOwner(*slot))
+                return FocusNavigationScope(*slot);
+        }
+#endif
+        if (is&lt;ShadowRoot&gt;(currentNode))
+            return FocusNavigationScope(downcast&lt;ShadowRoot&gt;(*currentNode));
+    }
+    ASSERT(root);
</ins><span class="cx">     return FocusNavigationScope(root-&gt;treeScope());
</span><span class="cx"> }
</span><span class="cx"> 
</span><del>-FocusNavigationScope FocusNavigationScope::scopeOwnedByShadowHost(Element&amp; element)
</del><ins>+FocusNavigationScope FocusNavigationScope::scopeOwnedByScopeOwner(Element&amp; element)
</ins><span class="cx"> {
</span><del>-    ASSERT(element.shadowRoot());
</del><ins>+#if ENABLE(SHADOW_DOM) || ENABLE(DETAILS_ELEMENT)
+    ASSERT(element.shadowRoot() || is&lt;HTMLSlotElement&gt;(element));
+    if (is&lt;HTMLSlotElement&gt;(element))
+        return FocusNavigationScope(downcast&lt;HTMLSlotElement&gt;(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-&gt;focusedElement()-&gt;dispatchFocusEvent(nullptr, FocusDirectionNone);
</span><span class="cx"> }
</span><span class="cx"> 
</span><del>-static inline bool hasCustomFocusLogic(Element&amp; element)
</del><ins>+static inline bool isFocusableElementOrScopeOwner(Element&amp; element, KeyboardEvent&amp; event)
</ins><span class="cx"> {
</span><del>-    return is&lt;HTMLElement&gt;(element) &amp;&amp; downcast&lt;HTMLElement&gt;(element).hasCustomFocusLogic();
</del><ins>+    return element.isKeyboardFocusable(&amp;event) || isFocusScopeOwner(element);
</ins><span class="cx"> }
</span><span class="cx"> 
</span><del>-static inline bool isNonFocusableShadowHost(Element&amp; element, KeyboardEvent&amp; event)
</del><ins>+static inline bool isNonFocusableScopeOwner(Element&amp; element, KeyboardEvent&amp; event)
</ins><span class="cx"> {
</span><del>-    return !element.isKeyboardFocusable(&amp;event) &amp;&amp; element.shadowRoot() &amp;&amp; !hasCustomFocusLogic(element);
</del><ins>+    return !element.isKeyboardFocusable(&amp;event) &amp;&amp; isFocusScopeOwner(element);
</ins><span class="cx"> }
</span><span class="cx"> 
</span><del>-static inline bool isFocusableShadowHost(Node&amp; node, KeyboardEvent&amp; event)
</del><ins>+static inline bool isFocusableScopeOwner(Element&amp; element, KeyboardEvent&amp; event)
</ins><span class="cx"> {
</span><del>-    return is&lt;Element&gt;(node) &amp;&amp; downcast&lt;Element&gt;(node).isKeyboardFocusable(&amp;event) &amp;&amp; downcast&lt;Element&gt;(node).shadowRoot() &amp;&amp; !hasCustomFocusLogic(downcast&lt;Element&gt;(node));
</del><ins>+    return element.isKeyboardFocusable(&amp;event) &amp;&amp; isFocusScopeOwner(element);
</ins><span class="cx"> }
</span><span class="cx"> 
</span><span class="cx"> static inline int shadowAdjustedTabIndex(Element&amp; element, KeyboardEvent&amp; 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&amp; element, KeyboardEvent&amp; event)
-{
-    return element.isKeyboardFocusable(&amp;event) || isNonFocusableShadowHost(element, event);
-}
-
</del><span class="cx"> FocusController::FocusController(Page&amp; 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&amp; scope, Node* currentNode, KeyboardEvent* event)
</span><span class="cx"> {
</span><del>-    ASSERT(!is&lt;Element&gt;(currentNode) || !isNonFocusableShadowHost(*downcast&lt;Element&gt;(currentNode), *event));
</del><ins>+    ASSERT(!is&lt;Element&gt;(currentNode) || !isNonFocusableScopeOwner(downcast&lt;Element&gt;(*currentNode), *event));
</ins><span class="cx"> 
</span><del>-    if (currentNode &amp;&amp; direction == FocusDirectionForward &amp;&amp; isFocusableShadowHost(*currentNode, *event)) {
-        if (Element* candidateInInnerScope = findFocusableElementWithinScope(direction, FocusNavigationScope::scopeOwnedByShadowHost(downcast&lt;Element&gt;(*currentNode)), 0, event))
</del><ins>+    if (currentNode &amp;&amp; direction == FocusDirectionForward &amp;&amp; is&lt;Element&gt;(currentNode) &amp;&amp; isFocusableScopeOwner(downcast&lt;Element&gt;(*currentNode), *event)) {
+        if (Element* candidateInInnerScope = findFocusableElementWithinScope(direction, FocusNavigationScope::scopeOwnedByScopeOwner(downcast&lt;Element&gt;(*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 &amp;&amp; isFocusableShadowHost(*owner, *event))
</del><ins>+        if (direction == FocusDirectionBackward &amp;&amp; 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&lt;Element&gt;(*node))
</span><span class="cx">             continue;
</span><span class="cx">         Element&amp; element = downcast&lt;Element&gt;(*node);
</span><del>-        if (isFocusableOrHasShadowTreeWithoutCustomFocusLogic(element, *event) &amp;&amp; shadowAdjustedTabIndex(element, *event) == tabIndex)
</del><ins>+        if (isFocusableElementOrScopeOwner(element, *event) &amp;&amp; shadowAdjustedTabIndex(element, *event) == tabIndex)
</ins><span class="cx">             return &amp;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&lt;int&gt;::max();
</span><span class="cx">     Element* winner = nullptr;
</span><del>-    for (Node* node = &amp;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&lt;Element&gt;(*node))
</span><span class="cx">             continue;
</span><span class="cx">         Element&amp; candidate = downcast&lt;Element&gt;(*node);
</span><span class="cx">         int candidateTabIndex = candidate.tabIndex();
</span><del>-        if (isFocusableOrHasShadowTreeWithoutCustomFocusLogic(candidate, event) &amp;&amp; candidateTabIndex &gt; tabIndex &amp;&amp; (!winner || candidateTabIndex &lt; winningTabIndex)) {
</del><ins>+        if (isFocusableElementOrScopeOwner(candidate, event) &amp;&amp; candidateTabIndex &gt; tabIndex &amp;&amp; (!winner || candidateTabIndex &lt; winningTabIndex)) {
</ins><span class="cx">             winner = &amp;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&amp; element = downcast&lt;Element&gt;(*node);
</span><span class="cx">         int currentTabIndex = shadowAdjustedTabIndex(element, event);
</span><del>-        if (isFocusableOrHasShadowTreeWithoutCustomFocusLogic(element, event) &amp;&amp; currentTabIndex &lt; tabIndex &amp;&amp; currentTabIndex &gt; winningTabIndex) {
</del><ins>+        if (isFocusableElementOrScopeOwner(element, event) &amp;&amp; currentTabIndex &lt; tabIndex &amp;&amp; currentTabIndex &gt; winningTabIndex) {
</ins><span class="cx">             winner = &amp;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&lt;Element&gt;(*node))
</span><span class="cx">                     continue;
</span><span class="cx">                 Element&amp; element = downcast&lt;Element&gt;(*node);
</span><del>-                if (isFocusableOrHasShadowTreeWithoutCustomFocusLogic(element, *event) &amp;&amp; shadowAdjustedTabIndex(element, *event) &gt;= 0)
</del><ins>+                if (isFocusableElementOrScopeOwner(element, *event) &amp;&amp; shadowAdjustedTabIndex(element, *event) &gt;= 0)
</ins><span class="cx">                     return &amp;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, &amp;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&amp; scope, Node* start, KeyboardEvent* event)
</span><span class="cx"> {
</span><span class="cx">     Node* last = nullptr;
</span><del>-    for (Node* node = &amp;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&lt;Element&gt;(*node))
</span><span class="cx">                 continue;
</span><span class="cx">             Element&amp; element = downcast&lt;Element&gt;(*node);
</span><del>-            if (isFocusableOrHasShadowTreeWithoutCustomFocusLogic(element, *event) &amp;&amp; shadowAdjustedTabIndex(element, *event) &gt;= 0)
</del><ins>+            if (isFocusableElementOrScopeOwner(element, *event) &amp;&amp; shadowAdjustedTabIndex(element, *event) &gt;= 0)
</ins><span class="cx">                 return &amp;element;
</span><span class="cx">         }
</span><span class="cx">     }
</span></span></pre>
</div>
</div>

</body>
</html>