REGRESSION (r157328): popover to check into flight ba.com dismisses instantly when...
authorrniwa@webkit.org <rniwa@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 22 Apr 2014 23:25:59 +0000 (23:25 +0000)
committerrniwa@webkit.org <rniwa@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 22 Apr 2014 23:25:59 +0000 (23:25 +0000)
https://bugs.webkit.org/show_bug.cgi?id=131949

Reviewed by Darin Adler.

Source/WebCore:
The regression was caused by two bugs:
1. The event didn't stop propagating itself even when it should.
   If the related target is same as the event origin, the event propagation should stop when the event reaches
   the root of the related target's tree scope. Otherwise, it should stop when it reaches the related target.

2. Mouse event's related target exposed nodes inside a user-agent shadow DOM when the related target appeared
   inside the origin.

Fixed the bugs by re-introducing path shrinkage algorithm removed in r157328 into EventPath::setRelatedTarget
and adding an algorithm to determine the least common ancestor of the related target and the current target
in moveToParentOrShadowHost. The latter algorithm doesn't match the shadow DOM specification:
http://www.w3.org/TR/2013/WD-shadow-dom-20130514/
but it's good enough in terms of the Web exposed behavior as we don't support author defined insertion points.

Test: fast/events/shadow-event-path.html

* dom/EventDispatcher.cpp:
(WebCore::EventRelatedNodeResolver::moveToParentOrShadowHost):
(WebCore::EventRelatedNodeResolver::findHostOfTreeScopeInTargetTreeScope): Added.
(WebCore::EventDispatcher::dispatchEvent):
(WebCore::EventPath::setRelatedTarget):

LayoutTests:
Add a test that dumps the event target and the related target of every mouse event
when dispatched inside an input element inside a details element.

This catches the regression as well as other bugs I encountered while fixing the bug.

We need a WK2 specific results because WK1 mac results contain an extra fake mouse move event.

* fast/events/shadow-event-path-expected.txt: Added.
* fast/events/shadow-event-path.html: Added.
* platform/mac-wk2/fast/events/shadow-event-expected.txt: Added.

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@167689 268f45cc-cd09-0410-ab3c-d52691b4dbfc

LayoutTests/ChangeLog
LayoutTests/fast/events/shadow-event-path-expected.txt [new file with mode: 0644]
LayoutTests/fast/events/shadow-event-path.html [new file with mode: 0644]
LayoutTests/platform/mac-wk2/fast/events/shadow-event-path-expected.txt [new file with mode: 0644]
Source/WebCore/ChangeLog
Source/WebCore/dom/EventDispatcher.cpp

index 672bfb3..d6ba1e9 100644 (file)
@@ -1,5 +1,23 @@
 2014-04-22  Ryosuke Niwa  <rniwa@webkit.org>
 
+        REGRESSION (r157328): popover to check into flight ba.com dismisses instantly when focusing form
+        https://bugs.webkit.org/show_bug.cgi?id=131949
+
+        Reviewed by Darin Adler.
+
+        Add a test that dumps the event target and the related target of every mouse event
+        when dispatched inside an input element inside a details element.
+
+        This catches the regression as well as other bugs I encountered while fixing the bug.
+
+        We need a WK2 specific results because WK1 mac results contain an extra fake mouse move event.
+
+        * fast/events/shadow-event-path-expected.txt: Added.
+        * fast/events/shadow-event-path.html: Added.
+        * platform/mac-wk2/fast/events/shadow-event-expected.txt: Added.
+
+2014-04-22  Ryosuke Niwa  <rniwa@webkit.org>
+
         Rollout r156635 since the old behavior was intentional.
 
         * editing/caret/selection-with-caret-type-progress-expected.txt: Removed.
diff --git a/LayoutTests/fast/events/shadow-event-path-expected.txt b/LayoutTests/fast/events/shadow-event-path-expected.txt
new file mode 100644 (file)
index 0000000..96a9120
--- /dev/null
@@ -0,0 +1,338 @@
+This test records target and relatedTarget at each element while dispatching a mouse click event at an input element.
+
+
+Content:<div id="detailsContainer"><details><summary><div id="divInsideSummary"><input id="target" type="text" size="10"></div></summary></details></div>
+
+mouseover@input#target
+    target:input#target
+    relatedTarget:null
+
+mouseover@div#divInsideSummary
+    target:input#target
+    relatedTarget:null
+
+mouseover@summary
+    target:input#target
+    relatedTarget:null
+
+mouseover@details
+    target:input#target
+    relatedTarget:null
+
+mouseover@div#detailsContainer
+    target:input#target
+    relatedTarget:null
+
+mouseover@body
+    target:input#target
+    relatedTarget:null
+
+mouseover@html
+    target:input#target
+    relatedTarget:null
+
+mouseover@document
+    target:input#target
+    relatedTarget:null
+
+mouseover@window
+    target:input#target
+    relatedTarget:null
+
+mouseenter@input#target
+    target:input#target
+    relatedTarget:null
+
+mouseenter@div#divInsideSummary
+    target:div#divInsideSummary
+    relatedTarget:null
+
+mouseenter@summary
+    target:summary
+    relatedTarget:null
+
+mouseenter@details
+    target:details
+    relatedTarget:null
+
+mouseenter@div#detailsContainer
+    target:div#detailsContainer
+    relatedTarget:null
+
+mouseenter@body
+    target:body
+    relatedTarget:null
+
+mouseenter@html
+    target:html
+    relatedTarget:null
+
+mousemove@input#target
+    target:input#target
+    relatedTarget:null
+
+mousemove@div#divInsideSummary
+    target:input#target
+    relatedTarget:null
+
+mousemove@summary
+    target:input#target
+    relatedTarget:null
+
+mousemove@details
+    target:input#target
+    relatedTarget:null
+
+mousemove@div#detailsContainer
+    target:input#target
+    relatedTarget:null
+
+mousemove@body
+    target:input#target
+    relatedTarget:null
+
+mousemove@html
+    target:input#target
+    relatedTarget:null
+
+mousemove@document
+    target:input#target
+    relatedTarget:null
+
+mousemove@window
+    target:input#target
+    relatedTarget:null
+
+mouseout@input#target
+    target:input#target
+    relatedTarget:div#divInsideSummary
+
+mouseout@div#divInsideSummary
+    target:input#target
+    relatedTarget:div#divInsideSummary
+
+mouseout@summary
+    target:input#target
+    relatedTarget:div#divInsideSummary
+
+mouseout@details
+    target:input#target
+    relatedTarget:div#divInsideSummary
+
+mouseout@div#detailsContainer
+    target:input#target
+    relatedTarget:div#divInsideSummary
+
+mouseout@body
+    target:input#target
+    relatedTarget:div#divInsideSummary
+
+mouseout@html
+    target:input#target
+    relatedTarget:div#divInsideSummary
+
+mouseout@document
+    target:input#target
+    relatedTarget:div#divInsideSummary
+
+mouseout@window
+    target:input#target
+    relatedTarget:div#divInsideSummary
+
+mouseleave@input#target
+    target:input#target
+    relatedTarget:div#divInsideSummary
+
+mouseover@div#divInsideSummary
+    target:div#divInsideSummary
+    relatedTarget:input#target
+
+mouseover@summary
+    target:div#divInsideSummary
+    relatedTarget:input#target
+
+mouseover@details
+    target:div#divInsideSummary
+    relatedTarget:input#target
+
+mouseover@div#detailsContainer
+    target:div#divInsideSummary
+    relatedTarget:input#target
+
+mouseover@body
+    target:div#divInsideSummary
+    relatedTarget:input#target
+
+mouseover@html
+    target:div#divInsideSummary
+    relatedTarget:input#target
+
+mouseover@document
+    target:div#divInsideSummary
+    relatedTarget:input#target
+
+mouseover@window
+    target:div#divInsideSummary
+    relatedTarget:input#target
+
+mousemove@div#divInsideSummary
+    target:div#divInsideSummary
+    relatedTarget:null
+
+mousemove@summary
+    target:div#divInsideSummary
+    relatedTarget:null
+
+mousemove@details
+    target:div#divInsideSummary
+    relatedTarget:null
+
+mousemove@div#detailsContainer
+    target:div#divInsideSummary
+    relatedTarget:null
+
+mousemove@body
+    target:div#divInsideSummary
+    relatedTarget:null
+
+mousemove@html
+    target:div#divInsideSummary
+    relatedTarget:null
+
+mousemove@document
+    target:div#divInsideSummary
+    relatedTarget:null
+
+mousemove@window
+    target:div#divInsideSummary
+    relatedTarget:null
+
+mousedown@div#divInsideSummary
+    target:div#divInsideSummary
+    relatedTarget:null
+
+mousedown@summary
+    target:div#divInsideSummary
+    relatedTarget:null
+
+mousedown@details
+    target:div#divInsideSummary
+    relatedTarget:null
+
+mousedown@div#detailsContainer
+    target:div#divInsideSummary
+    relatedTarget:null
+
+mousedown@body
+    target:div#divInsideSummary
+    relatedTarget:null
+
+mousedown@html
+    target:div#divInsideSummary
+    relatedTarget:null
+
+mousedown@document
+    target:div#divInsideSummary
+    relatedTarget:null
+
+mousedown@window
+    target:div#divInsideSummary
+    relatedTarget:null
+
+mouseup@div#divInsideSummary
+    target:div#divInsideSummary
+    relatedTarget:null
+
+mouseup@summary
+    target:div#divInsideSummary
+    relatedTarget:null
+
+mouseup@details
+    target:div#divInsideSummary
+    relatedTarget:null
+
+mouseup@div#detailsContainer
+    target:div#divInsideSummary
+    relatedTarget:null
+
+mouseup@body
+    target:div#divInsideSummary
+    relatedTarget:null
+
+mouseup@html
+    target:div#divInsideSummary
+    relatedTarget:null
+
+mouseup@document
+    target:div#divInsideSummary
+    relatedTarget:null
+
+mouseup@window
+    target:div#divInsideSummary
+    relatedTarget:null
+
+click@div#divInsideSummary
+    target:div#divInsideSummary
+    relatedTarget:null
+
+click@summary
+    target:div#divInsideSummary
+    relatedTarget:null
+
+click@details
+    target:div#divInsideSummary
+    relatedTarget:null
+
+click@div#detailsContainer
+    target:div#divInsideSummary
+    relatedTarget:null
+
+click@body
+    target:div#divInsideSummary
+    relatedTarget:null
+
+click@html
+    target:div#divInsideSummary
+    relatedTarget:null
+
+click@document
+    target:div#divInsideSummary
+    relatedTarget:null
+
+click@window
+    target:div#divInsideSummary
+    relatedTarget:null
+
+mousemove@div#divInsideSummary
+    target:div#divInsideSummary
+    relatedTarget:null
+
+mousemove@summary
+    target:div#divInsideSummary
+    relatedTarget:null
+
+mousemove@details
+    target:div#divInsideSummary
+    relatedTarget:null
+
+mousemove@div#detailsContainer
+    target:div#divInsideSummary
+    relatedTarget:null
+
+mousemove@body
+    target:div#divInsideSummary
+    relatedTarget:null
+
+mousemove@html
+    target:div#divInsideSummary
+    relatedTarget:null
+
+mousemove@document
+    target:div#divInsideSummary
+    relatedTarget:null
+
+mousemove@window
+    target:div#divInsideSummary
+    relatedTarget:null
+
+
diff --git a/LayoutTests/fast/events/shadow-event-path.html b/LayoutTests/fast/events/shadow-event-path.html
new file mode 100644 (file)
index 0000000..4847457
--- /dev/null
@@ -0,0 +1,65 @@
+<!DOCTYPE html>
+<html>
+<body>
+<p>This test records target and relatedTarget at each element while dispatching a mouse click event at an input element.</p>
+<div id="detailsContainer"><details><summary><div id="divInsideSummary"><input id="target" type="text" size="10"></summary></div></detials></div>
+<pre id="log"></pre>
+<script>
+
+if (window.testRunner)
+    testRunner.dumpAsText();
+
+function targetIdentifier(target) {
+    if (target === undefined || target === null)
+        return target;
+    if (target === window)
+        return 'window';
+    if (target === document)
+        return 'document';
+    return target.localName + (target.id ? '#' + target.id : '');
+}
+
+function attachListeners(eventname) {
+    var targets = Array.prototype.slice.call(document.querySelectorAll('*'));
+    targets.push(window);
+    targets.push(document);
+    targets.forEach(function (target) {
+        target.addEventListener(eventname, function (event) {
+            log.textContent += eventname + '@' + targetIdentifier(target) + '\n'
+                + '    target:' + targetIdentifier(event.target) + '\n'
+                + '    relatedTarget:' + targetIdentifier(event.relatedTarget) + '\n\n';
+        });
+    });
+}
+
+var log = document.getElementById('log');
+log.textContent = 'Content:' + detailsContainer.outerHTML + '\n\n';
+var target = document.getElementById('target');
+
+attachListeners('mousemove');
+attachListeners('mousedown');
+attachListeners('mouseover');
+attachListeners('mouseout');
+attachListeners('mouseenter');
+attachListeners('mouseleave');
+attachListeners('mouseup');
+attachListeners('click');
+
+function runTest() {
+    testRunner.waitUntilDone();
+    eventSender.mouseMoveTo(target.offsetLeft + target.offsetWidth / 2, target.offsetTop + target.offsetHeight / 2);
+    eventSender.mouseMoveTo(target.offsetLeft + target.offsetWidth + 100, target.offsetTop + target.offsetHeight / 2);
+    eventSender.mouseDown();
+    eventSender.mouseUp();
+    testRunner.notifyDone();
+}
+
+if (window.testRunner && !window.eventSender)
+    log.textContent += 'This test requires eventSender.';
+else if (window.eventSender) {
+    window.onload = runTest;
+}
+
+</script>
+</body>
+</html>
diff --git a/LayoutTests/platform/mac-wk2/fast/events/shadow-event-path-expected.txt b/LayoutTests/platform/mac-wk2/fast/events/shadow-event-path-expected.txt
new file mode 100644 (file)
index 0000000..33d6513
--- /dev/null
@@ -0,0 +1,306 @@
+This test records target and relatedTarget at each element while dispatching a mouse click event at an input element.
+
+
+Content:<div id="detailsContainer"><details><summary><div id="divInsideSummary"><input id="target" type="text" size="10"></div></summary></details></div>
+
+mouseover@input#target
+    target:input#target
+    relatedTarget:null
+
+mouseover@div#divInsideSummary
+    target:input#target
+    relatedTarget:null
+
+mouseover@summary
+    target:input#target
+    relatedTarget:null
+
+mouseover@details
+    target:input#target
+    relatedTarget:null
+
+mouseover@div#detailsContainer
+    target:input#target
+    relatedTarget:null
+
+mouseover@body
+    target:input#target
+    relatedTarget:null
+
+mouseover@html
+    target:input#target
+    relatedTarget:null
+
+mouseover@document
+    target:input#target
+    relatedTarget:null
+
+mouseover@window
+    target:input#target
+    relatedTarget:null
+
+mouseenter@input#target
+    target:input#target
+    relatedTarget:null
+
+mouseenter@div#divInsideSummary
+    target:div#divInsideSummary
+    relatedTarget:null
+
+mouseenter@summary
+    target:summary
+    relatedTarget:null
+
+mouseenter@details
+    target:details
+    relatedTarget:null
+
+mouseenter@div#detailsContainer
+    target:div#detailsContainer
+    relatedTarget:null
+
+mouseenter@body
+    target:body
+    relatedTarget:null
+
+mouseenter@html
+    target:html
+    relatedTarget:null
+
+mousemove@input#target
+    target:input#target
+    relatedTarget:null
+
+mousemove@div#divInsideSummary
+    target:input#target
+    relatedTarget:null
+
+mousemove@summary
+    target:input#target
+    relatedTarget:null
+
+mousemove@details
+    target:input#target
+    relatedTarget:null
+
+mousemove@div#detailsContainer
+    target:input#target
+    relatedTarget:null
+
+mousemove@body
+    target:input#target
+    relatedTarget:null
+
+mousemove@html
+    target:input#target
+    relatedTarget:null
+
+mousemove@document
+    target:input#target
+    relatedTarget:null
+
+mousemove@window
+    target:input#target
+    relatedTarget:null
+
+mouseout@input#target
+    target:input#target
+    relatedTarget:div#divInsideSummary
+
+mouseout@div#divInsideSummary
+    target:input#target
+    relatedTarget:div#divInsideSummary
+
+mouseout@summary
+    target:input#target
+    relatedTarget:div#divInsideSummary
+
+mouseout@details
+    target:input#target
+    relatedTarget:div#divInsideSummary
+
+mouseout@div#detailsContainer
+    target:input#target
+    relatedTarget:div#divInsideSummary
+
+mouseout@body
+    target:input#target
+    relatedTarget:div#divInsideSummary
+
+mouseout@html
+    target:input#target
+    relatedTarget:div#divInsideSummary
+
+mouseout@document
+    target:input#target
+    relatedTarget:div#divInsideSummary
+
+mouseout@window
+    target:input#target
+    relatedTarget:div#divInsideSummary
+
+mouseleave@input#target
+    target:input#target
+    relatedTarget:div#divInsideSummary
+
+mouseover@div#divInsideSummary
+    target:div#divInsideSummary
+    relatedTarget:input#target
+
+mouseover@summary
+    target:div#divInsideSummary
+    relatedTarget:input#target
+
+mouseover@details
+    target:div#divInsideSummary
+    relatedTarget:input#target
+
+mouseover@div#detailsContainer
+    target:div#divInsideSummary
+    relatedTarget:input#target
+
+mouseover@body
+    target:div#divInsideSummary
+    relatedTarget:input#target
+
+mouseover@html
+    target:div#divInsideSummary
+    relatedTarget:input#target
+
+mouseover@document
+    target:div#divInsideSummary
+    relatedTarget:input#target
+
+mouseover@window
+    target:div#divInsideSummary
+    relatedTarget:input#target
+
+mousemove@div#divInsideSummary
+    target:div#divInsideSummary
+    relatedTarget:null
+
+mousemove@summary
+    target:div#divInsideSummary
+    relatedTarget:null
+
+mousemove@details
+    target:div#divInsideSummary
+    relatedTarget:null
+
+mousemove@div#detailsContainer
+    target:div#divInsideSummary
+    relatedTarget:null
+
+mousemove@body
+    target:div#divInsideSummary
+    relatedTarget:null
+
+mousemove@html
+    target:div#divInsideSummary
+    relatedTarget:null
+
+mousemove@document
+    target:div#divInsideSummary
+    relatedTarget:null
+
+mousemove@window
+    target:div#divInsideSummary
+    relatedTarget:null
+
+mousedown@div#divInsideSummary
+    target:div#divInsideSummary
+    relatedTarget:null
+
+mousedown@summary
+    target:div#divInsideSummary
+    relatedTarget:null
+
+mousedown@details
+    target:div#divInsideSummary
+    relatedTarget:null
+
+mousedown@div#detailsContainer
+    target:div#divInsideSummary
+    relatedTarget:null
+
+mousedown@body
+    target:div#divInsideSummary
+    relatedTarget:null
+
+mousedown@html
+    target:div#divInsideSummary
+    relatedTarget:null
+
+mousedown@document
+    target:div#divInsideSummary
+    relatedTarget:null
+
+mousedown@window
+    target:div#divInsideSummary
+    relatedTarget:null
+
+mouseup@div#divInsideSummary
+    target:div#divInsideSummary
+    relatedTarget:null
+
+mouseup@summary
+    target:div#divInsideSummary
+    relatedTarget:null
+
+mouseup@details
+    target:div#divInsideSummary
+    relatedTarget:null
+
+mouseup@div#detailsContainer
+    target:div#divInsideSummary
+    relatedTarget:null
+
+mouseup@body
+    target:div#divInsideSummary
+    relatedTarget:null
+
+mouseup@html
+    target:div#divInsideSummary
+    relatedTarget:null
+
+mouseup@document
+    target:div#divInsideSummary
+    relatedTarget:null
+
+mouseup@window
+    target:div#divInsideSummary
+    relatedTarget:null
+
+click@div#divInsideSummary
+    target:div#divInsideSummary
+    relatedTarget:null
+
+click@summary
+    target:div#divInsideSummary
+    relatedTarget:null
+
+click@details
+    target:div#divInsideSummary
+    relatedTarget:null
+
+click@div#detailsContainer
+    target:div#divInsideSummary
+    relatedTarget:null
+
+click@body
+    target:div#divInsideSummary
+    relatedTarget:null
+
+click@html
+    target:div#divInsideSummary
+    relatedTarget:null
+
+click@document
+    target:div#divInsideSummary
+    relatedTarget:null
+
+click@window
+    target:div#divInsideSummary
+    relatedTarget:null
+
+
index bb82534..7aac387 100644 (file)
@@ -1,5 +1,34 @@
 2014-04-22  Ryosuke Niwa  <rniwa@webkit.org>
 
+        REGRESSION (r157328): popover to check into flight ba.com dismisses instantly when focusing form
+        https://bugs.webkit.org/show_bug.cgi?id=131949
+
+        Reviewed by Darin Adler.
+
+        The regression was caused by two bugs:
+        1. The event didn't stop propagating itself even when it should.
+           If the related target is same as the event origin, the event propagation should stop when the event reaches
+           the root of the related target's tree scope. Otherwise, it should stop when it reaches the related target.
+
+        2. Mouse event's related target exposed nodes inside a user-agent shadow DOM when the related target appeared
+           inside the origin.
+
+        Fixed the bugs by re-introducing path shrinkage algorithm removed in r157328 into EventPath::setRelatedTarget
+        and adding an algorithm to determine the least common ancestor of the related target and the current target
+        in moveToParentOrShadowHost. The latter algorithm doesn't match the shadow DOM specification:
+        http://www.w3.org/TR/2013/WD-shadow-dom-20130514/
+        but it's good enough in terms of the Web exposed behavior as we don't support author defined insertion points.
+
+        Test: fast/events/shadow-event-path.html
+
+        * dom/EventDispatcher.cpp:
+        (WebCore::EventRelatedNodeResolver::moveToParentOrShadowHost):
+        (WebCore::EventRelatedNodeResolver::findHostOfTreeScopeInTargetTreeScope): Added.
+        (WebCore::EventDispatcher::dispatchEvent):
+        (WebCore::EventPath::setRelatedTarget):
+
+2014-04-22  Ryosuke Niwa  <rniwa@webkit.org>
+
         Rollout r156635 since the old behavior was intentional.
 
         * page/EventHandler.cpp:
index 5ebc38f..4647563 100644 (file)
@@ -90,7 +90,7 @@ public:
 #if ENABLE(TOUCH_EVENTS)
     void updateTouchLists(const TouchEvent&);
 #endif
-    void setRelatedTarget(EventTarget&);
+    void setRelatedTarget(Node& origin, EventTarget&);
 
     bool hasEventListeners(const AtomicString& eventType) const;
 
@@ -148,18 +148,50 @@ public:
             ASSERT(m_currentTreeScope->parentTreeScope() == &newTreeScope);
         }
 
-        if (m_relatedNodeInCurrentTreeScope) { // relatedNode is under the current tree scope
+        if (&newTreeScope == &m_relatedNodeTreeScope)
+            m_relatedNodeInCurrentTreeScope = &m_relatedNode;
+        else if (m_relatedNodeInCurrentTreeScope) {
             ASSERT(m_currentTreeScope);
             m_relatedNodeInCurrentTreeScope = &newTarget;
-        } else if (&newTreeScope == &m_relatedNodeTreeScope) // relatedNode is in the current tree scope;
-            m_relatedNodeInCurrentTreeScope = &m_relatedNode;
-        // Otherwise, we haven't reached the tree scope that contains relatedNode yet.
+        } else {
+            if (!m_currentTreeScope) {
+                TreeScope* newTreeScopeAncestor = &newTreeScope;
+                do {
+                    m_relatedNodeInCurrentTreeScope = findHostOfTreeScopeInTargetTreeScope(m_relatedNodeTreeScope, *newTreeScopeAncestor);
+                    newTreeScopeAncestor = newTreeScopeAncestor->parentTreeScope();
+                    if (newTreeScopeAncestor == &m_relatedNodeTreeScope) {
+                        m_relatedNodeInCurrentTreeScope = &m_relatedNode;
+                        break;
+                    }
+                } while (newTreeScopeAncestor && !m_relatedNodeInCurrentTreeScope);
+            }
+            ASSERT(m_relatedNodeInCurrentTreeScope || findHostOfTreeScopeInTargetTreeScope(newTreeScope, m_relatedNodeTreeScope)
+                || &newTreeScope.documentScope() != &m_relatedNodeTreeScope.documentScope());
+        }
 
         m_currentTreeScope = &newTreeScope;
 
         return m_relatedNodeInCurrentTreeScope;
     }
 
+    static Node* findHostOfTreeScopeInTargetTreeScope(const TreeScope& startingTreeScope, const TreeScope& targetScope)
+    {
+        ASSERT(&targetScope != &startingTreeScope);
+        Node* previousHost = 0;
+        for (const TreeScope* scope = &startingTreeScope; scope; scope = scope->parentTreeScope()) {
+            if (scope == &targetScope) {
+                ASSERT(previousHost);
+                ASSERT_WITH_SECURITY_IMPLICATION(&previousHost->treeScope() == &targetScope);
+                return previousHost;
+            }
+            if (scope->rootNode().isShadowRoot())
+                previousHost = toShadowRoot(scope->rootNode()).hostElement();
+            else
+                ASSERT_WITH_SECURITY_IMPLICATION(!scope->parentTreeScope());
+        }
+        return 0;
+    }
+
 private:
     Node& m_relatedNode;
     const TreeScope& m_relatedNodeTreeScope;
@@ -305,7 +337,7 @@ bool EventDispatcher::dispatchEvent(Node* origin, PassRefPtr<Event> prpEvent)
     EventPath eventPath(*node, *event);
 
     if (EventTarget* relatedTarget = event->relatedTarget())
-        eventPath.setRelatedTarget(*relatedTarget);
+        eventPath.setRelatedTarget(*node, *relatedTarget);
 #if ENABLE(TOUCH_EVENTS) && !PLATFORM(IOS)
     if (event->isTouchEvent())
         eventPath.updateTouchLists(*toTouchEvent(event.get()));
@@ -452,7 +484,7 @@ void EventPath::updateTouchLists(const TouchEvent& touchEvent)
 }
 #endif
 
-void EventPath::setRelatedTarget(EventTarget& relatedTarget)
+void EventPath::setRelatedTarget(Node& origin, EventTarget& relatedTarget)
 {
     Node* relatedNode = relatedTarget.toNode();
     if (!relatedNode)
@@ -460,9 +492,22 @@ void EventPath::setRelatedTarget(EventTarget& relatedTarget)
 
     EventRelatedNodeResolver resolver(*relatedNode);
 
+    bool originIsRelatedTarget = &origin == relatedNode;
+    Node& rootNodeInOriginTreeScope = origin.treeScope().rootNode();
+
     size_t eventPathSize = m_path.size();
-    for (size_t i = 0; i < eventPathSize; i++)
-        toMouseOrFocusEventContext(*m_path[i]).setRelatedTarget(resolver.moveToParentOrShadowHost(*m_path[i]->node()));
+    size_t i = 0;
+    while (i < eventPathSize) {
+        Node* contextNode = m_path[i]->node();
+        Node* currentRelatedNode = resolver.moveToParentOrShadowHost(*contextNode);
+        if (!originIsRelatedTarget && m_path[i]->target() == currentRelatedNode)
+            break;
+        toMouseOrFocusEventContext(*m_path[i]).setRelatedTarget(currentRelatedNode);
+        i++;
+        if (originIsRelatedTarget && &rootNodeInOriginTreeScope == contextNode)
+            break;
+    }
+    m_path.shrink(i);
 }
 
 bool EventPath::hasEventListeners(const AtomicString& eventType) const