Extract EventPath.h/cpp out of EventDispatcher.cpp
authorrniwa@webkit.org <rniwa@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 10 Mar 2016 05:27:07 +0000 (05:27 +0000)
committerrniwa@webkit.org <rniwa@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 10 Mar 2016 05:27:07 +0000 (05:27 +0000)
https://bugs.webkit.org/show_bug.cgi?id=155285

Reviewed by Chris Dumez.

Extracted EventPath.h/cpp out of EventDispatcher.cpp to add the support for Event.deepPath()
in webkit.org/b/153538. The new file defines member functions of EventPath and RelatedNodeRetargeter.

* CMakeLists.txt:
* WebCore.xcodeproj/project.pbxproj:
* dom/DOMAllInOne.cpp:
* dom/EventDispatcher.cpp:
(WebCore::EventDispatcher::dispatchScopedEvent):
(WebCore::EventDispatcher::dispatchEvent):
(WebCore::EventPath): Moved to EventPath.cpp.
(WebCore::eventTargetRespectingTargetRules): Moved to EventPath.h.
(WebCore::shouldEventCrossShadowBoundary): Moved to EventPath.cpp.
(WebCore::nodeOrHostIfPseudoElement): Ditto.
(WebCore::RelatedNodeRetargeter): Moved to EventPath.cpp.
* dom/EventPath.cpp: Added.
(WebCore::shouldEventCrossShadowBoundary): Moved from EventDispatcher.cpp.
(WebCore::nodeOrHostIfPseudoElement): Ditto.
(WebCore::EventPath::EventPath): Ditto.
(WebCore::RelatedNodeRetargeter): Ditto.
* dom/EventPath.h: Added.
(WebCore::EventPath::isEmpty):
(WebCore::EventPath::size):
(WebCore::EventPath::contextAt):
(WebCore::EventPath::lastContextIfExists):
(WebCore::EventPath::eventTargetRespectingTargetRules): Moved from EventDispatcher.cpp.

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

Source/WebCore/CMakeLists.txt
Source/WebCore/ChangeLog
Source/WebCore/WebCore.xcodeproj/project.pbxproj
Source/WebCore/dom/DOMAllInOne.cpp
Source/WebCore/dom/EventDispatcher.cpp
Source/WebCore/dom/EventPath.cpp [new file with mode: 0644]
Source/WebCore/dom/EventPath.h [new file with mode: 0644]

index f3abc75..0b2a456 100644 (file)
@@ -1449,6 +1449,7 @@ set(WebCore_SOURCES
     dom/EventDispatcher.cpp
     dom/EventListenerMap.cpp
     dom/EventNames.cpp
+    dom/EventPath.cpp
     dom/EventTarget.cpp
     dom/ExceptionBase.cpp
     dom/ExceptionCodePlaceholder.cpp
index 2932856..7780f73 100644 (file)
@@ -1,3 +1,36 @@
+2016-03-09  Ryosuke Niwa  <rniwa@webkit.org>
+
+        Extract EventPath.h/cpp out of EventDispatcher.cpp
+        https://bugs.webkit.org/show_bug.cgi?id=155285
+
+        Reviewed by Chris Dumez.
+
+        Extracted EventPath.h/cpp out of EventDispatcher.cpp to add the support for Event.deepPath()
+        in webkit.org/b/153538. The new file defines member functions of EventPath and RelatedNodeRetargeter.
+
+        * CMakeLists.txt:
+        * WebCore.xcodeproj/project.pbxproj:
+        * dom/DOMAllInOne.cpp:
+        * dom/EventDispatcher.cpp:
+        (WebCore::EventDispatcher::dispatchScopedEvent):
+        (WebCore::EventDispatcher::dispatchEvent):
+        (WebCore::EventPath): Moved to EventPath.cpp.
+        (WebCore::eventTargetRespectingTargetRules): Moved to EventPath.h.
+        (WebCore::shouldEventCrossShadowBoundary): Moved to EventPath.cpp.
+        (WebCore::nodeOrHostIfPseudoElement): Ditto.
+        (WebCore::RelatedNodeRetargeter): Moved to EventPath.cpp.
+        * dom/EventPath.cpp: Added.
+        (WebCore::shouldEventCrossShadowBoundary): Moved from EventDispatcher.cpp.
+        (WebCore::nodeOrHostIfPseudoElement): Ditto.
+        (WebCore::EventPath::EventPath): Ditto.
+        (WebCore::RelatedNodeRetargeter): Ditto.
+        * dom/EventPath.h: Added.
+        (WebCore::EventPath::isEmpty):
+        (WebCore::EventPath::size):
+        (WebCore::EventPath::contextAt):
+        (WebCore::EventPath::lastContextIfExists):
+        (WebCore::EventPath::eventTargetRespectingTargetRules): Moved from EventDispatcher.cpp.
+
 2016-03-09  Simon Fraser  <simon.fraser@apple.com>
 
         Font antialiasing (smoothing) changes when elements are rendered into compositing layers
index 81ab634..3e46d6f 100644 (file)
                9B69D3B81B99100700E3512B /* JSHTMLSlotElement.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 9B69D3B61B99100700E3512B /* JSHTMLSlotElement.cpp */; };
                9B69D3B91B99100700E3512B /* JSHTMLSlotElement.h in Headers */ = {isa = PBXBuildFile; fileRef = 9B69D3B71B99100700E3512B /* JSHTMLSlotElement.h */; };
                9B6C41531344949000085B62 /* StringWithDirection.h in Headers */ = {isa = PBXBuildFile; fileRef = 9B6C41521344949000085B62 /* StringWithDirection.h */; settings = {ATTRIBUTES = (Private, ); }; };
+               9B714E201C91166900AC0E92 /* EventPath.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 9B714E1E1C91166900AC0E92 /* EventPath.cpp */; };
+               9B714E211C91166900AC0E92 /* EventPath.h in Headers */ = {isa = PBXBuildFile; fileRef = 9B714E1F1C91166900AC0E92 /* EventPath.h */; };
                9BA273F4172206BB0097CE47 /* LogicalSelectionOffsetCaches.h in Headers */ = {isa = PBXBuildFile; fileRef = 9BA273F3172206BB0097CE47 /* LogicalSelectionOffsetCaches.h */; };
                9BAB6C6C12550631001626D4 /* EditingStyle.h in Headers */ = {isa = PBXBuildFile; fileRef = 9BAB6C6A12550631001626D4 /* EditingStyle.h */; settings = {ATTRIBUTES = (Private, ); }; };
                9BAB6C6D12550631001626D4 /* EditingStyle.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 9BAB6C6B12550631001626D4 /* EditingStyle.cpp */; };
                9B6BC9601B975966005AE1F0 /* JSShadowRoot.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = JSShadowRoot.cpp; sourceTree = "<group>"; };
                9B6BC9611B975966005AE1F0 /* JSShadowRoot.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSShadowRoot.h; sourceTree = "<group>"; };
                9B6C41521344949000085B62 /* StringWithDirection.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = StringWithDirection.h; sourceTree = "<group>"; };
+               9B714E1E1C91166900AC0E92 /* EventPath.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = EventPath.cpp; sourceTree = "<group>"; };
+               9B714E1F1C91166900AC0E92 /* EventPath.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = EventPath.h; sourceTree = "<group>"; };
                9BA273F3172206BB0097CE47 /* LogicalSelectionOffsetCaches.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LogicalSelectionOffsetCaches.h; sourceTree = "<group>"; };
                9BAB6C6A12550631001626D4 /* EditingStyle.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = EditingStyle.h; sourceTree = "<group>"; };
                9BAB6C6B12550631001626D4 /* EditingStyle.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = EditingStyle.cpp; sourceTree = "<group>"; };
                                939885C108B7E3D100E707C4 /* EventNames.cpp */,
                                939885C208B7E3D100E707C4 /* EventNames.h */,
                                BC9A6142146859D9006057FD /* EventNames.in */,
+                               9B714E1E1C91166900AC0E92 /* EventPath.cpp */,
+                               9B714E1F1C91166900AC0E92 /* EventPath.h */,
                                8F6756191288B17B0047ACA3 /* EventQueue.h */,
                                CE5CB1B314EDAB6F00BB2795 /* EventSender.h */,
                                E12EDBE90B308E0B002704B6 /* EventTarget.cpp */,
                                BCA846D70DC67A350026C309 /* RenderReplica.h in Headers */,
                                1479FAEE109AE37500DED655 /* RenderRuby.h in Headers */,
                                1479FAF0109AE37500DED655 /* RenderRubyBase.h in Headers */,
+                               9B714E211C91166900AC0E92 /* EventPath.h in Headers */,
                                1479FAF2109AE37500DED655 /* RenderRubyRun.h in Headers */,
                                1479FAF4109AE37500DED655 /* RenderRubyText.h in Headers */,
                                BC3BE9940E9C1C7C00835588 /* RenderScrollbar.h in Headers */,
                                59A8F1D411A69508001AC34A /* DeviceOrientationController.cpp in Sources */,
                                59D1C10411EB5DCF00B638C8 /* DeviceOrientationData.cpp in Sources */,
                                59A85EA2119D68D900DEF1EF /* DeviceOrientationEvent.cpp in Sources */,
+                               9B714E201C91166900AC0E92 /* EventPath.cpp in Sources */,
                                267725FC1A5B3AD9003C24DD /* DFA.cpp in Sources */,
                                5C9A7A751AA0F6EA00958ACF /* DFABytecodeCompiler.cpp in Sources */,
                                5C9A7A761AA0F6ED00958ACF /* DFABytecodeInterpreter.cpp in Sources */,
index 7dada31..72030dc 100644 (file)
@@ -83,6 +83,7 @@
 #include "EventDispatcher.cpp"
 #include "EventListenerMap.cpp"
 #include "EventNames.cpp"
+#include "EventPath.cpp"
 #include "EventTarget.cpp"
 #include "ExceptionBase.cpp"
 #include "ExceptionCodePlaceholder.cpp"
index 0138f44..585632c 100644 (file)
 #include "EventDispatcher.h"
 
 #include "EventContext.h"
-#include "FocusEvent.h"
+#include "EventPath.h"
 #include "FrameView.h"
 #include "HTMLInputElement.h"
-#include "HTMLMediaElement.h"
-#include "HTMLSlotElement.h"
 #include "MouseEvent.h"
 #include "PseudoElement.h"
 #include "ScopedEventQueue.h"
 #include "ShadowRoot.h"
-#include "SVGNames.h"
-#include "SVGUseElement.h"
 #include "TouchEvent.h"
 #include <wtf/NeverDestroyed.h>
 
@@ -77,51 +73,10 @@ bool WindowEventContext::handleLocalEvents(Event& event)
     return true;
 }
 
-class EventPath {
-public:
-    EventPath(Node& origin, Event&);
-
-    bool isEmpty() const { return m_path.isEmpty(); }
-    size_t size() const { return m_path.size(); }
-    const EventContext& contextAt(size_t i) const { return *m_path[i]; }
-    EventContext& contextAt(size_t i) { return *m_path[i]; }
-
-#if ENABLE(TOUCH_EVENTS)
-    void retargetTouchLists(const TouchEvent&);
-#endif
-    void setRelatedTarget(Node& origin, EventTarget&);
-
-    bool hasEventListeners(const AtomicString& eventType) const;
-
-    EventContext* lastContextIfExists() { return m_path.isEmpty() ? nullptr : m_path.last().get(); }
-
-private:
-#if ENABLE(TOUCH_EVENTS)
-    void retargetTouch(TouchEventContext::TouchListType, const Touch&);
-#endif
-
-    Event& m_event;
-    Vector<std::unique_ptr<EventContext>, 32> m_path;
-};
-
-inline EventTarget* eventTargetRespectingTargetRules(Node& referenceNode)
-{
-    if (is<PseudoElement>(referenceNode))
-        return downcast<PseudoElement>(referenceNode).hostElement();
-
-    // Events sent to elements inside an SVG use element's shadow tree go to the use element.
-    if (is<SVGElement>(referenceNode)) {
-        if (auto* useElement = downcast<SVGElement>(referenceNode).correspondingUseElement())
-            return useElement;
-    }
-
-    return &referenceNode;
-}
-
 void EventDispatcher::dispatchScopedEvent(Node& node, Event& event)
 {
     // We need to set the target here because it can go away by the time we actually fire the event.
-    event.setTarget(eventTargetRespectingTargetRules(node));
+    event.setTarget(EventPath::eventTargetRespectingTargetRules(node));
     ScopedEventQueue::singleton().enqueueEvent(event);
 }
 
@@ -209,7 +164,7 @@ bool EventDispatcher::dispatchEvent(Node* origin, Event& event)
 
     ChildNodesLazySnapshot::takeChildNodesLazySnapshot();
 
-    EventTarget* target = eventTargetRespectingTargetRules(*node);
+    EventTarget* target = EventPath::eventTargetRespectingTargetRules(*node);
     event.setTarget(target);
     if (!event.target())
         return true;
@@ -225,7 +180,7 @@ bool EventDispatcher::dispatchEvent(Node* origin, Event& event)
     if (!event.propagationStopped() && !eventPath.isEmpty())
         dispatchEventInDOM(event, eventPath, windowEventContext);
 
-    event.setTarget(eventTargetRespectingTargetRules(*node));
+    event.setTarget(EventPath::eventTargetRespectingTargetRules(*node));
     event.setCurrentTarget(nullptr);
     event.setEventPhase(0);
 
@@ -246,325 +201,4 @@ bool EventDispatcher::dispatchEvent(Node* origin, Event& event)
     return !event.defaultPrevented();
 }
 
-static inline bool shouldEventCrossShadowBoundary(Event& event, ShadowRoot& shadowRoot, EventTarget& target)
-{
-    Node* targetNode = target.toNode();
-#if ENABLE(FULLSCREEN_API) && ENABLE(VIDEO)
-    // Video-only full screen is a mode where we use the shadow DOM as an implementation
-    // detail that should not be detectable by the web content.
-    if (targetNode) {
-        if (Element* element = targetNode->document().webkitCurrentFullScreenElement()) {
-            // FIXME: We assume that if the full screen element is a media element that it's
-            // the video-only full screen. Both here and elsewhere. But that is probably wrong.
-            if (element->isMediaElement() && shadowRoot.host() == element)
-                return false;
-        }
-    }
-#endif
-
-    // WebKit never allowed selectstart event to cross the the shadow DOM boundary.
-    // Changing this breaks existing sites.
-    // See https://bugs.webkit.org/show_bug.cgi?id=52195 for details.
-    const AtomicString& eventType = event.type();
-    bool targetIsInShadowRoot = targetNode && &targetNode->treeScope().rootNode() == &shadowRoot;
-    return !targetIsInShadowRoot
-        || !(eventType == eventNames().abortEvent
-            || eventType == eventNames().changeEvent
-            || eventType == eventNames().errorEvent
-            || eventType == eventNames().loadEvent
-            || eventType == eventNames().resetEvent
-            || eventType == eventNames().resizeEvent
-            || eventType == eventNames().scrollEvent
-            || eventType == eventNames().selectEvent
-            || eventType == eventNames().selectstartEvent);
-}
-
-static Node* nodeOrHostIfPseudoElement(Node* node)
-{
-    return is<PseudoElement>(*node) ? downcast<PseudoElement>(*node).hostElement() : node;
-}
-
-EventPath::EventPath(Node& originalTarget, Event& event)
-    : m_event(event)
-{
-#if ENABLE(SHADOW_DOM) || ENABLE(DETAILS_ELEMENT)
-    Vector<EventTarget*, 16> targetStack;
-#endif
-
-    bool isMouseOrFocusEvent = event.isMouseEvent() || event.isFocusEvent();
-#if ENABLE(TOUCH_EVENTS)
-    bool isTouchEvent = event.isTouchEvent();
-#endif
-    EventTarget* target = nullptr;
-
-    Node* node = nodeOrHostIfPseudoElement(&originalTarget);
-    while (node) {
-        if (!target)
-            target = eventTargetRespectingTargetRules(*node);
-        ContainerNode* parent;
-        for (; node; node = parent) {
-            EventTarget* currentTarget = eventTargetRespectingTargetRules(*node);
-            if (isMouseOrFocusEvent)
-                m_path.append(std::make_unique<MouseOrFocusEventContext>(node, currentTarget, target));
-#if ENABLE(TOUCH_EVENTS)
-            else if (isTouchEvent)
-                m_path.append(std::make_unique<TouchEventContext>(node, currentTarget, target));
-#endif
-            else
-                m_path.append(std::make_unique<EventContext>(node, currentTarget, target));
-            if (is<ShadowRoot>(*node))
-                break;
-            parent = node->parentNode();
-            if (!parent)
-                return;
-#if ENABLE(SHADOW_DOM) || ENABLE(DETAILS_ELEMENT)
-            if (ShadowRoot* shadowRootOfParent = parent->shadowRoot()) {
-                if (auto* assignedSlot = shadowRootOfParent->findAssignedSlot(*node)) {
-                    // node is assigned to a slot. Continue dispatching the event at this slot.
-                    targetStack.append(target);
-                    parent = assignedSlot;
-                    target = assignedSlot;
-                }
-            }
-#endif
-            node = parent;
-        }
-
-        ShadowRoot& shadowRoot = downcast<ShadowRoot>(*node);
-        // At a shadow root. Continue dispatching the event at the shadow host.
-#if ENABLE(SHADOW_DOM) || ENABLE(DETAILS_ELEMENT)
-        if (!targetStack.isEmpty()) {
-            // Move target back to a descendant of the shadow host if the event did not originate in this shadow tree or its inner shadow trees.
-            target = targetStack.last();
-            targetStack.removeLast();
-            ASSERT(shadowRoot.host()->contains(target->toNode()));
-        } else
-#endif
-            target = nullptr;
-
-        if (!shouldEventCrossShadowBoundary(event, shadowRoot, originalTarget))
-            return;
-        node = shadowRoot.host();
-    }
-}
-
-class RelatedNodeRetargeter {
-public:
-    RelatedNodeRetargeter(Node& relatedNode, TreeScope& targetTreeScope)
-        : m_relatedNode(relatedNode)
-        , m_retargetedRelatedNode(&relatedNode)
-    {
-        TreeScope* currentTreeScope = &m_relatedNode.treeScope();
-        if (LIKELY(currentTreeScope == &targetTreeScope))
-            return;
-
-        if (&currentTreeScope->documentScope() != &targetTreeScope.documentScope()) {
-            m_hasDifferentTreeRoot = true;
-            m_retargetedRelatedNode = nullptr;
-            return;
-        }
-        if (relatedNode.inDocument() != targetTreeScope.rootNode().inDocument()) {
-            m_hasDifferentTreeRoot = true;
-            while (m_retargetedRelatedNode->isInShadowTree())
-                m_retargetedRelatedNode = downcast<ShadowRoot>(m_retargetedRelatedNode->treeScope().rootNode()).host();
-            return;
-        }
-
-        collectTreeScopes();
-
-        // FIXME: We should collect this while constructing the event path.
-        Vector<TreeScope*, 8> targetTreeScopeAncestors;
-        for (TreeScope* currentTreeScope = &targetTreeScope; currentTreeScope; currentTreeScope = currentTreeScope->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& currentTreeScope)
-    {
-        checkConsistency(currentTreeScope);
-        return m_retargetedRelatedNode;
-    }
-
-    void moveToNewTreeScope(TreeScope* previousTreeScope, TreeScope& newTreeScope)
-    {
-        if (m_hasDifferentTreeRoot)
-            return;
-
-        auto& currentRelatedNodeScope = m_retargetedRelatedNode->treeScope();
-        if (previousTreeScope != &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] == &newTreeScope;
-                if (relatedNodeIsInSlot) {
-                    m_lowestCommonAncestorIndex--;
-                    m_retargetedRelatedNode = nodeInLowestCommonAncestor();
-                    ASSERT(&newTreeScope == &m_retargetedRelatedNode->treeScope());
-                }
-            } else
-                ASSERT(m_retargetedRelatedNode == &m_relatedNode);
-        } else {
-            ASSERT(previousTreeScope->parentTreeScope() == &newTreeScope);
-            m_lowestCommonAncestorIndex++;
-            ASSERT_WITH_SECURITY_IMPLICATION(m_ancestorTreeScopes.isEmpty() || m_lowestCommonAncestorIndex < m_ancestorTreeScopes.size());
-            m_retargetedRelatedNode = downcast<ShadowRoot>(currentRelatedNodeScope.rootNode()).host();
-            ASSERT(&newTreeScope == &m_retargetedRelatedNode->treeScope());
-        }
-    }
-
-    void checkConsistency(TreeScope& currentTreeScope)
-    {
-#if !ASSERT_DISABLED
-        for (auto* relatedNodeScope = &m_relatedNode.treeScope(); relatedNodeScope; relatedNodeScope = relatedNodeScope->parentTreeScope()) {
-            for (auto* targetScope = &currentTreeScope; targetScope; targetScope = targetScope->parentTreeScope()) {
-                if (targetScope == relatedNodeScope) {
-                    ASSERT(&m_retargetedRelatedNode->treeScope() == relatedNodeScope);
-                    return;
-                }
-            }
-        }
-        ASSERT(!m_retargetedRelatedNode);
-#else
-        UNUSED_PARAM(currentTreeScope);
-#endif
-    }
-
-private:
-    Node* nodeInLowestCommonAncestor()
-    {
-        if (!m_lowestCommonAncestorIndex)
-            return &m_relatedNode;
-        auto& rootNode = m_ancestorTreeScopes[m_lowestCommonAncestorIndex - 1]->rootNode();
-        return downcast<ShadowRoot>(rootNode).host();
-    }
-
-    void collectTreeScopes()
-    {
-        ASSERT(m_ancestorTreeScopes.isEmpty());
-        for (TreeScope* currentTreeScope = &m_relatedNode.treeScope(); currentTreeScope; currentTreeScope = currentTreeScope->parentTreeScope())
-            m_ancestorTreeScopes.append(currentTreeScope);
-        ASSERT_WITH_SECURITY_IMPLICATION(!m_ancestorTreeScopes.isEmpty());
-    }
-
-    Node& m_relatedNode;
-    Node* m_retargetedRelatedNode;
-    Vector<TreeScope*, 8> m_ancestorTreeScopes;
-    unsigned m_lowestCommonAncestorIndex { 0 };
-    bool m_hasDifferentTreeRoot { false };
-};
-
-void EventPath::setRelatedTarget(Node& origin, EventTarget& relatedTarget)
-{
-    Node* relatedNode = relatedTarget.toNode();
-    if (!relatedNode || m_path.isEmpty())
-        return;
-
-    RelatedNodeRetargeter retargeter(*relatedNode, downcast<MouseOrFocusEventContext>(*m_path[0]).node()->treeScope());
-
-    bool originIsRelatedTarget = &origin == relatedNode;
-    // 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;
-    Node& rootNodeInOriginTreeScope = origin.treeScope().rootNode();
-    TreeScope* previousTreeScope = nullptr;
-    size_t originalEventPathSize = m_path.size();
-    for (unsigned contextIndex = 0; contextIndex < originalEventPathSize; contextIndex++) {
-        auto& context = downcast<MouseOrFocusEventContext>(*m_path[contextIndex]);
-
-        TreeScope& currentTreeScope = context.node()->treeScope();
-        if (UNLIKELY(previousTreeScope && &currentTreeScope != previousTreeScope))
-            retargeter.moveToNewTreeScope(previousTreeScope, currentTreeScope);
-
-        Node* currentRelatedNode = retargeter.currentNode(currentTreeScope);
-        if (UNLIKELY(shouldTrimEventPath && !originIsRelatedTarget && context.target() == currentRelatedNode)) {
-            m_path.shrink(contextIndex);
-            break;
-        }
-
-        context.setRelatedTarget(currentRelatedNode);
-
-        if (UNLIKELY(shouldTrimEventPath && originIsRelatedTarget && context.node() == &rootNodeInOriginTreeScope)) {
-            m_path.shrink(contextIndex + 1);
-            break;
-        }
-
-        previousTreeScope = &currentTreeScope;
-    }
-}
-
-#if ENABLE(TOUCH_EVENTS)
-void EventPath::retargetTouch(TouchEventContext::TouchListType touchListType, const Touch& touch)
-{
-    EventTarget* eventTarget = touch.target();
-    if (!eventTarget)
-        return;
-
-    Node* targetNode = eventTarget->toNode();
-    if (!targetNode)
-        return;
-
-    RelatedNodeRetargeter retargeter(*targetNode, m_path[0]->node()->treeScope());
-    TreeScope* previousTreeScope = nullptr;
-    for (auto& context : m_path) {
-        TreeScope& currentTreeScope = context->node()->treeScope();
-        if (UNLIKELY(previousTreeScope && &currentTreeScope != previousTreeScope))
-            retargeter.moveToNewTreeScope(previousTreeScope, currentTreeScope);
-
-        Node* currentRelatedNode = retargeter.currentNode(currentTreeScope);
-        downcast<TouchEventContext>(*context).touchList(touchListType)->append(touch.cloneWithNewTarget(currentRelatedNode));
-
-        previousTreeScope = &currentTreeScope;
-    }
-}
-
-void EventPath::retargetTouchLists(const TouchEvent& touchEvent)
-{
-    if (touchEvent.touches()) {
-        for (size_t i = 0; i < touchEvent.touches()->length(); ++i)
-            retargetTouch(TouchEventContext::Touches, *touchEvent.touches()->item(i));
-    }
-
-    if (touchEvent.targetTouches()) {
-        for (size_t i = 0; i < touchEvent.targetTouches()->length(); ++i)
-            retargetTouch(TouchEventContext::TargetTouches, *touchEvent.targetTouches()->item(i));
-    }
-
-    if (touchEvent.changedTouches()) {
-        for (size_t i = 0; i < touchEvent.changedTouches()->length(); ++i)
-            retargetTouch(TouchEventContext::ChangedTouches, *touchEvent.changedTouches()->item(i));
-    }
-}
-#endif
-
-bool EventPath::hasEventListeners(const AtomicString& eventType) const
-{
-    for (auto& eventPath : m_path) {
-        if (eventPath->node()->hasEventListeners(eventType))
-            return true;
-    }
-
-    return false;
-}
-
 }
diff --git a/Source/WebCore/dom/EventPath.cpp b/Source/WebCore/dom/EventPath.cpp
new file mode 100644 (file)
index 0000000..9abbb70
--- /dev/null
@@ -0,0 +1,375 @@
+/*
+ * Copyright (C) 2013 Google Inc. All rights reserved.
+ * Copyright (C) 2013-2016 Apple Inc. All rights reserved.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public License
+ * along with this library; see the file COPYING.LIB.  If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#include "config.h"
+#include "EventPath.h"
+
+#include "Event.h"
+#include "EventContext.h"
+#include "EventNames.h"
+#include "HTMLSlotElement.h"
+#include "Node.h"
+#include "PseudoElement.h"
+#include "ShadowRoot.h"
+#include "TouchEvent.h"
+
+namespace WebCore {
+
+static inline bool shouldEventCrossShadowBoundary(Event& event, ShadowRoot& shadowRoot, EventTarget& target)
+{
+    Node* targetNode = target.toNode();
+
+#if ENABLE(FULLSCREEN_API) && ENABLE(VIDEO)
+    // Video-only full screen is a mode where we use the shadow DOM as an implementation
+    // detail that should not be detectable by the web content.
+    if (targetNode) {
+        if (Element* element = targetNode->document().webkitCurrentFullScreenElement()) {
+            // FIXME: We assume that if the full screen element is a media element that it's
+            // the video-only full screen. Both here and elsewhere. But that is probably wrong.
+            if (element->isMediaElement() && shadowRoot.host() == element)
+                return false;
+        }
+    }
+#endif
+
+    // WebKit never allowed selectstart event to cross the the shadow DOM boundary.
+    // Changing this breaks existing sites.
+    // See https://bugs.webkit.org/show_bug.cgi?id=52195 for details.
+    const AtomicString& eventType = event.type();
+    bool targetIsInShadowRoot = targetNode && &targetNode->treeScope().rootNode() == &shadowRoot;
+    return !targetIsInShadowRoot
+        || !(eventType == eventNames().abortEvent
+        || eventType == eventNames().changeEvent
+        || eventType == eventNames().errorEvent
+        || eventType == eventNames().loadEvent
+        || eventType == eventNames().resetEvent
+        || eventType == eventNames().resizeEvent
+        || eventType == eventNames().scrollEvent
+        || eventType == eventNames().selectEvent
+        || eventType == eventNames().selectstartEvent);
+}
+
+static Node* nodeOrHostIfPseudoElement(Node* node)
+{
+    return is<PseudoElement>(*node) ? downcast<PseudoElement>(*node).hostElement() : node;
+}
+
+class RelatedNodeRetargeter {
+public:
+    RelatedNodeRetargeter(Node& relatedNode, TreeScope& targetTreeScope);
+
+    Node* currentNode(TreeScope& currentTreeScope);
+    void moveToNewTreeScope(TreeScope* previousTreeScope, TreeScope& newTreeScope);
+
+private:
+
+    Node* nodeInLowestCommonAncestor();
+    void collectTreeScopes();
+
+#if ASSERT_DISABLED
+    void checkConsistency(TreeScope&) { }
+#else
+    void checkConsistency(TreeScope& currentTreeScope);
+#endif
+
+    Node& m_relatedNode;
+    Node* m_retargetedRelatedNode;
+    Vector<TreeScope*, 8> m_ancestorTreeScopes;
+    unsigned m_lowestCommonAncestorIndex { 0 };
+    bool m_hasDifferentTreeRoot { false };
+};
+
+EventPath::EventPath(Node& originalTarget, Event& event)
+    : m_event(event)
+{
+#if ENABLE(SHADOW_DOM) || ENABLE(DETAILS_ELEMENT)
+    Vector<EventTarget*, 16> targetStack;
+#endif
+
+    bool isMouseOrFocusEvent = event.isMouseEvent() || event.isFocusEvent();
+#if ENABLE(TOUCH_EVENTS)
+    bool isTouchEvent = event.isTouchEvent();
+#endif
+    EventTarget* target = nullptr;
+
+    Node* node = nodeOrHostIfPseudoElement(&originalTarget);
+    while (node) {
+        if (!target)
+            target = eventTargetRespectingTargetRules(*node);
+        ContainerNode* parent;
+        for (; node; node = parent) {
+            EventTarget* currentTarget = eventTargetRespectingTargetRules(*node);
+
+            if (isMouseOrFocusEvent)
+                m_path.append(std::make_unique<MouseOrFocusEventContext>(node, currentTarget, target));
+#if ENABLE(TOUCH_EVENTS)
+            else if (isTouchEvent)
+                m_path.append(std::make_unique<TouchEventContext>(node, currentTarget, target));
+#endif
+            else
+                m_path.append(std::make_unique<EventContext>(node, currentTarget, target));
+
+            if (is<ShadowRoot>(*node))
+                break;
+
+            parent = node->parentNode();
+
+            if (!parent)
+                return;
+
+#if ENABLE(SHADOW_DOM) || ENABLE(DETAILS_ELEMENT)
+            if (ShadowRoot* shadowRootOfParent = parent->shadowRoot()) {
+                if (auto* assignedSlot = shadowRootOfParent->findAssignedSlot(*node)) {
+                    // node is assigned to a slot. Continue dispatching the event at this slot.
+                    targetStack.append(target);
+                    parent = assignedSlot;
+                    target = assignedSlot;
+                }
+            }
+#endif
+            node = parent;
+        }
+
+        ShadowRoot& shadowRoot = downcast<ShadowRoot>(*node);
+        // At a shadow root. Continue dispatching the event at the shadow host.
+#if ENABLE(SHADOW_DOM) || ENABLE(DETAILS_ELEMENT)
+        if (!targetStack.isEmpty()) {
+            // Move target back to a descendant of the shadow host if the event did not originate in this shadow tree or its inner shadow trees.
+            target = targetStack.last();
+            targetStack.removeLast();
+            ASSERT(shadowRoot.host()->contains(target->toNode()));
+        } else
+#endif
+            target = nullptr;
+
+        if (!shouldEventCrossShadowBoundary(event, shadowRoot, originalTarget))
+            return;
+        node = shadowRoot.host();
+    }
+}
+
+void EventPath::setRelatedTarget(Node& origin, EventTarget& relatedTarget)
+{
+    Node* relatedNode = relatedTarget.toNode();
+    if (!relatedNode || m_path.isEmpty())
+        return;
+
+    RelatedNodeRetargeter retargeter(*relatedNode, downcast<MouseOrFocusEventContext>(*m_path[0]).node()->treeScope());
+
+    bool originIsRelatedTarget = &origin == relatedNode;
+    // 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;
+    Node& rootNodeInOriginTreeScope = origin.treeScope().rootNode();
+    TreeScope* previousTreeScope = nullptr;
+    size_t originalEventPathSize = m_path.size();
+    for (unsigned contextIndex = 0; contextIndex < originalEventPathSize; contextIndex++) {
+        auto& context = downcast<MouseOrFocusEventContext>(*m_path[contextIndex]);
+
+        TreeScope& currentTreeScope = context.node()->treeScope();
+        if (UNLIKELY(previousTreeScope && &currentTreeScope != previousTreeScope))
+            retargeter.moveToNewTreeScope(previousTreeScope, currentTreeScope);
+
+        Node* currentRelatedNode = retargeter.currentNode(currentTreeScope);
+        if (UNLIKELY(shouldTrimEventPath && !originIsRelatedTarget && context.target() == currentRelatedNode)) {
+            m_path.shrink(contextIndex);
+            break;
+        }
+
+        context.setRelatedTarget(currentRelatedNode);
+
+        if (UNLIKELY(shouldTrimEventPath && originIsRelatedTarget && context.node() == &rootNodeInOriginTreeScope)) {
+            m_path.shrink(contextIndex + 1);
+            break;
+        }
+
+        previousTreeScope = &currentTreeScope;
+    }
+}
+
+#if ENABLE(TOUCH_EVENTS)
+void EventPath::retargetTouch(TouchEventContext::TouchListType touchListType, const Touch& touch)
+{
+    EventTarget* eventTarget = touch.target();
+    if (!eventTarget)
+        return;
+
+    Node* targetNode = eventTarget->toNode();
+    if (!targetNode)
+        return;
+
+    RelatedNodeRetargeter retargeter(*targetNode, m_path[0]->node()->treeScope());
+    TreeScope* previousTreeScope = nullptr;
+    for (auto& context : m_path) {
+        TreeScope& currentTreeScope = context->node()->treeScope();
+        if (UNLIKELY(previousTreeScope && &currentTreeScope != previousTreeScope))
+            retargeter.moveToNewTreeScope(previousTreeScope, currentTreeScope);
+
+        Node* currentRelatedNode = retargeter.currentNode(currentTreeScope);
+        downcast<TouchEventContext>(*context).touchList(touchListType)->append(touch.cloneWithNewTarget(currentRelatedNode));
+
+        previousTreeScope = &currentTreeScope;
+    }
+}
+
+void EventPath::retargetTouchLists(const TouchEvent& touchEvent)
+{
+    if (touchEvent.touches()) {
+        for (size_t i = 0; i < touchEvent.touches()->length(); ++i)
+            retargetTouch(TouchEventContext::Touches, *touchEvent.touches()->item(i));
+    }
+
+    if (touchEvent.targetTouches()) {
+        for (size_t i = 0; i < touchEvent.targetTouches()->length(); ++i)
+            retargetTouch(TouchEventContext::TargetTouches, *touchEvent.targetTouches()->item(i));
+    }
+
+    if (touchEvent.changedTouches()) {
+        for (size_t i = 0; i < touchEvent.changedTouches()->length(); ++i)
+            retargetTouch(TouchEventContext::ChangedTouches, *touchEvent.changedTouches()->item(i));
+    }
+}
+#endif
+
+bool EventPath::hasEventListeners(const AtomicString& eventType) const
+{
+    for (auto& eventPath : m_path) {
+        if (eventPath->node()->hasEventListeners(eventType))
+            return true;
+    }
+
+    return false;
+}
+
+RelatedNodeRetargeter::RelatedNodeRetargeter(Node& relatedNode, TreeScope& targetTreeScope)
+    : m_relatedNode(relatedNode)
+    , m_retargetedRelatedNode(&relatedNode)
+{
+    TreeScope* currentTreeScope = &m_relatedNode.treeScope();
+    if (LIKELY(currentTreeScope == &targetTreeScope))
+        return;
+
+    if (&currentTreeScope->documentScope() != &targetTreeScope.documentScope()) {
+        m_hasDifferentTreeRoot = true;
+        m_retargetedRelatedNode = nullptr;
+        return;
+    }
+    if (relatedNode.inDocument() != targetTreeScope.rootNode().inDocument()) {
+        m_hasDifferentTreeRoot = true;
+        while (m_retargetedRelatedNode->isInShadowTree())
+            m_retargetedRelatedNode = downcast<ShadowRoot>(m_retargetedRelatedNode->treeScope().rootNode()).host();
+        return;
+    }
+
+    collectTreeScopes();
+
+    // FIXME: We should collect this while constructing the event path.
+    Vector<TreeScope*, 8> targetTreeScopeAncestors;
+    for (TreeScope* currentTreeScope = &targetTreeScope; currentTreeScope; currentTreeScope = currentTreeScope->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();
+}
+
+inline Node* RelatedNodeRetargeter::currentNode(TreeScope& currentTreeScope)
+{
+    checkConsistency(currentTreeScope);
+    return m_retargetedRelatedNode;
+}
+
+void RelatedNodeRetargeter::moveToNewTreeScope(TreeScope* previousTreeScope, TreeScope& newTreeScope)
+{
+    if (m_hasDifferentTreeRoot)
+        return;
+
+    auto& currentRelatedNodeScope = m_retargetedRelatedNode->treeScope();
+    if (previousTreeScope != &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] == &newTreeScope;
+            if (relatedNodeIsInSlot) {
+                m_lowestCommonAncestorIndex--;
+                m_retargetedRelatedNode = nodeInLowestCommonAncestor();
+                ASSERT(&newTreeScope == &m_retargetedRelatedNode->treeScope());
+            }
+        } else
+            ASSERT(m_retargetedRelatedNode == &m_relatedNode);
+    } else {
+        ASSERT(previousTreeScope->parentTreeScope() == &newTreeScope);
+        m_lowestCommonAncestorIndex++;
+        ASSERT_WITH_SECURITY_IMPLICATION(m_ancestorTreeScopes.isEmpty() || m_lowestCommonAncestorIndex < m_ancestorTreeScopes.size());
+        m_retargetedRelatedNode = downcast<ShadowRoot>(currentRelatedNodeScope.rootNode()).host();
+        ASSERT(&newTreeScope == &m_retargetedRelatedNode->treeScope());
+    }
+}
+
+inline Node* RelatedNodeRetargeter::nodeInLowestCommonAncestor()
+{
+    if (!m_lowestCommonAncestorIndex)
+        return &m_relatedNode;
+    auto& rootNode = m_ancestorTreeScopes[m_lowestCommonAncestorIndex - 1]->rootNode();
+    return downcast<ShadowRoot>(rootNode).host();
+}
+
+void RelatedNodeRetargeter::collectTreeScopes()
+{
+    ASSERT(m_ancestorTreeScopes.isEmpty());
+    for (TreeScope* currentTreeScope = &m_relatedNode.treeScope(); currentTreeScope; currentTreeScope = currentTreeScope->parentTreeScope())
+        m_ancestorTreeScopes.append(currentTreeScope);
+    ASSERT_WITH_SECURITY_IMPLICATION(!m_ancestorTreeScopes.isEmpty());
+}
+
+#if !ASSERT_DISABLED
+void RelatedNodeRetargeter::checkConsistency(TreeScope& currentTreeScope)
+{
+    for (auto* relatedNodeScope = &m_relatedNode.treeScope(); relatedNodeScope; relatedNodeScope = relatedNodeScope->parentTreeScope()) {
+        for (auto* targetScope = &currentTreeScope; targetScope; targetScope = targetScope->parentTreeScope()) {
+            if (targetScope == relatedNodeScope) {
+                ASSERT(&m_retargetedRelatedNode->treeScope() == relatedNodeScope);
+                return;
+            }
+        }
+    }
+    ASSERT(!m_retargetedRelatedNode);
+}
+#endif
+
+}
diff --git a/Source/WebCore/dom/EventPath.h b/Source/WebCore/dom/EventPath.h
new file mode 100644 (file)
index 0000000..529e644
--- /dev/null
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2013 Google Inc. All rights reserved.
+ * Copyright (C) 2013-2016 Apple Inc. All rights reserved.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public License
+ * along with this library; see the file COPYING.LIB.  If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifndef EventPath_h
+#define EventPath_h
+
+#include "EventContext.h"
+#include "PseudoElement.h"
+#include "SVGElement.h"
+#include "SVGUseElement.h"
+#include <wtf/Forward.h>
+#include <wtf/Vector.h>
+
+namespace WebCore {
+
+class EventPath {
+public:
+    EventPath(Node& origin, Event&);
+
+    bool isEmpty() const { return m_path.isEmpty(); }
+    size_t size() const { return m_path.size(); }
+    const EventContext& contextAt(size_t i) const { return *m_path[i]; }
+    EventContext& contextAt(size_t i) { return *m_path[i]; }
+
+#if ENABLE(TOUCH_EVENTS)
+    void retargetTouchLists(const TouchEvent&);
+#endif
+    void setRelatedTarget(Node& origin, EventTarget&);
+
+    bool hasEventListeners(const AtomicString& eventType) const;
+
+    EventContext* lastContextIfExists() { return m_path.isEmpty() ? nullptr : m_path.last().get(); }
+
+    static EventTarget* eventTargetRespectingTargetRules(Node& referenceNode)
+    {
+        if (is<PseudoElement>(referenceNode))
+            return downcast<PseudoElement>(referenceNode).hostElement();
+
+        // Events sent to elements inside an SVG use element's shadow tree go to the use element.
+        if (is<SVGElement>(referenceNode)) {
+            if (auto* useElement = downcast<SVGElement>(referenceNode).correspondingUseElement())
+                return useElement;
+        }
+
+        return &referenceNode;
+    }
+
+private:
+#if ENABLE(TOUCH_EVENTS)
+    void retargetTouch(TouchEventContext::TouchListType, const Touch&);
+#endif
+
+    Event& m_event;
+    Vector<std::unique_ptr<EventContext>, 32> m_path;
+};
+
+}
+
+#endif /* EventPath_h */