<!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>[190288] 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/190288">190288</a></dd>
<dt>Author</dt> <dd>rniwa@webkit.org</dd>
<dt>Date</dt> <dd>2015-09-28 15:11:08 -0700 (Mon, 28 Sep 2015)</dd>
</dl>

<h3>Log Message</h3>
<pre>relatedNode should be retargeted respecting slots
https://bugs.webkit.org/show_bug.cgi?id=149591

Reviewed by Antti Koivisto.

Source/WebCore:

This patch retargets relatedNode with respect to shadow boundaries after <a href="http://trac.webkit.org/projects/webkit/changeset/190214">r190214</a> as specified in
https://w3c.github.io/webcomponents/spec/shadow/#retargeting-relatedtarget

Naively implementing the spec'ed behavior can result in O(n^2) behavior since we need to find the common tree scope
ancestor for each target in the event path. This patch avoids this by implementing an O(1) incremental update step
for when target's tree scope changes in the event path. See the description for moveToNewTreeScope below.

Test: fast/shadow-dom/event-with-related-target.html

* dom/EventContext.h: Replaced toMouseOrFocusEventContext by downcast&lt;MouseOrFocusEventContext&gt;.
* dom/EventDispatcher.cpp:
(WebCore::EventRelatedNodeResolver): Removed the code for relatedNode. This class is now only used for touch events.
(WebCore::EventPath): Added m_event as a member variable.

(WebCore::RelatedNodeRetargeter): Added.

(WebCore::RelatedNodeRetargeter::RelatedNodeRetargeter): Does the initial retargeting of relatedNode. When the
tree scope of relatedNode and the target are the same, we immediately exit without collecting ancestor tree scopes
of relatedNode as an optimization. We also special case when the relatedNode and the target are in two different
documents (relatedNode should be nullptr) and when one is in document and the other one is not in the document
(relatedNode should be the host of the outermost shadow root). Otherwise we have to do the real work by collecting
all tree scope ancestors and walking downwards from the document tree scope (note target and relatedNode share the
same document scope here since we would have exited early otherwise).

(WebCore::RelatedNodeRetargeter::currentNode): Returned relatedNode retargeted for the current tree scope.

(WebCore::RelatedNodeRetargeter::moveToNewTreeScope): Moves to a new tree scope. If the original target and
relatedNode were in different trees, there is nothing to be done. Note that we can only move out of a shadow root
to its host or move into a slot so newTreeScope (current target's tree scope) and previousTreeScope (previous
target's tree scope) must have a child-parent relationship.

If previousTreeScope did not contain the retargeted relatedNode, then neither can its child shadow trees. Thus,
there is nothing to be done when moving into a slot in this case. If we're moving out of previousTreeScope, then
newTreeScope may contain the retargeted relatedNode but that still doesn't require any work. So we exit early in
both cases.

Otherwise (previousTreeScope contained retargeted relatedNode), if we're moving out of a child shadow root
(previousTreeScope) then relatedNode should also move to previousTreeScope's shadow host since previousTreeScope
is a direct-child shadow tree of newTreeScope and previousTreeScope's shadow host resides in newTreeScope.

If we're moving into a child shadow root via a slot, then there are three possibilities: relatedNode is in
previousTreeScope, newTreeScope and its child shadow trees, or newTreeScope's sibling tree scopes and its children.
If it is in previousTreeScope (m_lowestCommonAncestorIndex is zero) or in newTreeScope's sibling, then
previousTreeScope is the lowest common tree scope ancestor so there is nothing to be done. If relatedNode is in
newTreeScope, then the retargeted relatedNode is either the shadow host of the shadow tree that contains
relatedNode or relatedNode itself.

(WebCore::RelatedNodeRetargeter::checkConsistency): Finds the retargeted relatedNode in the simplest way to verify
the correctness of the algorithm. We can disable this consistency check if it slows down debug builds too much.
(WebCore::RelatedNodeRetargeter::nodeInLowestCommonAncestor): Finds the
(WebCore::RelatedNodeRetargeter::collectTreeScopes):
(WebCore::EventPath::setRelatedTarget): Rewritten using RelatedNodeRetargeter.

LayoutTests:

Added a new testharness.js test for retargeting relatedNode.

* fast/shadow-dom/event-with-related-target.html: Added.</pre>

<h3>Modified Paths</h3>
<ul>
<li><a href="#trunkLayoutTestsChangeLog">trunk/LayoutTests/ChangeLog</a></li>
<li><a href="#trunkSourceWebCoreChangeLog">trunk/Source/WebCore/ChangeLog</a></li>
<li><a href="#trunkSourceWebCoredomEventContexth">trunk/Source/WebCore/dom/EventContext.h</a></li>
<li><a href="#trunkSourceWebCoredomEventDispatchercpp">trunk/Source/WebCore/dom/EventDispatcher.cpp</a></li>
</ul>

<h3>Added Paths</h3>
<ul>
<li><a href="#trunkLayoutTestsfastshadowdomeventwithrelatedtargetexpectedtxt">trunk/LayoutTests/fast/shadow-dom/event-with-related-target-expected.txt</a></li>
<li><a href="#trunkLayoutTestsfastshadowdomeventwithrelatedtargethtml">trunk/LayoutTests/fast/shadow-dom/event-with-related-target.html</a></li>
</ul>

</div>
<div id="patch">
<h3>Diff</h3>
<a id="trunkLayoutTestsChangeLog"></a>
<div class="modfile"><h4>Modified: trunk/LayoutTests/ChangeLog (190287 => 190288)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/LayoutTests/ChangeLog        2015-09-28 22:08:55 UTC (rev 190287)
+++ trunk/LayoutTests/ChangeLog        2015-09-28 22:11:08 UTC (rev 190288)
</span><span class="lines">@@ -1,3 +1,14 @@
</span><ins>+2015-09-28  Ryosuke Niwa  &lt;rniwa@webkit.org&gt;
+
+        relatedNode should be retargeted respecting slots
+        https://bugs.webkit.org/show_bug.cgi?id=149591
+
+        Reviewed by Antti Koivisto.
+
+        Added a new testharness.js test for retargeting relatedNode.
+
+        * fast/shadow-dom/event-with-related-target.html: Added.
+
</ins><span class="cx"> 2015-09-28  Saam barati  &lt;sbarati@apple.com&gt;
</span><span class="cx"> 
</span><span class="cx">         js/regress/getter-richards-try-catch is timing out on debug layout tests
</span></span></pre></div>
<a id="trunkLayoutTestsfastshadowdomeventwithrelatedtargetexpectedtxt"></a>
<div class="addfile"><h4>Added: trunk/LayoutTests/fast/shadow-dom/event-with-related-target-expected.txt (0 => 190288)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/LayoutTests/fast/shadow-dom/event-with-related-target-expected.txt                                (rev 0)
+++ trunk/LayoutTests/fast/shadow-dom/event-with-related-target-expected.txt        2015-09-28 22:11:08 UTC (rev 190288)
</span><span class="lines">@@ -0,0 +1,20 @@
</span><ins>+
+PASS Firing an event at B1a with relatedNode at B1 with open mode shadow trees 
+PASS Firing an event at B1a with relatedNode at B1 with closed mode shadow trees 
+PASS Firing an event at B1a with relatedNode at B1b1 with open mode shadow trees 
+PASS Firing an event at B1a with relatedNode at B1b1 with closed mode shadow trees 
+PASS Firing an event at B1b1 with relatedNode at B1a with open mode shadow trees 
+PASS Firing an event at B1b1 with relatedNode at B1a with closed mode shadow trees 
+PASS Firing an event at B1a with relatedNode at D1 with open mode shadow trees 
+PASS Firing an event at B1a with relatedNode at D1 with closed mode shadow trees 
+PASS Firing an event at D1 with relatedNode at B1a with open mode shadow trees 
+PASS Firing an event at D1 with relatedNode at B1a with closed mode shadow trees 
+PASS Firing an event at B1a with relatedNode at A1a with open mode shadow trees 
+PASS Firing an event at B1a with relatedNode at A1a with closed mode shadow trees 
+PASS Firing an event at B1a with relatedNode at A1a with open mode shadow trees 
+PASS Firing an event at B1a with relatedNode at A1a with closed mode shadow trees 
+PASS Firing an event at B1a with relatedNode at A1a with open mode shadow trees 
+PASS Firing an event at B1a with relatedNode at A1a with closed mode shadow trees 
+PASS Firing an event at B1a with relatedNode at A1a with open mode shadow trees 
+PASS Firing an event at B1a with relatedNode at A1a with closed mode shadow trees 
+
</ins></span></pre></div>
<a id="trunkLayoutTestsfastshadowdomeventwithrelatedtargethtml"></a>
<div class="addfile"><h4>Added: trunk/LayoutTests/fast/shadow-dom/event-with-related-target.html (0 => 190288)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/LayoutTests/fast/shadow-dom/event-with-related-target.html                                (rev 0)
+++ trunk/LayoutTests/fast/shadow-dom/event-with-related-target.html        2015-09-28 22:11:08 UTC (rev 190288)
</span><span class="lines">@@ -0,0 +1,343 @@
</span><ins>+&lt;!DOCTYPE html&gt;
+&lt;html&gt;
+&lt;head&gt;
+    &lt;title&gt;Shadow DOM: Firing an event with relatedTarget inside a shadow tree&lt;/title&gt;
+    &lt;meta name=&quot;author&quot; title=&quot;Ryosuke Niwa&quot; href=&quot;mailto:rniwa@webkit.org&quot;&gt;
+    &lt;meta name=&quot;assert&quot; content=&quot;The retargeting algorithm is used to determine relative targets&quot;&gt;
+    &lt;link rel=&quot;help&quot; href=&quot;https://w3c.github.io/webcomponents/spec/shadow/#retargeting-relatedtarget&quot;&gt;
+    &lt;script src=&quot;../../resources/testharness.js&quot;&gt;&lt;/script&gt;
+    &lt;script src=&quot;../../resources/testharnessreport.js&quot;&gt;&lt;/script&gt;
+    &lt;link rel='stylesheet' href='../../resources/testharness.css'&gt;
+&lt;/head&gt;
+&lt;body&gt;
+    &lt;div id=&quot;log&quot;&gt;&lt;/div&gt;
+    &lt;script&gt;
+
+        function dispatchEventWithLog(shadow, target, event) {
+            var eventPath = [];
+            var relatedTargets = [];
+
+            var attachedNodes = [];
+            for (var nodeKey in shadow) {
+                var startingNode = shadow[nodeKey];
+                for (var node = startingNode; node; node = node.parentNode) {
+                    if (attachedNodes.indexOf(node) &gt;= 0)
+                        continue;
+                    attachedNodes.push(node);
+                    node.addEventListener(event.type, (function (event) {
+                        eventPath.push(this.label);
+                        relatedTargets.push(event.relatedTarget.label);
+                    }).bind(node));
+                }
+            }
+
+            target.dispatchEvent(event);
+
+            return {eventPath: eventPath, relatedTargets: relatedTargets};
+        }
+
+        /*
+        -SR: ShadowRoot  -S: Slot
+        A ------------------------------- A-SR
+        + B ------------ B-SR             + A1 --- A1-SR
+          + C            + B1 --- B1-SR   + A2-S   + A1a
+          + D --- D-SR   + B1a    + B1b --- B1b-SR
+                  + D1            + B1c-S   + B1b1
+                                            + B1b2
+        */
+        function createTestTree(mode) {
+            var namedNodes = {};
+
+            function element(name) {
+                var element = document.createElement(name.indexOf('-S') &gt; 0 ? 'slot' : 'div');
+                element.label = name;
+                namedNodes[name] = element;
+                for (var i = 1; i &lt; arguments.length; i++) {
+                    var item = arguments[i];
+                    if (typeof(item) == 'function')
+                        item(element);
+                    else
+                        element.appendChild(item);
+                }
+                return element;
+            }
+
+            function shadow(name) {
+                var children = [];
+                for (var i = 1; i &lt; arguments.length; i++)
+                    children.push(arguments[i]);
+                return function (element) {
+                    var shadowRoot = element.attachShadow({mode: mode});
+                    shadowRoot.label = name;
+                    namedNodes[name] = shadowRoot;
+                    for (var child of children)
+                        shadowRoot.appendChild(child);
+                }
+            }
+
+            var host = element('A',
+                shadow('A-SR',
+                    element('A1',
+                        shadow('A1-SR',
+                            element('A1a'))),
+                    element('A2-S')
+                ),
+                element('B',
+                    shadow('B-SR',
+                        element('B1',
+                            shadow('B1-SR',
+                                element('B1b',
+                                    shadow('B1b-SR',
+                                        element('B1b1'),
+                                        element('B1b2'))),
+                                element('B1c-S')),
+                            element('B1a'))),
+                    element('C'),
+                    element('D',
+                        shadow('D-SR',
+                            element('D1')))));
+
+            return namedNodes;
+        }
+
+        /*
+        -SR: ShadowRoot  -S: Slot  target: (~)  relatedTarget: [~]  *: indicates start  digit: event path order
+        A (8) --------------------------------------------- A-SR (7)
+        + B (5) [5-8] --- B-SR (4)                          + A1 -------- A1-SR
+          + C             + B1 (3) [*; 0-4] --- B1-SR (2)   + A2-S (6)    + A1a
+          + D --- D-SR      + B1a (*; 0)        + B1b [1,2] --- B1b-SR
+                  + D1                          + B1c-S (1)     + B1b1
+                                                                + B1b2
+        */
+        function testEventAtB1aWithB1a(mode) {
+            test(function () {
+                var nodes = createTestTree(mode);
+
+                log = dispatchEventWithLog(nodes, nodes.B1a, new MouseEvent('foo', {bubbles: true, relatedTarget: nodes.B1}));
+
+                assert_array_equals(log.eventPath,
+                    ['B1a', 'B1c-S', 'B1-SR', 'B1', 'B-SR', 'B', 'A2-S', 'A-SR', 'A'], 'The event path must be correct.');
+                assert_array_equals(log.relatedTargets,
+                    ['B1',  'B1',    'B1',    'B1', 'B1',   'B', 'B',    'B',    'B'], 'The related targets must be correct.');
+
+            }, 'Firing an event at B1a with relatedNode at B1 with ' + mode + ' mode shadow trees');
+        }
+
+        testEventAtB1aWithB1a('open');
+        testEventAtB1aWithB1a('closed');
+
+        /*
+        -SR: ShadowRoot  -S: Slot  target: (~)  relatedTarget: [~]  *: indicates start  digit: event path order
+        A (8) -------------------------------------------- A-SR (7)
+        + B (5) [5-8] --- B-SR (4)                         + A1 ------ A1-SR
+          + C             + B1 (3) [0,3-4] --- B1-SR (2)   + A2-S (6)  + A1a
+          + D --- D-SR      + B1a (*; 0)       + B1b [1,2] --- B1b-SR
+                  + D1                         + B1c-S (1)     + B1b1 [*]
+                                                               + B1b2
+        */
+        function testEventAtB1aWithB1b1(mode) {
+            test(function () {
+                var nodes = createTestTree(mode);
+
+                log = dispatchEventWithLog(nodes, nodes.B1a, new MouseEvent('foo', {bubbles: true, relatedTarget: nodes.B1b1}));
+
+                assert_array_equals(log.eventPath,
+                    ['B1a', 'B1c-S', 'B1-SR', 'B1', 'B-SR', 'B', 'A2-S', 'A-SR', 'A'], 'The event path must be correct.');
+                assert_array_equals(log.relatedTargets,
+                    ['B1',  'B1b',   'B1b',   'B1', 'B1',   'B', 'B',    'B',    'B'], 'The related targets must be correct.');
+
+            }, 'Firing an event at B1a with relatedNode at B1b1 with ' + mode + ' mode shadow trees');
+        }
+
+        testEventAtB1aWithB1b1('open');
+        testEventAtB1aWithB1b1('closed');
+
+        /*
+        -SR: ShadowRoot  -S: Slot  target: (~)  relatedTarget: [~]  *: indicates start  digit: event path order
+        A (9) ------------------------------------------------------- A-SR (8)
+        + B (6) [6-9] --- B-SR (5)                                    + A1 ------ A1-SR
+          + C             + B1 (4) --------- B1-SR (3)                + A2-S (7)  + A1a
+          + D --- D-SR      + B1a [*; 0-5]   + B1b (2) --- B1b-SR (1)
+                  + D1                       + B1c-S       + B1b1 (*; 0)
+                                                           + B1b2
+        */
+        function testEventAtB1b1WithB1a(mode) {
+            test(function () {
+                var nodes = createTestTree(mode);
+
+                log = dispatchEventWithLog(nodes, nodes.B1b1, new MouseEvent('foo', {bubbles: true, relatedTarget: nodes.B1a}));
+
+                assert_array_equals(log.eventPath,
+                    ['B1b1', 'B1b-SR', 'B1b', 'B1-SR', 'B1', 'B-SR', 'B', 'A2-S', 'A-SR', 'A'], 'The event path must be correct.');
+                assert_array_equals(log.relatedTargets,
+                    ['B1a',  'B1a',    'B1a', 'B1a',   'B1a', 'B1a', 'B', 'B',    'B',    'B'], 'The related targets must be correct.');
+
+            }, 'Firing an event at B1b1 with relatedNode at B1a with ' + mode + ' mode shadow trees');
+        }
+
+        testEventAtB1b1WithB1a('open');
+        testEventAtB1b1WithB1a('closed');
+
+        /*
+        -SR: ShadowRoot  -S: Slot  target: (~)  relatedTarget: [~]  *: indicates start  digit: event path order
+        A (8) -------------------------------------------------- A-SR (7)
+        + B (5) -------------- B-SR (4)                          + A1 -------- A1-SR
+          + C                  + B1 (3) [*; 0-4] --- B1-SR (2)   + A2-S (6)    + A1a
+          + D [0-8] --- D-SR     + B1a (*; 0)        + B1b ------ B1b-SR
+                        + D1 [*]                     + B1c-S (1)  + B1b1
+                                                                  + B1b2
+        */
+        function testEventAtB1aWithD1(mode) {
+            test(function () {
+                var nodes = createTestTree(mode);
+
+                log = dispatchEventWithLog(nodes, nodes.B1a, new MouseEvent('foo', {bubbles: true, relatedTarget: nodes.D1}));
+
+                assert_array_equals(log.eventPath,
+                    ['B1a', 'B1c-S', 'B1-SR', 'B1', 'B-SR', 'B', 'A2-S', 'A-SR', 'A'], 'The event path must be correct.');
+                assert_array_equals(log.relatedTargets,
+                    ['D', 'D', 'D', 'D', 'D', 'D', 'D', 'D', 'D'], 'The related targets must be correct.');
+
+            }, 'Firing an event at B1a with relatedNode at D1 with ' + mode + ' mode shadow trees');
+        }
+
+        testEventAtB1aWithD1('open');
+        testEventAtB1aWithD1('closed');
+
+        /*
+        -SR: ShadowRoot  -S: Slot  target: (~)  relatedTarget: [~]  *: indicates start  digit: event path order
+        A (6) ----------------------------------- A-SR (5)
+        + B (3) [0] ----------- B-SR              + A1 ------ A1-SR
+          + C                   + B1 ----- B1-SR  + A2-S (4)  + A1a
+          + D (2) --- D-SR (1)  + B1a [*]  + B1b --- B1b-SR
+                        + D1 (*; 0)         + B1c-S   + B1b1
+                                                      + B1b2
+        */
+        function testEventAtD1WithB1a(mode) {
+            test(function () {
+                var nodes = createTestTree(mode);
+
+                log = dispatchEventWithLog(nodes, nodes.D1, new MouseEvent('foo', {bubbles: true, relatedTarget: nodes.B1a}));
+
+                assert_array_equals(log.eventPath,
+                    ['D1', 'D-SR', 'D', 'B', 'A2-S', 'A-SR', 'A'], 'The event path must be correct.');
+                assert_array_equals(log.relatedTargets,
+                    ['B', 'B', 'B', 'B', 'B', 'B', 'B'], 'The related targets must be correct.');
+
+            }, 'Firing an event at D1 with relatedNode at B1a with ' + mode + ' mode shadow trees');
+        }
+
+        testEventAtD1WithB1a('open');
+        testEventAtD1WithB1a('closed');
+
+        /*
+        -SR: ShadowRoot  -S: Slot  target: (~)  relatedTarget: [~]  *: indicates start  digit: event path order
+        A (8) [0-5,8] ---------------------------------------- A-SR (7)
+        + B (5)  ------- B-SR (4)                              + A1 [6,7] --- A1-SR
+          + C            + B1 (3) ----- B1-SR (2)              + A2-S (6)     + A1a [*]
+          + D --- D-SR   + B1a (*; 0)   + B1b ------- B1b-SR
+                  + D1                  + B1c-S (1)   + B1b1
+                                                      + B1b2
+        */
+        function testEventAtB1aWithA1a(mode) {
+            test(function () {
+                var nodes = createTestTree(mode);
+
+                log = dispatchEventWithLog(nodes, nodes.B1a, new MouseEvent('foo', {bubbles: true, relatedTarget: nodes.A1a}));
+
+                assert_array_equals(log.eventPath,
+                    ['B1a', 'B1c-S', 'B1-SR', 'B1', 'B-SR', 'B', 'A2-S', 'A-SR', 'A'], 'The event path must be correct.');
+                assert_array_equals(log.relatedTargets,
+                    ['A',   'A',     'A',     'A',   'A',   'A', 'A1',   'A1',   'A'], 'The related targets must be correct.');
+
+            }, 'Firing an event at B1a with relatedNode at A1a with ' + mode + ' mode shadow trees');
+        }
+
+        testEventAtB1aWithA1a('open');
+        testEventAtB1aWithA1a('closed');
+
+        /*
+        -SR: ShadowRoot  -S: Slot  target: (~)  relatedTarget: [~]  *: indicates start  digit: event path order
+        A (4) ----------------------------------------- A-SR (3)
+        + B [0-4]  ----- B-SR                           + A1 (2) --- A1-SR (1)
+          + C            + B1 ------- B1-SR             + A2-S       + A1a (*; 0)
+          + D --- D-SR     + B1a [*]  + B1b --- B1b-SR
+                  + D1                + B1c-S   + B1b1
+                                                + B1b2
+        */
+        function testEventAtA1aWithB1a(mode) {
+            test(function () {
+                var nodes = createTestTree(mode);
+
+                log = dispatchEventWithLog(nodes, nodes.A1a, new MouseEvent('foo', {bubbles: true, relatedTarget: nodes.B1a}));
+
+                assert_array_equals(log.eventPath,
+                    ['A1a', 'A1-SR', 'A1', 'A-SR', 'A'], 'The event path must be correct.');
+                assert_array_equals(log.relatedTargets,
+                    ['B', 'B', 'B', 'B', 'B'], 'The related targets must be correct.');
+
+            }, 'Firing an event at B1a with relatedNode at A1a with ' + mode + ' mode shadow trees');
+        }
+
+        testEventAtA1aWithB1a('open');
+        testEventAtA1aWithB1a('closed');
+
+        /*
+        -SR: ShadowRoot  -S: Slot  target: (~)  relatedTarget: [~]  *: indicates start  digit: event path order
+        A (8) ----------------------------------- A-SR (7)
+        + B (5)  ----- B-SR (4)                   + A2-S (6)
+          + C          + B1 (3) ----- B1-SR (2)
+          + D --- D-SR   + B1a (*; 0) + B1b ------- B1b-SR
+                  + D1                + B1c-S (1)   + B1b1
+                                                    + B1b2
+        A1 [0-6] --- A1-SR
+                   + A1a [*]
+        */
+        function testEventAtB1aWithDetachedA1a(mode) {
+            test(function () {
+                var nodes = createTestTree(mode);
+
+                nodes['A-SR'].removeChild(nodes.A1);
+                log = dispatchEventWithLog(nodes, nodes.B1a, new MouseEvent('foo', {bubbles: true, relatedTarget: nodes.A1a}));
+
+                assert_array_equals(log.eventPath,
+                    ['B1a', 'B1c-S', 'B1-SR', 'B1', 'B-SR', 'B', 'A2-S', 'A-SR', 'A'], 'The event path must be correct.');
+                assert_array_equals(log.relatedTargets,
+                    ['A1', 'A1', 'A1', 'A1', 'A1', 'A1', 'A1', 'A1', 'A1'], 'The related targets must be correct.');
+
+            }, 'Firing an event at B1a with relatedNode at A1a with ' + mode + ' mode shadow trees');
+        }
+
+        testEventAtB1aWithDetachedA1a('open');
+        testEventAtB1aWithDetachedA1a('closed');
+
+        /*
+        -SR: ShadowRoot  -S: Slot  target: (~)  relatedTarget: [~]  *: indicates start  digit: event path order
+        A ----------------------------------- A-SR
+        + B [0-3]  ----- B-SR                 + A2-S
+          + C            + B1 -------- B1-SR
+          + D --- D-SR     + B1a [*]   + B1b --- B1b-SR
+                  + D1                 + B1c-S   + B1b1
+                                                 + B1b2
+        A1 (2) --- A1-SR (1)
+                   + A1a (*; 0)
+        */
+        function testEventAtA1aWithDetachedB1a(mode) {
+            test(function () {
+                var nodes = createTestTree(mode);
+
+                nodes['A-SR'].removeChild(nodes.A1);
+                log = dispatchEventWithLog(nodes, nodes.A1a, new MouseEvent('foo', {bubbles: true, relatedTarget: nodes.B1a}));
+
+                assert_array_equals(log.eventPath,      ['A1a', 'A1-SR', 'A1'], 'The event path must be correct.');
+                assert_array_equals(log.relatedTargets, ['B',   'B',     'B' ], 'The related targets must be correct.');
+
+            }, 'Firing an event at B1a with relatedNode at A1a with ' + mode + ' mode shadow trees');
+        }
+
+        testEventAtA1aWithDetachedB1a('open');
+        testEventAtA1aWithDetachedB1a('closed');
+
+    &lt;/script&gt;
+    &lt;/body&gt;
+&lt;/html&gt;
</ins></span></pre></div>
<a id="trunkSourceWebCoreChangeLog"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebCore/ChangeLog (190287 => 190288)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebCore/ChangeLog        2015-09-28 22:08:55 UTC (rev 190287)
+++ trunk/Source/WebCore/ChangeLog        2015-09-28 22:11:08 UTC (rev 190288)
</span><span class="lines">@@ -1,3 +1,63 @@
</span><ins>+2015-09-28  Ryosuke Niwa  &lt;rniwa@webkit.org&gt;
+
+        relatedNode should be retargeted respecting slots
+        https://bugs.webkit.org/show_bug.cgi?id=149591
+
+        Reviewed by Antti Koivisto.
+
+        This patch retargets relatedNode with respect to shadow boundaries after r190214 as specified in
+        https://w3c.github.io/webcomponents/spec/shadow/#retargeting-relatedtarget
+
+        Naively implementing the spec'ed behavior can result in O(n^2) behavior since we need to find the common tree scope
+        ancestor for each target in the event path. This patch avoids this by implementing an O(1) incremental update step
+        for when target's tree scope changes in the event path. See the description for moveToNewTreeScope below.
+
+        Test: fast/shadow-dom/event-with-related-target.html
+
+        * dom/EventContext.h: Replaced toMouseOrFocusEventContext by downcast&lt;MouseOrFocusEventContext&gt;.
+        * dom/EventDispatcher.cpp:
+        (WebCore::EventRelatedNodeResolver): Removed the code for relatedNode. This class is now only used for touch events.
+        (WebCore::EventPath): Added m_event as a member variable.
+
+        (WebCore::RelatedNodeRetargeter): Added.
+
+        (WebCore::RelatedNodeRetargeter::RelatedNodeRetargeter): Does the initial retargeting of relatedNode. When the
+        tree scope of relatedNode and the target are the same, we immediately exit without collecting ancestor tree scopes
+        of relatedNode as an optimization. We also special case when the relatedNode and the target are in two different
+        documents (relatedNode should be nullptr) and when one is in document and the other one is not in the document
+        (relatedNode should be the host of the outermost shadow root). Otherwise we have to do the real work by collecting
+        all tree scope ancestors and walking downwards from the document tree scope (note target and relatedNode share the
+        same document scope here since we would have exited early otherwise).
+
+        (WebCore::RelatedNodeRetargeter::currentNode): Returned relatedNode retargeted for the current tree scope.
+
+        (WebCore::RelatedNodeRetargeter::moveToNewTreeScope): Moves to a new tree scope. If the original target and
+        relatedNode were in different trees, there is nothing to be done. Note that we can only move out of a shadow root
+        to its host or move into a slot so newTreeScope (current target's tree scope) and previousTreeScope (previous
+        target's tree scope) must have a child-parent relationship.
+
+        If previousTreeScope did not contain the retargeted relatedNode, then neither can its child shadow trees. Thus,
+        there is nothing to be done when moving into a slot in this case. If we're moving out of previousTreeScope, then
+        newTreeScope may contain the retargeted relatedNode but that still doesn't require any work. So we exit early in
+        both cases.
+
+        Otherwise (previousTreeScope contained retargeted relatedNode), if we're moving out of a child shadow root
+        (previousTreeScope) then relatedNode should also move to previousTreeScope's shadow host since previousTreeScope
+        is a direct-child shadow tree of newTreeScope and previousTreeScope's shadow host resides in newTreeScope.
+
+        If we're moving into a child shadow root via a slot, then there are three possibilities: relatedNode is in
+        previousTreeScope, newTreeScope and its child shadow trees, or newTreeScope's sibling tree scopes and its children.
+        If it is in previousTreeScope (m_lowestCommonAncestorIndex is zero) or in newTreeScope's sibling, then
+        previousTreeScope is the lowest common tree scope ancestor so there is nothing to be done. If relatedNode is in
+        newTreeScope, then the retargeted relatedNode is either the shadow host of the shadow tree that contains
+        relatedNode or relatedNode itself.
+
+        (WebCore::RelatedNodeRetargeter::checkConsistency): Finds the retargeted relatedNode in the simplest way to verify
+        the correctness of the algorithm. We can disable this consistency check if it slows down debug builds too much.
+        (WebCore::RelatedNodeRetargeter::nodeInLowestCommonAncestor): Finds the 
+        (WebCore::RelatedNodeRetargeter::collectTreeScopes):
+        (WebCore::EventPath::setRelatedTarget): Rewritten using RelatedNodeRetargeter.
+
</ins><span class="cx"> 2015-09-28  Alex Christensen  &lt;achristensen@webkit.org&gt;
</span><span class="cx"> 
</span><span class="cx">         Build WK1 with CMake on Mac
</span></span></pre></div>
<a id="trunkSourceWebCoredomEventContexth"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebCore/dom/EventContext.h (190287 => 190288)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebCore/dom/EventContext.h        2015-09-28 22:08:55 UTC (rev 190287)
+++ trunk/Source/WebCore/dom/EventContext.h        2015-09-28 22:11:08 UTC (rev 190288)
</span><span class="lines">@@ -76,13 +76,7 @@
</span><span class="cx">     RefPtr&lt;EventTarget&gt; m_relatedTarget;
</span><span class="cx"> };
</span><span class="cx"> 
</span><del>-inline MouseOrFocusEventContext&amp; toMouseOrFocusEventContext(EventContext&amp; eventContext)
-{
-    ASSERT_WITH_SECURITY_IMPLICATION(eventContext.isMouseOrFocusEventContext());
-    return static_cast&lt;MouseOrFocusEventContext&amp;&gt;(eventContext);
-}
</del><span class="cx"> 
</span><del>-
</del><span class="cx"> #if ENABLE(TOUCH_EVENTS) &amp;&amp; !PLATFORM(IOS)
</span><span class="cx"> class TouchEventContext final : public EventContext {
</span><span class="cx"> public:
</span><span class="lines">@@ -162,4 +156,8 @@
</span><span class="cx"> 
</span><span class="cx"> }
</span><span class="cx"> 
</span><ins>+SPECIALIZE_TYPE_TRAITS_BEGIN(WebCore::MouseOrFocusEventContext)
+static bool isType(const WebCore::EventContext&amp; context) { return context.isMouseOrFocusEventContext(); }
+SPECIALIZE_TYPE_TRAITS_END()
+
</ins><span class="cx"> #endif // EventContext_h
</span></span></pre></div>
<a id="trunkSourceWebCoredomEventDispatchercpp"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebCore/dom/EventDispatcher.cpp (190287 => 190288)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebCore/dom/EventDispatcher.cpp        2015-09-28 22:08:55 UTC (rev 190287)
+++ trunk/Source/WebCore/dom/EventDispatcher.cpp        2015-09-28 22:11:08 UTC (rev 190288)
</span><span class="lines">@@ -2,7 +2,7 @@
</span><span class="cx">  * Copyright (C) 1999 Lars Knoll (knoll@kde.org)
</span><span class="cx">  *           (C) 1999 Antti Koivisto (koivisto@kde.org)
</span><span class="cx">  *           (C) 2001 Dirk Mueller (mueller@kde.org)
</span><del>- * Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2013 Apple Inc. All rights reserved.
</del><ins>+ * Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2013, 2015 Apple Inc. All rights reserved.
</ins><span class="cx">  * Copyright (C) 2008 Nokia Corporation and/or its subsidiary(-ies)
</span><span class="cx">  * Copyright (C) 2009 Torch Mobile Inc. All rights reserved. (http://www.torchmobile.com/)
</span><span class="cx">  * Copyright (C) 2010, 2011, 2012, 2013 Google Inc. All rights reserved.
</span><span class="lines">@@ -101,24 +101,14 @@
</span><span class="cx">     void updateTouchListsInEventPath(const TouchList*, TouchEventContext::TouchListType);
</span><span class="cx"> #endif
</span><span class="cx"> 
</span><ins>+    Event&amp; m_event;
</ins><span class="cx">     Vector&lt;std::unique_ptr&lt;EventContext&gt;, 32&gt; m_path;
</span><span class="cx"> };
</span><span class="cx"> 
</span><ins>+#if ENABLE(TOUCH_EVENTS) &amp;&amp; !PLATFORM(IOS)
+// FIXME: Use RelatedNodeRetargeter instead.
</ins><span class="cx"> class EventRelatedNodeResolver {
</span><span class="cx"> public:
</span><del>-    EventRelatedNodeResolver(Node&amp; relatedNode)
-        : m_relatedNode(relatedNode)
-        , m_relatedNodeTreeScope(relatedNode.treeScope())
-        , m_relatedNodeInCurrentTreeScope(nullptr)
-        , m_currentTreeScope(nullptr)
-#if ENABLE(TOUCH_EVENTS) &amp;&amp; !PLATFORM(IOS)
-        , m_touch(0)
-        , m_touchListType(TouchEventContext::NotTouchList)
-#endif
-    {
-    }
-
-#if ENABLE(TOUCH_EVENTS) &amp;&amp; !PLATFORM(IOS)
</del><span class="cx">     EventRelatedNodeResolver(Touch&amp; touch, TouchEventContext::TouchListType touchListType)
</span><span class="cx">         : m_relatedNode(*touch.target()-&gt;toNode())
</span><span class="cx">         , m_relatedNodeTreeScope(m_relatedNode.treeScope())
</span><span class="lines">@@ -129,12 +119,9 @@
</span><span class="cx">     {
</span><span class="cx">         ASSERT(touch.target()-&gt;toNode());
</span><span class="cx">     }
</span><del>-#endif
</del><span class="cx"> 
</span><del>-#if ENABLE(TOUCH_EVENTS) &amp;&amp; !PLATFORM(IOS)
</del><span class="cx">     Touch* touch() const { return m_touch; }
</span><span class="cx">     TouchEventContext::TouchListType touchListType() const { return m_touchListType; }
</span><del>-#endif
</del><span class="cx"> 
</span><span class="cx">     Node* moveToParentOrShadowHost(Node&amp; newTarget)
</span><span class="cx">     {
</span><span class="lines">@@ -197,11 +184,10 @@
</span><span class="cx">     const TreeScope&amp; m_relatedNodeTreeScope;
</span><span class="cx">     Node* m_relatedNodeInCurrentTreeScope;
</span><span class="cx">     TreeScope* m_currentTreeScope;
</span><del>-#if ENABLE(TOUCH_EVENTS) &amp;&amp; !PLATFORM(IOS)
</del><span class="cx">     Touch* m_touch;
</span><span class="cx">     TouchEventContext::TouchListType m_touchListType;
</span><ins>+};
</ins><span class="cx"> #endif
</span><del>-};
</del><span class="cx"> 
</span><span class="cx"> inline EventTarget* eventTargetRespectingTargetRules(Node&amp; referenceNode)
</span><span class="cx"> {
</span><span class="lines">@@ -415,6 +401,7 @@
</span><span class="cx"> }
</span><span class="cx"> 
</span><span class="cx"> EventPath::EventPath(Node&amp; originalTarget, Event&amp; event)
</span><ins>+    : m_event(event)
</ins><span class="cx"> {
</span><span class="cx"> #if ENABLE(SHADOW_DOM)
</span><span class="cx">     Vector&lt;EventTarget*, 16&gt; targetStack;
</span><span class="lines">@@ -513,30 +500,170 @@
</span><span class="cx"> }
</span><span class="cx"> #endif
</span><span class="cx"> 
</span><ins>+class RelatedNodeRetargeter {
+public:
+    RelatedNodeRetargeter(Node&amp; relatedNode, TreeScope&amp; targetTreeScope)
+        : m_relatedNode(relatedNode)
+        , m_retargetedRelatedNode(&amp;relatedNode)
+    {
+        TreeScope* currentTreeScope = &amp;m_relatedNode.treeScope();
+        if (LIKELY(currentTreeScope == &amp;targetTreeScope))
+            return;
+
+        if (&amp;currentTreeScope-&gt;documentScope() != &amp;targetTreeScope.documentScope()) {
+            m_hasDifferentTreeRoot = true;
+            m_retargetedRelatedNode = nullptr;
+            return;
+        }
+        if (relatedNode.inDocument() != targetTreeScope.rootNode().inDocument()) {
+            m_hasDifferentTreeRoot = true;
+            while (m_retargetedRelatedNode-&gt;isInShadowTree())
+                m_retargetedRelatedNode = downcast&lt;ShadowRoot&gt;(m_retargetedRelatedNode-&gt;treeScope().rootNode()).host();
+            return;
+        }
+
+        collectTreeScopes();
+
+        // FIXME: We should collect this while constructing the event path.
+        Vector&lt;TreeScope*, 8&gt; targetTreeScopeAncestors;
+        for (TreeScope* currentTreeScope = &amp;targetTreeScope; currentTreeScope; currentTreeScope = currentTreeScope-&gt;parentTreeScope())
+            targetTreeScopeAncestors.append(currentTreeScope);
+        ASSERT_WITH_SECURITY_IMPLICATION(!targetTreeScopeAncestors.isEmpty());
+
+        unsigned i = m_ancestorTreeScopes.size();
+        unsigned j = targetTreeScopeAncestors.size();
+        ASSERT_WITH_SECURITY_IMPLICATION(m_ancestorTreeScopes.last() == targetTreeScopeAncestors.last());
+        while (m_ancestorTreeScopes[i - 1] == targetTreeScopeAncestors[j - 1]) {
+            i--;
+            j--;
+            if (!i || !j)
+                break;
+        }
+
+        m_lowestCommonAncestorIndex = i;
+        m_retargetedRelatedNode = nodeInLowestCommonAncestor();
+    }
+
+    Node* currentNode(TreeScope&amp; currentTreeScope)
+    {
+        checkConsistency(currentTreeScope);
+        return m_retargetedRelatedNode;
+    }
+
+    void moveToNewTreeScope(TreeScope* previousTreeScope, TreeScope&amp; newTreeScope)
+    {
+        if (m_hasDifferentTreeRoot)
+            return;
+
+        auto&amp; currentRelatedNodeScope = m_retargetedRelatedNode-&gt;treeScope();
+        if (previousTreeScope != &amp;currentRelatedNodeScope) {
+            // currentRelatedNode is still outside our shadow tree. New tree scope may contain currentRelatedNode
+            // but there is no need to re-target it. Moving into a slot (thereby a deeper shadow tree) doesn't matter.
+            return;
+        }
+
+        bool enteredSlot = newTreeScope.parentTreeScope() == previousTreeScope;
+        if (enteredSlot) {
+            if (m_lowestCommonAncestorIndex) {
+                if (m_ancestorTreeScopes.isEmpty())
+                    collectTreeScopes();
+                bool relatedNodeIsInSlot = m_ancestorTreeScopes[m_lowestCommonAncestorIndex - 1] == &amp;newTreeScope;
+                if (relatedNodeIsInSlot) {
+                    m_lowestCommonAncestorIndex--;
+                    m_retargetedRelatedNode = nodeInLowestCommonAncestor();
+                    ASSERT(&amp;newTreeScope == &amp;m_retargetedRelatedNode-&gt;treeScope());
+                }
+            } else
+                ASSERT(m_retargetedRelatedNode == &amp;m_relatedNode);
+        } else {
+            ASSERT(previousTreeScope-&gt;parentTreeScope() == &amp;newTreeScope);
+            m_lowestCommonAncestorIndex++;
+            ASSERT_WITH_SECURITY_IMPLICATION(m_ancestorTreeScopes.isEmpty() || m_lowestCommonAncestorIndex &lt; m_ancestorTreeScopes.size());
+            m_retargetedRelatedNode = downcast&lt;ShadowRoot&gt;(currentRelatedNodeScope.rootNode()).host();
+            ASSERT(&amp;newTreeScope == &amp;m_retargetedRelatedNode-&gt;treeScope());
+        }
+    }
+
+    void checkConsistency(TreeScope&amp; currentTreeScope)
+    {
+#if !ASSERT_DISABLED
+        for (auto* relatedNodeScope = &amp;m_relatedNode.treeScope(); relatedNodeScope; relatedNodeScope = relatedNodeScope-&gt;parentTreeScope()) {
+            for (auto* targetScope = &amp;currentTreeScope; targetScope; targetScope = targetScope-&gt;parentTreeScope()) {
+                if (targetScope == relatedNodeScope) {
+                    ASSERT(&amp;m_retargetedRelatedNode-&gt;treeScope() == relatedNodeScope);
+                    return;
+                }
+            }
+        }
+        ASSERT(!m_retargetedRelatedNode);
+#else
+        UNUSED_PARAM(currentTreeScope);
+#endif
+    }
+
+private:
+    Node* nodeInLowestCommonAncestor()
+    {
+        if (!m_lowestCommonAncestorIndex)
+            return &amp;m_relatedNode;
+        auto&amp; rootNode = m_ancestorTreeScopes[m_lowestCommonAncestorIndex - 1]-&gt;rootNode();
+        return downcast&lt;ShadowRoot&gt;(rootNode).host();
+    }
+
+    void collectTreeScopes()
+    {
+        ASSERT(m_ancestorTreeScopes.isEmpty());
+        for (TreeScope* currentTreeScope = &amp;m_relatedNode.treeScope(); currentTreeScope; currentTreeScope = currentTreeScope-&gt;parentTreeScope())
+            m_ancestorTreeScopes.append(currentTreeScope);
+        ASSERT_WITH_SECURITY_IMPLICATION(!m_ancestorTreeScopes.isEmpty());
+    }
+
+    Node&amp; m_relatedNode;
+    Node* m_retargetedRelatedNode;
+    Vector&lt;TreeScope*, 8&gt; m_ancestorTreeScopes;
+    unsigned m_lowestCommonAncestorIndex { 0 };
+    bool m_hasDifferentTreeRoot { false };
+};
+
</ins><span class="cx"> void EventPath::setRelatedTarget(Node&amp; origin, EventTarget&amp; relatedTarget)
</span><span class="cx"> {
</span><ins>+    UNUSED_PARAM(origin);
</ins><span class="cx">     Node* relatedNode = relatedTarget.toNode();
</span><del>-    if (!relatedNode)
</del><ins>+    if (!relatedNode || m_path.isEmpty())
</ins><span class="cx">         return;
</span><span class="cx"> 
</span><del>-    EventRelatedNodeResolver resolver(*relatedNode);
</del><ins>+    RelatedNodeRetargeter retargeter(*relatedNode, downcast&lt;MouseOrFocusEventContext&gt;(*m_path[0]).node()-&gt;treeScope());
</ins><span class="cx"> 
</span><span class="cx">     bool originIsRelatedTarget = &amp;origin == relatedNode;
</span><ins>+    // FIXME: We should add a new flag on Event instead.
+    bool shouldTrimEventPath = m_event.type() == eventNames().mouseoverEvent
+        || m_event.type() == eventNames().mousemoveEvent
+        || m_event.type() == eventNames().mouseoutEvent;
</ins><span class="cx">     Node&amp; rootNodeInOriginTreeScope = origin.treeScope().rootNode();
</span><ins>+    TreeScope* previousTreeScope = nullptr;
+    size_t originalEventPathSize = m_path.size();
+    for (unsigned contextIndex = 0; contextIndex &lt; originalEventPathSize; contextIndex++) {
+        auto&amp; context = downcast&lt;MouseOrFocusEventContext&gt;(*m_path[contextIndex]);
</ins><span class="cx"> 
</span><del>-    size_t eventPathSize = m_path.size();
-    size_t i = 0;
-    while (i &lt; eventPathSize) {
-        Node* contextNode = m_path[i]-&gt;node();
-        Node* currentRelatedNode = resolver.moveToParentOrShadowHost(*contextNode);
-        if (!originIsRelatedTarget &amp;&amp; m_path[i]-&gt;target() == currentRelatedNode)
</del><ins>+        TreeScope&amp; currentTreeScope = context.node()-&gt;treeScope();
+        if (UNLIKELY(previousTreeScope &amp;&amp; &amp;currentTreeScope != previousTreeScope))
+            retargeter.moveToNewTreeScope(previousTreeScope, currentTreeScope);
+
+        Node* currentRelatedNode = retargeter.currentNode(currentTreeScope);
+        if (UNLIKELY(shouldTrimEventPath &amp;&amp; !originIsRelatedTarget &amp;&amp; context.target() == currentRelatedNode)) {
+            m_path.shrink(contextIndex);
</ins><span class="cx">             break;
</span><del>-        toMouseOrFocusEventContext(*m_path[i]).setRelatedTarget(currentRelatedNode);
-        i++;
-        if (originIsRelatedTarget &amp;&amp; &amp;rootNodeInOriginTreeScope == contextNode)
</del><ins>+        }
+
+        context.setRelatedTarget(currentRelatedNode);
+
+        if (UNLIKELY(shouldTrimEventPath &amp;&amp; originIsRelatedTarget &amp;&amp; context.node() == &amp;rootNodeInOriginTreeScope)) {
+            m_path.shrink(contextIndex + 1);
</ins><span class="cx">             break;
</span><ins>+        }
+
+        previousTreeScope = &amp;currentTreeScope;
</ins><span class="cx">     }
</span><del>-    m_path.shrink(i);
</del><span class="cx"> }
</span><span class="cx"> 
</span><span class="cx"> bool EventPath::hasEventListeners(const AtomicString&amp; eventType) const
</span></span></pre>
</div>
</div>

</body>
</html>