summaryrefslogtreecommitdiff
path: root/Source/WebCore/dom/EventPath.cpp
diff options
context:
space:
mode:
authorLorry Tar Creator <lorry-tar-importer@lorry>2017-06-27 06:07:23 +0000
committerLorry Tar Creator <lorry-tar-importer@lorry>2017-06-27 06:07:23 +0000
commit1bf1084f2b10c3b47fd1a588d85d21ed0eb41d0c (patch)
tree46dcd36c86e7fbc6e5df36deb463b33e9967a6f7 /Source/WebCore/dom/EventPath.cpp
parent32761a6cee1d0dee366b885b7b9c777e67885688 (diff)
downloadWebKitGtk-tarball-master.tar.gz
Diffstat (limited to 'Source/WebCore/dom/EventPath.cpp')
-rw-r--r--Source/WebCore/dom/EventPath.cpp406
1 files changed, 406 insertions, 0 deletions
diff --git a/Source/WebCore/dom/EventPath.cpp b/Source/WebCore/dom/EventPath.cpp
new file mode 100644
index 000000000..3161eaa37
--- /dev/null
+++ b/Source/WebCore/dom/EventPath.cpp
@@ -0,0 +1,406 @@
+/*
+ * 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 "DOMWindow.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 {
+
+class WindowEventContext final : public EventContext {
+public:
+ WindowEventContext(Node&, DOMWindow&, EventTarget*);
+ void handleLocalEvents(Event&) const final;
+};
+
+WindowEventContext::WindowEventContext(Node& node, DOMWindow& currentTarget, EventTarget* target)
+ : EventContext(&node, &currentTarget, target)
+{ }
+
+void WindowEventContext::handleLocalEvents(Event& event) const
+{
+ event.setTarget(m_target.get());
+ event.setCurrentTarget(m_currentTarget.get());
+ m_currentTarget->fireEventListeners(event);
+}
+
+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
+
+ bool targetIsInShadowRoot = targetNode && &targetNode->treeScope().rootNode() == &shadowRoot;
+ return !targetIsInShadowRoot || event.composed();
+}
+
+static Node* nodeOrHostIfPseudoElement(Node* node)
+{
+ return is<PseudoElement>(*node) ? downcast<PseudoElement>(*node).hostElement() : node;
+}
+
+class RelatedNodeRetargeter {
+public:
+ RelatedNodeRetargeter(Node& relatedNode, Node& target);
+
+ Node* currentNode(Node& currentTreeScope);
+ void moveToNewTreeScope(TreeScope* previousTreeScope, TreeScope& newTreeScope);
+
+private:
+
+ Node* nodeInLowestCommonAncestor();
+ void collectTreeScopes();
+
+#if ASSERT_DISABLED
+ void checkConsistency(Node&) { }
+#else
+ void checkConsistency(Node& currentTarget);
+#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)
+{
+ bool isMouseOrFocusEvent = event.isMouseEvent() || event.isFocusEvent();
+#if ENABLE(TOUCH_EVENTS)
+ bool isTouchEvent = event.isTouchEvent();
+#endif
+ Node* node = nodeOrHostIfPseudoElement(&originalTarget);
+ Node* target = node ? eventTargetRespectingTargetRules(*node) : nullptr;
+ while (node) {
+ while (node) {
+ 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;
+
+ ContainerNode* parent = node->parentNode();
+ if (UNLIKELY(!parent)) {
+ // https://dom.spec.whatwg.org/#interface-document
+ if (is<Document>(*node) && event.type() != eventNames().loadEvent) {
+ ASSERT(target);
+ if (auto* window = downcast<Document>(*node).domWindow())
+ m_path.append(std::make_unique<WindowEventContext>(*node, *window, target));
+ }
+ return;
+ }
+
+ auto* shadowRootOfParent = parent->shadowRoot();
+ if (UNLIKELY(shadowRootOfParent)) {
+ if (auto* assignedSlot = shadowRootOfParent->findAssignedSlot(*node)) {
+ // node is assigned to a slot. Continue dispatching the event at this slot.
+ parent = assignedSlot;
+ }
+ }
+ node = parent;
+ }
+
+ bool exitingShadowTreeOfTarget = &target->treeScope() == &node->treeScope();
+ ShadowRoot& shadowRoot = downcast<ShadowRoot>(*node);
+ if (!shouldEventCrossShadowBoundary(event, shadowRoot, originalTarget))
+ return;
+ node = shadowRoot.host();
+ if (exitingShadowTreeOfTarget)
+ target = eventTargetRespectingTargetRules(*node);
+
+ }
+}
+
+void EventPath::setRelatedTarget(Node& origin, EventTarget& relatedTarget)
+{
+ Node* relatedNode = relatedTarget.toNode();
+ if (!relatedNode || m_path.isEmpty())
+ return;
+
+ RelatedNodeRetargeter retargeter(*relatedNode, *m_path[0]->node());
+
+ bool originIsRelatedTarget = &origin == relatedNode;
+ Node& rootNodeInOriginTreeScope = origin.treeScope().rootNode();
+ TreeScope* previousTreeScope = nullptr;
+ size_t originalEventPathSize = m_path.size();
+ for (unsigned contextIndex = 0; contextIndex < originalEventPathSize; contextIndex++) {
+ auto& ambgiousContext = *m_path[contextIndex];
+ if (!is<MouseOrFocusEventContext>(ambgiousContext))
+ continue;
+ auto& context = downcast<MouseOrFocusEventContext>(ambgiousContext);
+
+ Node& currentTarget = *context.node();
+ TreeScope& currentTreeScope = currentTarget.treeScope();
+ if (UNLIKELY(previousTreeScope && &currentTreeScope != previousTreeScope))
+ retargeter.moveToNewTreeScope(previousTreeScope, currentTreeScope);
+
+ Node* currentRelatedNode = retargeter.currentNode(currentTarget);
+ if (UNLIKELY(!originIsRelatedTarget && context.target() == currentRelatedNode)) {
+ m_path.shrink(contextIndex);
+ break;
+ }
+
+ context.setRelatedTarget(currentRelatedNode);
+
+ if (UNLIKELY(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* previousTreeScope = nullptr;
+ for (auto& context : m_path) {
+ Node& currentTarget = *context->node();
+ TreeScope& currentTreeScope = currentTarget.treeScope();
+ if (UNLIKELY(previousTreeScope && &currentTreeScope != previousTreeScope))
+ retargeter.moveToNewTreeScope(previousTreeScope, currentTreeScope);
+
+ if (is<TouchEventContext>(*context)) {
+ Node* currentRelatedNode = retargeter.currentNode(currentTarget);
+ 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& context : m_path) {
+ if (context->node()->hasEventListeners(eventType))
+ return true;
+ }
+
+ return false;
+}
+
+// https://dom.spec.whatwg.org/#dom-event-composedpath
+Vector<EventTarget*> EventPath::computePathUnclosedToTarget(const EventTarget& target) const
+{
+ Vector<EventTarget*> path;
+ auto* targetNode = const_cast<EventTarget&>(target).toNode();
+ if (!targetNode) {
+ auto* domWindow = const_cast<EventTarget&>(target).toDOMWindow();
+ if (!domWindow)
+ return path;
+ targetNode = domWindow->document();
+ ASSERT(targetNode);
+ }
+ for (auto& context : m_path) {
+ if (auto* nodeInPath = context->currentTarget()->toNode()) {
+ if (!targetNode->isClosedShadowHidden(*nodeInPath))
+ path.append(context->currentTarget());
+ } else
+ path.append(context->currentTarget());
+ }
+ return path;
+}
+
+static Node* moveOutOfAllShadowRoots(Node& startingNode)
+{
+ Node* node = &startingNode;
+ while (node->isInShadowTree())
+ node = downcast<ShadowRoot>(node->treeScope().rootNode()).host();
+ return node;
+}
+
+RelatedNodeRetargeter::RelatedNodeRetargeter(Node& relatedNode, Node& target)
+ : m_relatedNode(relatedNode)
+ , m_retargetedRelatedNode(&relatedNode)
+{
+ auto& targetTreeScope = target.treeScope();
+ TreeScope* currentTreeScope = &m_relatedNode.treeScope();
+ if (LIKELY(currentTreeScope == &targetTreeScope && target.isConnected() && m_relatedNode.isConnected()))
+ return;
+
+ if (&currentTreeScope->documentScope() != &targetTreeScope.documentScope()) {
+ m_hasDifferentTreeRoot = true;
+ m_retargetedRelatedNode = nullptr;
+ return;
+ }
+ if (relatedNode.isConnected() != target.isConnected()) {
+ m_hasDifferentTreeRoot = true;
+ m_retargetedRelatedNode = moveOutOfAllShadowRoots(relatedNode);
+ 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;
+ }
+
+ bool lowestCommonAncestorIsDocumentScope = i + 1 == m_ancestorTreeScopes.size();
+ if (lowestCommonAncestorIsDocumentScope && !relatedNode.isConnected() && !target.isConnected()) {
+ Node& relatedNodeAncestorInDocumentScope = i ? *downcast<ShadowRoot>(m_ancestorTreeScopes[i - 1]->rootNode()).shadowHost() : relatedNode;
+ Node& targetAncestorInDocumentScope = j ? *downcast<ShadowRoot>(targetTreeScopeAncestors[j - 1]->rootNode()).shadowHost() : target;
+ if (&targetAncestorInDocumentScope.rootNode() != &relatedNodeAncestorInDocumentScope.rootNode()) {
+ m_hasDifferentTreeRoot = true;
+ m_retargetedRelatedNode = moveOutOfAllShadowRoots(relatedNode);
+ return;
+ }
+ }
+
+ m_lowestCommonAncestorIndex = i;
+ m_retargetedRelatedNode = nodeInLowestCommonAncestor();
+}
+
+inline Node* RelatedNodeRetargeter::currentNode(Node& currentTarget)
+{
+ checkConsistency(currentTarget);
+ 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(Node& currentTarget)
+{
+ if (!m_retargetedRelatedNode)
+ return;
+ ASSERT(!currentTarget.isClosedShadowHidden(*m_retargetedRelatedNode));
+ ASSERT(m_retargetedRelatedNode == &currentTarget.treeScope().retargetToScope(m_relatedNode));
+}
+#endif
+
+}