diff options
Diffstat (limited to 'Source/WebCore/dom/EventDispatcher.cpp')
-rw-r--r-- | Source/WebCore/dom/EventDispatcher.cpp | 444 |
1 files changed, 71 insertions, 373 deletions
diff --git a/Source/WebCore/dom/EventDispatcher.cpp b/Source/WebCore/dom/EventDispatcher.cpp index b61d440ae..0c3facc3a 100644 --- a/Source/WebCore/dom/EventDispatcher.cpp +++ b/Source/WebCore/dom/EventDispatcher.cpp @@ -2,7 +2,7 @@ * Copyright (C) 1999 Lars Knoll (knoll@kde.org) * (C) 1999 Antti Koivisto (koivisto@kde.org) * (C) 2001 Dirk Mueller (mueller@kde.org) - * Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2013 Apple Inc. All rights reserved. + * Copyright (C) 2004-2016 Apple Inc. All rights reserved. * Copyright (C) 2008 Nokia Corporation and/or its subsidiary(-ies) * Copyright (C) 2009 Torch Mobile Inc. All rights reserved. (http://www.torchmobile.com/) * Copyright (C) 2010, 2011, 2012, 2013 Google Inc. All rights reserved. @@ -26,217 +26,38 @@ #include "config.h" #include "EventDispatcher.h" +#include "CompositionEvent.h" #include "EventContext.h" -#include "FocusEvent.h" +#include "EventPath.h" +#include "Frame.h" #include "FrameView.h" #include "HTMLInputElement.h" -#include "HTMLMediaElement.h" -#include "InsertionPoint.h" -#include "InspectorInstrumentation.h" +#include "InputEvent.h" +#include "KeyboardEvent.h" +#include "MainFrame.h" #include "MouseEvent.h" -#include "PseudoElement.h" +#include "NoEventDispatchAssertion.h" #include "ScopedEventQueue.h" #include "ShadowRoot.h" +#include "TextEvent.h" #include "TouchEvent.h" -#if ENABLE(SVG) -#include "SVGElementInstance.h" -#include "SVGNames.h" -#include "SVGUseElement.h" -#endif - namespace WebCore { -class WindowEventContext { -public: - WindowEventContext(PassRefPtr<Node>, const EventContext*); - - DOMWindow* window() const { return m_window.get(); } - EventTarget* target() const { return m_target.get(); } - bool handleLocalEvents(Event&); - -private: - RefPtr<DOMWindow> m_window; - RefPtr<EventTarget> m_target; -}; - -WindowEventContext::WindowEventContext(PassRefPtr<Node> node, const EventContext* topEventContext) -{ - Node* topLevelContainer = topEventContext ? topEventContext->node() : node.get(); - if (!topLevelContainer->isDocumentNode()) - return; - - m_window = toDocument(topLevelContainer)->domWindow(); - m_target = topEventContext ? topEventContext->target() : node.get(); -} - -bool WindowEventContext::handleLocalEvents(Event& event) -{ - if (!m_window) - return false; - - event.setTarget(m_target.get()); - event.setCurrentTarget(m_window.get()); - m_window->fireEventListeners(&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) - bool updateTouchLists(const TouchEvent&); -#endif - void setRelatedTarget(EventTarget&); - - bool hasEventListeners(const AtomicString& eventType) const; - - EventContext* lastContextIfExists() { return m_path.isEmpty() ? 0 : m_path.last().get(); } - -private: -#if ENABLE(TOUCH_EVENTS) && !PLATFORM(IOS) - void updateTouchListsInEventPath(const TouchList*, TouchEventContext::TouchListType); -#endif - - Vector<std::unique_ptr<EventContext>, 32> m_path; -}; - -class EventRelatedNodeResolver { -public: - EventRelatedNodeResolver(Node& relatedNode) - : m_relatedNode(relatedNode) - , m_relatedNodeTreeScope(relatedNode.treeScope()) - , m_relatedNodeInCurrentTreeScope(nullptr) - , m_currentTreeScope(nullptr) -#if ENABLE(TOUCH_EVENTS) && !PLATFORM(IOS) - , m_touch(0) - , m_touchListType(TouchEventContext::NotTouchList) -#endif - { - } - -#if ENABLE(TOUCH_EVENTS) && !PLATFORM(IOS) - EventRelatedNodeResolver(Touch& touch, TouchEventContext::TouchListType touchListType) - : m_relatedNode(*touch.target()->toNode()) - , m_relatedNodeTreeScope(m_relatedNode.treeScope()) - , m_relatedNodeInCurrentTreeScope(nullptr) - , m_currentTreeScope(nullptr) - , m_touch(&touch) - , m_touchListType(touchListType) - { - ASSERT(touch.target()->toNode()); - } -#endif - -#if ENABLE(TOUCH_EVENTS) && !PLATFORM(IOS) - Touch* touch() const { return m_touch; } - TouchEventContext::TouchListType touchListType() const { return m_touchListType; } -#endif - - Node* moveToParentOrShadowHost(Node& newTarget) - { - TreeScope& newTreeScope = newTarget.treeScope(); - if (&newTreeScope == m_currentTreeScope) - return m_relatedNodeInCurrentTreeScope; - - if (m_currentTreeScope) { - ASSERT(m_currentTreeScope->rootNode()->isShadowRoot()); - ASSERT(&newTarget == toShadowRoot(m_currentTreeScope->rootNode())->hostElement()); - ASSERT(m_currentTreeScope->parentTreeScope() == &newTreeScope); - } - - if (m_relatedNodeInCurrentTreeScope) { // relatedNode is under the current tree scope - 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. - - m_currentTreeScope = &newTreeScope; - - return m_relatedNodeInCurrentTreeScope; - } - -private: - Node& m_relatedNode; - const TreeScope& m_relatedNodeTreeScope; - Node* m_relatedNodeInCurrentTreeScope; - TreeScope* m_currentTreeScope; -#if ENABLE(TOUCH_EVENTS) && !PLATFORM(IOS) - Touch* m_touch; - TouchEventContext::TouchListType m_touchListType; -#endif -}; - -inline EventTarget& eventTargetRespectingTargetRules(Node& referenceNode) -{ - if (referenceNode.isPseudoElement()) { - EventTarget* hostElement = toPseudoElement(referenceNode).hostElement(); - ASSERT(hostElement); - return *hostElement; - } - -#if ENABLE(SVG) - if (!referenceNode.isSVGElement() || !referenceNode.isInShadowTree()) - return referenceNode; - - // Spec: The event handling for the non-exposed tree works as if the referenced element had been textually included - // as a deeply cloned child of the 'use' element, except that events are dispatched to the SVGElementInstance objects - Node* rootNode = referenceNode.treeScope().rootNode(); - Element* shadowHostElement = rootNode->isShadowRoot() ? toShadowRoot(rootNode)->hostElement() : 0; - // At this time, SVG nodes are not supported in non-<use> shadow trees. - if (!shadowHostElement || !shadowHostElement->hasTagName(SVGNames::useTag)) - return referenceNode; - SVGUseElement* useElement = toSVGUseElement(shadowHostElement); - if (SVGElementInstance* instance = useElement->instanceForShadowTreeElement(&referenceNode)) - return *instance; -#endif - - return referenceNode; -} - -void EventDispatcher::dispatchScopedEvent(Node& node, PassRefPtr<Event> event) +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)); - ScopedEventQueue::instance().enqueueEvent(event); + event.setTarget(EventPath::eventTargetRespectingTargetRules(node)); + ScopedEventQueue::singleton().enqueueEvent(event); } -void EventDispatcher::dispatchSimulatedClick(Element* element, Event* underlyingEvent, SimulatedClickMouseEventOptions mouseEventOptions, SimulatedClickVisualOptions visualOptions) +static void callDefaultEventHandlersInTheBubblingOrder(Event& event, const EventPath& path) { - if (element->isDisabledFormControl()) - return; - - DEFINE_STATIC_LOCAL(HashSet<Element*>, elementsDispatchingSimulatedClicks, ()); - if (!elementsDispatchingSimulatedClicks.add(element).isNewEntry) + if (path.isEmpty()) return; - if (mouseEventOptions == SendMouseOverUpDownEvents) - dispatchEvent(element, SimulatedMouseEvent::create(eventNames().mouseoverEvent, element->document().defaultView(), underlyingEvent, element)); - - if (mouseEventOptions != SendNoEvents) - dispatchEvent(element, SimulatedMouseEvent::create(eventNames().mousedownEvent, element->document().defaultView(), underlyingEvent, element)); - element->setActive(true, visualOptions == ShowPressedLook); - if (mouseEventOptions != SendNoEvents) - dispatchEvent(element, SimulatedMouseEvent::create(eventNames().mouseupEvent, element->document().defaultView(), underlyingEvent, element)); - element->setActive(false); - - // always send click - dispatchEvent(element, SimulatedMouseEvent::create(eventNames().clickEvent, element->document().defaultView(), underlyingEvent, element)); - - elementsDispatchingSimulatedClicks.remove(element); -} - -static void callDefaultEventHandlersInTheBubblingOrder(Event& event, const EventPath& path) -{ // Non-bubbling events call only one default event handler, the one for the target. - path.contextAt(0).node()->defaultEventHandler(&event); + path.contextAt(0).node()->defaultEventHandler(event); ASSERT(!event.defaultPrevented()); if (event.defaultHandled() || !event.bubbles()) @@ -244,24 +65,18 @@ static void callDefaultEventHandlersInTheBubblingOrder(Event& event, const Event size_t size = path.size(); for (size_t i = 1; i < size; ++i) { - path.contextAt(i).node()->defaultEventHandler(&event); + path.contextAt(i).node()->defaultEventHandler(event); ASSERT(!event.defaultPrevented()); if (event.defaultHandled()) return; } } -static void dispatchEventInDOM(Event& event, const EventPath& path, WindowEventContext& windowEventContext) +static void dispatchEventInDOM(Event& event, const EventPath& path) { // Trigger capturing event handlers, starting at the top and working our way down. event.setEventPhase(Event::CAPTURING_PHASE); - // We don't dispatch load events to the window. This quirk was originally - // added because Mozilla doesn't propagate load events to the window object. - bool shouldFireEventAtWindow = event.type() != eventNames().loadEvent; - if (shouldFireEventAtWindow && windowEventContext.handleLocalEvents(event) && event.propagationStopped()) - return; - for (size_t i = path.size() - 1; i > 0; --i) { const EventContext& eventContext = path.contextAt(i); if (eventContext.currentTargetSameAsTarget()) @@ -282,7 +97,7 @@ static void dispatchEventInDOM(Event& event, const EventPath& path, WindowEventC const EventContext& eventContext = path.contextAt(i); if (eventContext.currentTargetSameAsTarget()) event.setEventPhase(Event::AT_TARGET); - else if (event.bubbles() && !event.cancelBubble()) + else if (event.bubbles()) event.setEventPhase(Event::BUBBLING_PHASE); else continue; @@ -290,200 +105,83 @@ static void dispatchEventInDOM(Event& event, const EventPath& path, WindowEventC if (event.propagationStopped()) return; } - if (event.bubbles() && !event.cancelBubble()) { - event.setEventPhase(Event::BUBBLING_PHASE); - if (shouldFireEventAtWindow) - windowEventContext.handleLocalEvents(event); - } } -bool EventDispatcher::dispatchEvent(Node* origin, PassRefPtr<Event> prpEvent) +static bool shouldSuppressEventDispatchInDOM(Node& node, Event& event) { - ASSERT(!NoEventDispatchAssertion::isEventDispatchForbidden()); - if (!prpEvent) - return true; - - ASSERT(origin); - RefPtr<Node> node(origin); - RefPtr<Event> event(prpEvent); - RefPtr<FrameView> view = node->document().view(); - EventPath eventPath(*node, *event); - - if (EventTarget* relatedTarget = event->relatedTarget()) - eventPath.setRelatedTarget(*relatedTarget); -#if ENABLE(TOUCH_EVENTS) && !PLATFORM(IOS) - if (event->isTouchEvent()) { - if (!eventPath.updateTouchLists(*toTouchEvent(event.get()))) - return true; - } -#endif - - ChildNodesLazySnapshot::takeChildNodesLazySnapshot(); - - event->setTarget(&eventTargetRespectingTargetRules(*node)); - ASSERT(!NoEventDispatchAssertion::isEventDispatchForbidden()); - ASSERT(event->target()); - WindowEventContext windowEventContext(node.get(), eventPath.lastContextIfExists()); - - InputElementClickState clickHandlingState; - if (isHTMLInputElement(node.get())) - toHTMLInputElement(*node).willDispatchEvent(*event, clickHandlingState); - - if (!event->propagationStopped() && !eventPath.isEmpty()) - dispatchEventInDOM(*event, eventPath, windowEventContext); - - event->setTarget(&eventTargetRespectingTargetRules(*node)); - event->setCurrentTarget(0); - event->setEventPhase(0); - - if (clickHandlingState.stateful) - toHTMLInputElement(*node).didDispatchClickEvent(*event, clickHandlingState); - - // Call default event handlers. While the DOM does have a concept of preventing - // default handling, the detail of which handlers are called is an internal - // implementation detail and not part of the DOM. - if (!event->defaultPrevented() && !event->defaultHandled()) - callDefaultEventHandlersInTheBubblingOrder(*event, eventPath); + if (!event.isTrusted()) + return false; - // Ensure that after event dispatch, the event's target object is the - // outermost shadow DOM boundary. - event->setTarget(windowEventContext.target()); - event->setCurrentTarget(0); + auto frame = node.document().frame(); + if (!frame) + return false; - return !event->defaultPrevented(); -} + if (!frame->mainFrame().loader().shouldSuppressKeyboardInput()) + return false; -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.hostElement() == element) - return false; - } + if (is<TextEvent>(event)) { + auto& textEvent = downcast<TextEvent>(event); + return textEvent.isKeyboard() || textEvent.isComposition(); } -#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); + return is<CompositionEvent>(event) || is<InputEvent>(event) || is<KeyboardEvent>(event); } -static Node* nodeOrHostIfPseudoElement(Node* node) +bool EventDispatcher::dispatchEvent(Node& node, Event& event) { - return node->isPseudoElement() ? toPseudoElement(node)->hostElement() : node; -} + ASSERT_WITH_SECURITY_IMPLICATION(NoEventDispatchAssertion::isEventAllowedInMainThread()); + Ref<Node> protectedNode(node); + RefPtr<FrameView> view = node.document().view(); + EventPath eventPath(node, event); -EventPath::EventPath(Node& targetNode, Event& event) -{ - bool inDocument = targetNode.inDocument(); - bool isSVGElement = targetNode.isSVGElement(); - bool isMouseOrFocusEvent = event.isMouseEvent() || event.isFocusEvent(); -#if ENABLE(TOUCH_EVENTS) && !PLATFORM(IOS) - bool isTouchEvent = event.isTouchEvent(); + if (EventTarget* relatedTarget = event.relatedTarget()) + eventPath.setRelatedTarget(node, *relatedTarget); +#if ENABLE(TOUCH_EVENTS) + if (is<TouchEvent>(event)) + eventPath.retargetTouchLists(downcast<TouchEvent>(event)); #endif - EventTarget* target = 0; - Node* node = nodeOrHostIfPseudoElement(&targetNode); - while (node) { - if (!target || !isSVGElement) // FIXME: This code doesn't make sense once we've climbed out of the SVG subtree in a HTML document. - target = &eventTargetRespectingTargetRules(*node); - for (; node; node = node->parentNode()) { - EventTarget& currentTarget = eventTargetRespectingTargetRules(*node); - if (isMouseOrFocusEvent) - m_path.append(std::make_unique<MouseOrFocusEventContext>(node, ¤tTarget, target)); -#if ENABLE(TOUCH_EVENTS) && !PLATFORM(IOS) - else if (isTouchEvent) - m_path.append(std::make_unique<TouchEventContext>(node, ¤tTarget, target)); -#endif - else - m_path.append(std::make_unique<EventContext>(node, ¤tTarget, target)); - if (!inDocument) - return; - if (node->isShadowRoot()) - break; - } - if (!node || !shouldEventCrossShadowBoundary(event, *toShadowRoot(node), *target)) - return; - node = toShadowRoot(node)->hostElement(); - } -} + ChildNodesLazySnapshot::takeChildNodesLazySnapshot(); -#if ENABLE(TOUCH_EVENTS) && !PLATFORM(IOS) -static void addRelatedNodeResolversForTouchList(Vector<EventRelatedNodeResolver, 16>& touchTargetResolvers, TouchList* touchList, TouchEventContext::TouchListType type) -{ - const size_t touchListSize = touchList->length(); - for (size_t i = 0; i < touchListSize; ++i) - touchTargetResolvers.append(EventRelatedNodeResolver(*touchList->item(i), type)); -} + EventTarget* target = EventPath::eventTargetRespectingTargetRules(node); + event.setTarget(target); + if (!event.target()) + return true; -bool EventPath::updateTouchLists(const TouchEvent& touchEvent) -{ - if (!touchEvent.touches() || !touchEvent.targetTouches() || !touchEvent.changedTouches()) - return false; - - Vector<EventRelatedNodeResolver, 16> touchTargetResolvers; - const size_t touchNodeCount = touchEvent.touches()->length() + touchEvent.targetTouches()->length() + touchEvent.changedTouches()->length(); - touchTargetResolvers.reserveInitialCapacity(touchNodeCount); + ASSERT_WITH_SECURITY_IMPLICATION(NoEventDispatchAssertion::isEventAllowedInMainThread()); + + InputElementClickState clickHandlingState; + if (is<HTMLInputElement>(node)) + downcast<HTMLInputElement>(node).willDispatchEvent(event, clickHandlingState); - addRelatedNodeResolversForTouchList(touchTargetResolvers, touchEvent.touches(), TouchEventContext::Touches); - addRelatedNodeResolversForTouchList(touchTargetResolvers, touchEvent.targetTouches(), TouchEventContext::TargetTouches); - addRelatedNodeResolversForTouchList(touchTargetResolvers, touchEvent.changedTouches(), TouchEventContext::ChangedTouches); + if (shouldSuppressEventDispatchInDOM(node, event)) + event.stopPropagation(); - ASSERT(touchTargetResolvers.size() == touchNodeCount); - size_t eventPathSize = m_path.size(); - for (size_t i = 0; i < eventPathSize; ++i) { - TouchEventContext& context = toTouchEventContext(*m_path[i]); - Node& nodeToMoveTo = *context.node(); - for (size_t resolverIndex = 0; resolverIndex < touchNodeCount; ++resolverIndex) { - EventRelatedNodeResolver& currentResolver = touchTargetResolvers[resolverIndex]; - Node* nodeInCurrentTreeScope = currentResolver.moveToParentOrShadowHost(nodeToMoveTo); - ASSERT(currentResolver.touch()); - context.touchList(currentResolver.touchListType())->append(currentResolver.touch()->cloneWithNewTarget(nodeInCurrentTreeScope)); - } + if (!event.propagationStopped() && !eventPath.isEmpty()) { + event.setEventPath(eventPath); + dispatchEventInDOM(event, eventPath); + event.clearEventPath(); } - return true; -} -#endif -void EventPath::setRelatedTarget(EventTarget& relatedTarget) -{ - Node* relatedNode = relatedTarget.toNode(); - if (!relatedNode) - return; + auto* finalTarget = event.target(); + event.setTarget(EventPath::eventTargetRespectingTargetRules(node)); + event.setCurrentTarget(nullptr); + event.resetPropagationFlags(); + event.setEventPhase(Event::NONE); - EventRelatedNodeResolver resolver(*relatedNode); + if (clickHandlingState.stateful) + downcast<HTMLInputElement>(node).didDispatchClickEvent(event, clickHandlingState); - 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())); -} + // Call default event handlers. While the DOM does have a concept of preventing + // default handling, the detail of which handlers are called is an internal + // implementation detail and not part of the DOM. + if (!event.defaultPrevented() && !event.defaultHandled()) + callDefaultEventHandlersInTheBubblingOrder(event, eventPath); -bool EventPath::hasEventListeners(const AtomicString& eventType) const -{ - for (size_t i = 0; i < m_path.size(); i++) { - if (m_path[i]->node()->hasEventListeners(eventType)) - return true; - } + event.setTarget(finalTarget); + event.setCurrentTarget(nullptr); - return false; + return !event.defaultPrevented(); } } |