/* * 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 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) 2011 Google 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 "EventDispatcher.h" #include "ComposedShadowTreeWalker.h" #include "ContainerNode.h" #include "ElementShadow.h" #include "EventContext.h" #include "EventDispatchMediator.h" #include "FrameView.h" #include "HTMLMediaElement.h" #include "InsertionPoint.h" #include "InspectorInstrumentation.h" #include "MouseEvent.h" #include "ScopedEventQueue.h" #include "ShadowRoot.h" #include "WindowEventContext.h" #include #include #if ENABLE(SVG) #include "SVGElementInstance.h" #include "SVGNames.h" #include "SVGUseElement.h" #endif namespace WebCore { static HashSet* gNodesDispatchingSimulatedClicks = 0; EventRelatedTargetAdjuster::EventRelatedTargetAdjuster(PassRefPtr node, PassRefPtr relatedTarget) : m_node(node) , m_relatedTarget(relatedTarget) { ASSERT(m_node); ASSERT(m_relatedTarget); } void EventRelatedTargetAdjuster::adjust(Vector& ancestors) { Vector relatedTargetStack; TreeScope* lastTreeScope = 0; for (AncestorChainWalker walker(m_relatedTarget.get()); walker.get(); walker.parent()) { Node* node = walker.get(); if (relatedTargetStack.isEmpty()) relatedTargetStack.append(node); else if (walker.crossingInsertionPoint()) relatedTargetStack.append(relatedTargetStack.last()); TreeScope* scope = node->treeScope(); // Skips adding a node to the map if treeScope does not change. Just for the performance optimization. if (scope != lastTreeScope) m_relatedTargetMap.add(scope, relatedTargetStack.last()); lastTreeScope = scope; if (node->isShadowRoot()) { ASSERT(!relatedTargetStack.isEmpty()); relatedTargetStack.removeLast(); } } lastTreeScope = 0; EventTarget* adjustedRelatedTarget = 0; for (Vector::iterator iter = ancestors.begin(); iter < ancestors.end(); ++iter) { TreeScope* scope = iter->node()->treeScope(); if (scope == lastTreeScope) { // Re-use the previous adjustedRelatedTarget if treeScope does not change. Just for the performance optimization. iter->setRelatedTarget(adjustedRelatedTarget); } else { adjustedRelatedTarget = findRelatedTarget(scope); iter->setRelatedTarget(adjustedRelatedTarget); } lastTreeScope = scope; if (iter->target() == adjustedRelatedTarget) { // Event dispatching should be stopped here. ancestors.shrink(iter - ancestors.begin()); break; } } } EventTarget* EventRelatedTargetAdjuster::findRelatedTarget(TreeScope* scope) { Vector parentTreeScopes; EventTarget* relatedTarget = 0; while (scope) { parentTreeScopes.append(scope); RelatedTargetMap::const_iterator found = m_relatedTargetMap.find(scope); if (found != m_relatedTargetMap.end()) { relatedTarget = found->value; break; } scope = scope->parentTreeScope(); } for (Vector::iterator iter = parentTreeScopes.begin(); iter < parentTreeScopes.end(); ++iter) m_relatedTargetMap.add(*iter, relatedTarget); return relatedTarget; } bool EventDispatcher::dispatchEvent(Node* node, PassRefPtr mediator) { ASSERT(!NoEventDispatchAssertion::isEventDispatchForbidden()); EventDispatcher dispatcher(node); return mediator->dispatchEvent(&dispatcher); } inline static EventTarget* eventTargetRespectingSVGTargetRules(Node* referenceNode) { ASSERT(referenceNode); #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 Element* shadowHostElement = toShadowRoot(referenceNode->treeScope()->rootNode())->host(); // At this time, SVG nodes are not supported in non- shadow trees. if (!shadowHostElement || !shadowHostElement->hasTagName(SVGNames::useTag)) return referenceNode; SVGUseElement* useElement = static_cast(shadowHostElement); if (SVGElementInstance* instance = useElement->instanceForShadowTreeElement(referenceNode)) return instance; #endif return referenceNode; } void EventDispatcher::adjustRelatedTarget(Event* event, PassRefPtr prpRelatedTarget) { if (!prpRelatedTarget) return; RefPtr relatedTarget = prpRelatedTarget->toNode(); if (!relatedTarget) return; if (!m_node.get()) return; ensureEventAncestors(event); EventRelatedTargetAdjuster(m_node, relatedTarget.release()).adjust(m_ancestors); } EventDispatcher::EventDispatcher(Node* node) : m_node(node) , m_ancestorsInitialized(false) #ifndef NDEBUG , m_eventDispatched(false) #endif { ASSERT(node); m_view = node->document()->view(); } void EventDispatcher::ensureEventAncestors(Event* event) { if (m_ancestorsInitialized) return; m_ancestorsInitialized = true; bool inDocument = m_node->inDocument(); bool isSVGElement = m_node->isSVGElement(); Vector targetStack; for (AncestorChainWalker walker(m_node.get()); walker.get(); walker.parent()) { Node* node = walker.get(); if (targetStack.isEmpty()) targetStack.append(eventTargetRespectingSVGTargetRules(node)); else if (walker.crossingInsertionPoint()) targetStack.append(targetStack.last()); m_ancestors.append(EventContext(node, eventTargetRespectingSVGTargetRules(node), targetStack.last())); if (!inDocument) return; if (!node->isShadowRoot()) continue; if (determineDispatchBehavior(event, toShadowRoot(node), targetStack.last()) == StayInsideShadowDOM) return; if (!isSVGElement) { ASSERT(!targetStack.isEmpty()); targetStack.removeLast(); } } } void EventDispatcher::dispatchScopedEvent(Node* node, PassRefPtr mediator) { // We need to set the target here because it can go away by the time we actually fire the event. mediator->event()->setTarget(eventTargetRespectingSVGTargetRules(node)); ScopedEventQueue::instance()->enqueueEventDispatchMediator(mediator); } void EventDispatcher::dispatchSimulatedClick(Node* node, Event* underlyingEvent, SimulatedClickMouseEventOptions mouseEventOptions, SimulatedClickVisualOptions visualOptions) { if (node->disabled()) return; if (!gNodesDispatchingSimulatedClicks) gNodesDispatchingSimulatedClicks = new HashSet; else if (gNodesDispatchingSimulatedClicks->contains(node)) return; gNodesDispatchingSimulatedClicks->add(node); if (mouseEventOptions == SendMouseOverUpDownEvents) EventDispatcher(node).dispatchEvent(SimulatedMouseEvent::create(eventNames().mouseoverEvent, node->document()->defaultView(), underlyingEvent)); if (mouseEventOptions != SendNoEvents) EventDispatcher(node).dispatchEvent(SimulatedMouseEvent::create(eventNames().mousedownEvent, node->document()->defaultView(), underlyingEvent)); node->setActive(true, visualOptions == ShowPressedLook); if (mouseEventOptions != SendNoEvents) EventDispatcher(node).dispatchEvent(SimulatedMouseEvent::create(eventNames().mouseupEvent, node->document()->defaultView(), underlyingEvent)); node->setActive(false); // always send click EventDispatcher(node).dispatchEvent(SimulatedMouseEvent::create(eventNames().clickEvent, node->document()->defaultView(), underlyingEvent)); gNodesDispatchingSimulatedClicks->remove(node); } bool EventDispatcher::dispatchEvent(PassRefPtr prpEvent) { #ifndef NDEBUG ASSERT(!m_eventDispatched); m_eventDispatched = true; #endif RefPtr event = prpEvent; ChildNodesLazySnapshot::takeChildNodesLazySnapshot(); event->setTarget(eventTargetRespectingSVGTargetRules(m_node.get())); ASSERT(!NoEventDispatchAssertion::isEventDispatchForbidden()); ASSERT(event->target()); ASSERT(!event->type().isNull()); // JavaScript code can create an event with an empty name, but not null. ensureEventAncestors(event.get()); WindowEventContext windowEventContext(event.get(), m_node.get(), topEventContext()); InspectorInstrumentationCookie cookie = InspectorInstrumentation::willDispatchEvent(m_node->document(), *event, windowEventContext.window(), m_node.get(), m_ancestors); void* preDispatchEventHandlerResult; if (dispatchEventPreProcess(event, preDispatchEventHandlerResult) == ContinueDispatching) if (dispatchEventAtCapturing(event, windowEventContext) == ContinueDispatching) if (dispatchEventAtTarget(event) == ContinueDispatching) dispatchEventAtBubbling(event, windowEventContext); dispatchEventPostProcess(event, preDispatchEventHandlerResult); // Ensure that after event dispatch, the event's target object is the // outermost shadow DOM boundary. event->setTarget(windowEventContext.target()); event->setCurrentTarget(0); InspectorInstrumentation::didDispatchEvent(cookie); return !event->defaultPrevented(); } inline EventDispatchContinuation EventDispatcher::dispatchEventPreProcess(PassRefPtr event, void*& preDispatchEventHandlerResult) { // Give the target node a chance to do some work before DOM event handlers get a crack. preDispatchEventHandlerResult = m_node->preDispatchEventHandler(event.get()); return (m_ancestors.isEmpty() || event->propagationStopped()) ? DoneDispatching : ContinueDispatching; } inline EventDispatchContinuation EventDispatcher::dispatchEventAtCapturing(PassRefPtr event, WindowEventContext& windowEventContext) { // Trigger capturing event handlers, starting at the top and working our way down. event->setEventPhase(Event::CAPTURING_PHASE); if (windowEventContext.handleLocalEvents(event.get()) && event->propagationStopped()) return DoneDispatching; for (size_t i = m_ancestors.size() - 1; i > 0; --i) { const EventContext& eventContext = m_ancestors[i]; if (eventContext.currentTargetSameAsTarget()) { if (event->bubbles()) continue; event->setEventPhase(Event::AT_TARGET); } else event->setEventPhase(Event::CAPTURING_PHASE); eventContext.handleLocalEvents(event.get()); if (event->propagationStopped()) return DoneDispatching; } return ContinueDispatching; } inline EventDispatchContinuation EventDispatcher::dispatchEventAtTarget(PassRefPtr event) { event->setEventPhase(Event::AT_TARGET); m_ancestors[0].handleLocalEvents(event.get()); return event->propagationStopped() ? DoneDispatching : ContinueDispatching; } inline EventDispatchContinuation EventDispatcher::dispatchEventAtBubbling(PassRefPtr event, WindowEventContext& windowContext) { if (event->bubbles() && !event->cancelBubble()) { // Trigger bubbling event handlers, starting at the bottom and working our way up. event->setEventPhase(Event::BUBBLING_PHASE); size_t size = m_ancestors.size(); for (size_t i = 1; i < size; ++i) { const EventContext& eventContext = m_ancestors[i]; if (eventContext.currentTargetSameAsTarget()) event->setEventPhase(Event::AT_TARGET); else event->setEventPhase(Event::BUBBLING_PHASE); eventContext.handleLocalEvents(event.get()); if (event->propagationStopped() || event->cancelBubble()) return DoneDispatching; } windowContext.handleLocalEvents(event.get()); } return ContinueDispatching; } inline void EventDispatcher::dispatchEventPostProcess(PassRefPtr event, void* preDispatchEventHandlerResult) { event->setTarget(eventTargetRespectingSVGTargetRules(m_node.get())); event->setCurrentTarget(0); event->setEventPhase(0); // Pass the data from the preDispatchEventHandler to the postDispatchEventHandler. m_node->postDispatchEventHandler(event.get(), preDispatchEventHandlerResult); // 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()) { // Non-bubbling events call only one default event handler, the one for the target. m_node->defaultEventHandler(event.get()); ASSERT(!event->defaultPrevented()); if (event->defaultHandled()) return; // For bubbling events, call default event handlers on the same targets in the // same order as the bubbling phase. if (event->bubbles()) { size_t size = m_ancestors.size(); for (size_t i = 1; i < size; ++i) { m_ancestors[i].node()->defaultEventHandler(event.get()); ASSERT(!event->defaultPrevented()); if (event->defaultHandled()) return; } } } } const EventContext* EventDispatcher::topEventContext() { return m_ancestors.isEmpty() ? 0 : &m_ancestors.last(); } static inline bool inTheSameScope(ShadowRoot* shadowRoot, EventTarget* target) { return target->toNode() && target->toNode()->treeScope()->rootNode() == shadowRoot; } EventDispatchBehavior EventDispatcher::determineDispatchBehavior(Event* event, ShadowRoot* shadowRoot, EventTarget* target) { #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 (Element* element = m_node->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 && shadowRoot->host() == element) return StayInsideShadowDOM; } #else UNUSED_PARAM(shadowRoot); #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(); if (inTheSameScope(shadowRoot, target) && (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 StayInsideShadowDOM; return RetargetEvent; } }