diff options
author | Lorry Tar Creator <lorry-tar-importer@lorry> | 2017-06-27 06:07:23 +0000 |
---|---|---|
committer | Lorry Tar Creator <lorry-tar-importer@lorry> | 2017-06-27 06:07:23 +0000 |
commit | 1bf1084f2b10c3b47fd1a588d85d21ed0eb41d0c (patch) | |
tree | 46dcd36c86e7fbc6e5df36deb463b33e9967a6f7 /Source/WebCore/dom/EventPath.cpp | |
parent | 32761a6cee1d0dee366b885b7b9c777e67885688 (diff) | |
download | WebKitGtk-tarball-master.tar.gz |
webkitgtk-2.16.5HEADwebkitgtk-2.16.5master
Diffstat (limited to 'Source/WebCore/dom/EventPath.cpp')
-rw-r--r-- | Source/WebCore/dom/EventPath.cpp | 406 |
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, ¤tTarget, 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 && ¤tTreeScope != 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 = ¤tTreeScope; + } +} + +#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 && ¤tTreeScope != previousTreeScope)) + retargeter.moveToNewTreeScope(previousTreeScope, currentTreeScope); + + if (is<TouchEventContext>(*context)) { + Node* currentRelatedNode = retargeter.currentNode(currentTarget); + downcast<TouchEventContext>(*context).touchList(touchListType)->append(touch.cloneWithNewTarget(currentRelatedNode)); + } + + previousTreeScope = ¤tTreeScope; + } +} + +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 (¤tTreeScope->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 != ¤tRelatedNodeScope) { + // 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 == ¤tTarget.treeScope().retargetToScope(m_relatedNode)); +} +#endif + +} |