EventDispatcher.cpp [plain text]
#include "config.h"
#include "EventDispatcher.h"
#include "EventContext.h"
#include "FocusEvent.h"
#include "FrameView.h"
#include "HTMLInputElement.h"
#include "HTMLMediaElement.h"
#include "InsertionPoint.h"
#include "InspectorInstrumentation.h"
#include "MouseEvent.h"
#include "PseudoElement.h"
#include "ScopedEventQueue.h"
#include "ShadowRoot.h"
#include "SVGNames.h"
#include "SVGUseElement.h"
#include "TouchEvent.h"
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 (!is<Document>(*topLevelContainer))
return;
m_window = downcast<Document>(*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(Node& origin, 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(is<ShadowRoot>(m_currentTreeScope->rootNode()));
ASSERT(&newTarget == downcast<ShadowRoot>(m_currentTreeScope->rootNode()).hostElement());
ASSERT(m_currentTreeScope->parentTreeScope() == &newTreeScope);
}
if (&newTreeScope == &m_relatedNodeTreeScope)
m_relatedNodeInCurrentTreeScope = &m_relatedNode;
else if (m_relatedNodeInCurrentTreeScope) {
ASSERT(m_currentTreeScope);
m_relatedNodeInCurrentTreeScope = &newTarget;
} else {
if (!m_currentTreeScope) {
TreeScope* newTreeScopeAncestor = &newTreeScope;
do {
m_relatedNodeInCurrentTreeScope = findHostOfTreeScopeInTargetTreeScope(m_relatedNodeTreeScope, *newTreeScopeAncestor);
newTreeScopeAncestor = newTreeScopeAncestor->parentTreeScope();
if (newTreeScopeAncestor == &m_relatedNodeTreeScope) {
m_relatedNodeInCurrentTreeScope = &m_relatedNode;
break;
}
} while (newTreeScopeAncestor && !m_relatedNodeInCurrentTreeScope);
}
ASSERT(m_relatedNodeInCurrentTreeScope || findHostOfTreeScopeInTargetTreeScope(newTreeScope, m_relatedNodeTreeScope)
|| &newTreeScope.documentScope() != &m_relatedNodeTreeScope.documentScope());
}
m_currentTreeScope = &newTreeScope;
return m_relatedNodeInCurrentTreeScope;
}
static Node* findHostOfTreeScopeInTargetTreeScope(const TreeScope& startingTreeScope, const TreeScope& targetScope)
{
ASSERT(&targetScope != &startingTreeScope);
Node* previousHost = nullptr;
for (const TreeScope* scope = &startingTreeScope; scope; scope = scope->parentTreeScope()) {
if (scope == &targetScope) {
ASSERT(previousHost);
ASSERT_WITH_SECURITY_IMPLICATION(&previousHost->treeScope() == &targetScope);
return previousHost;
}
if (is<ShadowRoot>(scope->rootNode()))
previousHost = downcast<ShadowRoot>(scope->rootNode()).hostElement();
else
ASSERT_WITH_SECURITY_IMPLICATION(!scope->parentTreeScope());
}
return nullptr;
}
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 (is<PseudoElement>(referenceNode))
return downcast<PseudoElement>(referenceNode).hostElement();
if (is<SVGElement>(referenceNode)) {
if (auto* useElement = downcast<SVGElement>(referenceNode).correspondingUseElement())
return useElement;
}
return &referenceNode;
}
void EventDispatcher::dispatchScopedEvent(Node& node, PassRefPtr<Event> event)
{
event->setTarget(eventTargetRespectingTargetRules(node));
ScopedEventQueue::singleton().enqueueEvent(event);
}
void EventDispatcher::dispatchSimulatedClick(Element* element, Event* underlyingEvent, SimulatedClickMouseEventOptions mouseEventOptions, SimulatedClickVisualOptions visualOptions)
{
if (element->isDisabledFormControl())
return;
DEPRECATED_DEFINE_STATIC_LOCAL(HashSet<Element*>, elementsDispatchingSimulatedClicks, ());
if (!elementsDispatchingSimulatedClicks.add(element).isNewEntry)
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);
dispatchEvent(element, SimulatedMouseEvent::create(eventNames().clickEvent, element->document().defaultView(), underlyingEvent, element));
elementsDispatchingSimulatedClicks.remove(element);
}
static void callDefaultEventHandlersInTheBubblingOrder(Event& event, const EventPath& path)
{
if (path.isEmpty())
return;
path.contextAt(0).node()->defaultEventHandler(&event);
ASSERT(!event.defaultPrevented());
if (event.defaultHandled() || !event.bubbles())
return;
size_t size = path.size();
for (size_t i = 1; i < size; ++i) {
path.contextAt(i).node()->defaultEventHandler(&event);
ASSERT(!event.defaultPrevented());
if (event.defaultHandled())
return;
}
}
static void dispatchEventInDOM(Event& event, const EventPath& path, WindowEventContext& windowEventContext)
{
event.setEventPhase(Event::CAPTURING_PHASE);
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())
continue;
eventContext.handleLocalEvents(event);
if (event.propagationStopped())
return;
}
event.setEventPhase(Event::AT_TARGET);
path.contextAt(0).handleLocalEvents(event);
if (event.propagationStopped())
return;
size_t size = path.size();
for (size_t i = 1; i < size; ++i) {
const EventContext& eventContext = path.contextAt(i);
if (eventContext.currentTargetSameAsTarget())
event.setEventPhase(Event::AT_TARGET);
else if (event.bubbles() && !event.cancelBubble())
event.setEventPhase(Event::BUBBLING_PHASE);
else
continue;
eventContext.handleLocalEvents(event);
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)
{
ASSERT_WITH_SECURITY_IMPLICATION(!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(*node, *relatedTarget);
#if ENABLE(TOUCH_EVENTS) && !PLATFORM(IOS)
if (is<TouchEvent>(*event)) {
if (!eventPath.updateTouchLists(downcast<TouchEvent>(*event)))
return true;
}
#endif
ChildNodesLazySnapshot::takeChildNodesLazySnapshot();
EventTarget* target = eventTargetRespectingTargetRules(*node);
event->setTarget(target);
if (!event->target())
return true;
ASSERT_WITH_SECURITY_IMPLICATION(!NoEventDispatchAssertion::isEventDispatchForbidden());
WindowEventContext windowEventContext(node.get(), eventPath.lastContextIfExists());
InputElementClickState clickHandlingState;
if (is<HTMLInputElement>(*node))
downcast<HTMLInputElement>(*node).willDispatchEvent(*event, clickHandlingState);
if (!event->propagationStopped() && !eventPath.isEmpty())
dispatchEventInDOM(*event, eventPath, windowEventContext);
event->setTarget(eventTargetRespectingTargetRules(*node));
event->setCurrentTarget(nullptr);
event->setEventPhase(0);
if (clickHandlingState.stateful)
downcast<HTMLInputElement>(*node).didDispatchClickEvent(*event, clickHandlingState);
if (!event->defaultPrevented() && !event->defaultHandled())
callDefaultEventHandlersInTheBubblingOrder(*event, eventPath);
event->setTarget(windowEventContext.target());
event->setCurrentTarget(0);
return !event->defaultPrevented();
}
static inline bool shouldEventCrossShadowBoundary(Event& event, ShadowRoot& shadowRoot, EventTarget& target)
{
Node* targetNode = target.toNode();
#if ENABLE(FULLSCREEN_API) && ENABLE(VIDEO)
if (targetNode) {
if (Element* element = targetNode->document().webkitCurrentFullScreenElement()) {
if (element->isMediaElement() && shadowRoot.hostElement() == element)
return false;
}
}
#endif
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& 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();
#endif
EventTarget* target = nullptr;
Node* node = nodeOrHostIfPseudoElement(&targetNode);
while (node) {
if (!target || !isSVGElement) target = eventTargetRespectingTargetRules(*node);
for (; node; node = node->parentNode()) {
EventTarget* currentTarget = eventTargetRespectingTargetRules(*node);
if (isMouseOrFocusEvent)
m_path.append(std::make_unique<MouseOrFocusEventContext>(node, currentTarget, target));
#if ENABLE(TOUCH_EVENTS) && !PLATFORM(IOS)
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 (!inDocument)
return;
if (is<ShadowRoot>(*node))
break;
}
if (!node || !shouldEventCrossShadowBoundary(event, downcast<ShadowRoot>(*node), *target))
return;
node = downcast<ShadowRoot>(*node).hostElement();
}
}
#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));
}
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);
addRelatedNodeResolversForTouchList(touchTargetResolvers, touchEvent.touches(), TouchEventContext::Touches);
addRelatedNodeResolversForTouchList(touchTargetResolvers, touchEvent.targetTouches(), TouchEventContext::TargetTouches);
addRelatedNodeResolversForTouchList(touchTargetResolvers, touchEvent.changedTouches(), TouchEventContext::ChangedTouches);
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));
}
}
return true;
}
#endif
void EventPath::setRelatedTarget(Node& origin, EventTarget& relatedTarget)
{
Node* relatedNode = relatedTarget.toNode();
if (!relatedNode)
return;
EventRelatedNodeResolver resolver(*relatedNode);
bool originIsRelatedTarget = &origin == relatedNode;
Node& rootNodeInOriginTreeScope = origin.treeScope().rootNode();
size_t eventPathSize = m_path.size();
size_t i = 0;
while (i < eventPathSize) {
Node* contextNode = m_path[i]->node();
Node* currentRelatedNode = resolver.moveToParentOrShadowHost(*contextNode);
if (!originIsRelatedTarget && m_path[i]->target() == currentRelatedNode)
break;
toMouseOrFocusEventContext(*m_path[i]).setRelatedTarget(currentRelatedNode);
i++;
if (originIsRelatedTarget && &rootNodeInOriginTreeScope == contextNode)
break;
}
m_path.shrink(i);
}
bool EventPath::hasEventListeners(const AtomicString& eventType) const
{
for (size_t i = 0; i < m_path.size(); i++) {
if (m_path[i]->node()->hasEventListeners(eventType))
return true;
}
return false;
}
}