| /* |
| * 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 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 "Event.h" |
| #include "EventContext.h" |
| #include "EventTarget.h" |
| #include "FrameView.h" |
| #include "InspectorInstrumentation.h" |
| #include "MouseEvent.h" |
| #include "Node.h" |
| #include "ScopedEventQueue.h" |
| |
| #if ENABLE(SVG) |
| #include "SVGElementInstance.h" |
| #include "SVGNames.h" |
| #include "SVGUseElement.h" |
| #endif |
| |
| #include "UIEvent.h" |
| #include "UIEventWithKeyState.h" |
| #include "WindowEventContext.h" |
| |
| #include <wtf/RefPtr.h> |
| |
| namespace WebCore { |
| |
| static HashSet<Node*>* gNodesDispatchingSimulatedClicks = 0; |
| |
| bool EventDispatcher::dispatchEvent(Node* node, const EventDispatchMediator& mediator) |
| { |
| ASSERT(!eventDispatchForbidden()); |
| |
| EventDispatcher dispatcher(node); |
| return mediator.dispatchEvent(&dispatcher); |
| } |
| |
| static EventTarget* findElementInstance(Node* referenceNode) |
| { |
| #if ENABLE(SVG) |
| // 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 |
| for (Node* n = referenceNode; n; n = n->parentNode()) { |
| if (!n->isSVGShadowRoot() || !n->isSVGElement()) |
| continue; |
| |
| Element* shadowTreeParentElement = n->svgShadowHost(); |
| ASSERT(shadowTreeParentElement->hasTagName(SVGNames::useTag)); |
| |
| if (SVGElementInstance* instance = static_cast<SVGUseElement*>(shadowTreeParentElement)->instanceForShadowTreeElement(referenceNode)) |
| return instance; |
| } |
| #else |
| // SVG elements with SVG disabled should not be possible. |
| ASSERT_NOT_REACHED(); |
| #endif |
| |
| return referenceNode; |
| } |
| |
| inline static EventTarget* eventTargetRespectingSVGTargetRules(Node* referenceNode) |
| { |
| ASSERT(referenceNode); |
| |
| return referenceNode->isSVGElement() ? findElementInstance(referenceNode) : referenceNode; |
| } |
| |
| void EventDispatcher::dispatchScopedEvent(Node* node, PassRefPtr<Event> event) |
| { |
| // We need to set the target here because it can go away by the time we actually fire the event. |
| event->setTarget(eventTargetRespectingSVGTargetRules(node)); |
| |
| ScopedEventQueue::instance()->enqueueEvent(event); |
| } |
| |
| void EventDispatcher::dispatchSimulatedClick(Node* node, PassRefPtr<Event> underlyingEvent, bool sendMouseEvents, bool showPressedLook) |
| { |
| if (node->disabled()) |
| return; |
| |
| EventDispatcher dispatcher(node); |
| |
| if (!gNodesDispatchingSimulatedClicks) |
| gNodesDispatchingSimulatedClicks = new HashSet<Node*>; |
| else if (gNodesDispatchingSimulatedClicks->contains(node)) |
| return; |
| |
| gNodesDispatchingSimulatedClicks->add(node); |
| |
| // send mousedown and mouseup before the click, if requested |
| if (sendMouseEvents) |
| dispatcher.dispatchEvent(SimulatedMouseEvent::create(eventNames().mousedownEvent, node->document()->defaultView(), underlyingEvent)); |
| node->setActive(true, showPressedLook); |
| if (sendMouseEvents) |
| dispatcher.dispatchEvent(SimulatedMouseEvent::create(eventNames().mouseupEvent, node->document()->defaultView(), underlyingEvent)); |
| node->setActive(false); |
| |
| // always send click |
| dispatcher.dispatchEvent(SimulatedMouseEvent::create(eventNames().clickEvent, node->document()->defaultView(), underlyingEvent)); |
| |
| gNodesDispatchingSimulatedClicks->remove(node); |
| } |
| |
| static inline bool isShadowRootOrSVGShadowRoot(const Node* node) |
| { |
| return node->isShadowRoot() || node->isSVGShadowRoot(); |
| } |
| |
| PassRefPtr<EventTarget> EventDispatcher::adjustToShadowBoundaries(PassRefPtr<Node> relatedTarget, const Vector<Node*> relatedTargetAncestors) |
| { |
| Vector<EventContext>::const_iterator lowestCommonBoundary = m_ancestors.end(); |
| // Assume divergent boundary is the relatedTarget itself (in other words, related target ancestor chain does not cross any shadow DOM boundaries). |
| Vector<Node*>::const_iterator firstDivergentBoundary = relatedTargetAncestors.begin(); |
| |
| Vector<EventContext>::const_iterator targetAncestor = m_ancestors.end(); |
| // Walk down from the top, looking for lowest common ancestor, also monitoring shadow DOM boundaries. |
| bool diverged = false; |
| for (Vector<Node*>::const_iterator i = relatedTargetAncestors.end() - 1; i >= relatedTargetAncestors.begin(); --i) { |
| if (diverged) { |
| if (isShadowRootOrSVGShadowRoot(*i)) { |
| firstDivergentBoundary = i + 1; |
| break; |
| } |
| continue; |
| } |
| |
| if (targetAncestor == m_ancestors.begin()) { |
| diverged = true; |
| continue; |
| } |
| |
| targetAncestor--; |
| |
| if (isShadowRootOrSVGShadowRoot(*i)) |
| lowestCommonBoundary = targetAncestor; |
| |
| if ((*i) != (*targetAncestor).node()) |
| diverged = true; |
| } |
| |
| if (!diverged) { |
| // The relatedTarget is an ancestor or shadowHost of the target. |
| if (m_node->shadowHost() == relatedTarget.get()) |
| lowestCommonBoundary = m_ancestors.begin(); |
| } else if ((*firstDivergentBoundary) == m_node.get()) { |
| // Since ancestors does not contain target itself, we must account |
| // for the possibility that target is a shadowHost of relatedTarget |
| // and thus serves as the lowestCommonBoundary. |
| // Luckily, in this case the firstDivergentBoundary is target. |
| lowestCommonBoundary = m_ancestors.begin(); |
| } |
| |
| // Trim ancestors to lowestCommonBoundary to keep events inside of the common shadow DOM subtree. |
| if (lowestCommonBoundary != m_ancestors.end()) |
| m_ancestors.shrink(lowestCommonBoundary - m_ancestors.begin()); |
| // Set event's related target to the first encountered shadow DOM boundary in the divergent subtree. |
| return firstDivergentBoundary != relatedTargetAncestors.begin() ? *firstDivergentBoundary : relatedTarget; |
| } |
| |
| inline static bool ancestorsCrossShadowBoundaries(const Vector<EventContext>& ancestors) |
| { |
| return ancestors.isEmpty() || ancestors.first().node() == ancestors.last().node(); |
| } |
| |
| // FIXME: Once https://bugs.webkit.org/show_bug.cgi?id=52963 lands, this should |
| // be greatly improved. See https://bugs.webkit.org/show_bug.cgi?id=54025. |
| PassRefPtr<EventTarget> EventDispatcher::adjustRelatedTarget(Event* event, PassRefPtr<EventTarget> prpRelatedTarget) |
| { |
| if (!prpRelatedTarget) |
| return 0; |
| |
| RefPtr<Node> relatedTarget = prpRelatedTarget->toNode(); |
| if (!relatedTarget) |
| return 0; |
| |
| Node* target = m_node.get(); |
| if (!target) |
| return prpRelatedTarget; |
| |
| ensureEventAncestors(event); |
| |
| // Calculate early if the common boundary is even possible by looking at |
| // ancestors size and if the retargeting has occured (indicating the presence of shadow DOM boundaries). |
| // If there are no boundaries detected, the target and related target can't have a common boundary. |
| bool noCommonBoundary = ancestorsCrossShadowBoundaries(m_ancestors); |
| |
| Vector<Node*> relatedTargetAncestors; |
| Node* outermostShadowBoundary = relatedTarget.get(); |
| for (Node* n = outermostShadowBoundary; n; n = n->parentOrHostNode()) { |
| if (isShadowRootOrSVGShadowRoot(n)) |
| outermostShadowBoundary = n->parentOrHostNode(); |
| if (!noCommonBoundary) |
| relatedTargetAncestors.append(n); |
| } |
| |
| // Short-circuit the fast case when we know there is no need to calculate a common boundary. |
| if (noCommonBoundary) |
| return outermostShadowBoundary; |
| |
| return adjustToShadowBoundaries(relatedTarget.release(), relatedTargetAncestors); |
| } |
| |
| EventDispatcher::EventDispatcher(Node* node) |
| : m_node(node) |
| , m_ancestorsInitialized(false) |
| { |
| ASSERT(node); |
| m_view = node->document()->view(); |
| } |
| |
| void EventDispatcher::ensureEventAncestors(Event* event) |
| { |
| EventDispatchBehavior behavior = determineDispatchBehavior(event); |
| |
| if (!m_node->inDocument()) |
| return; |
| |
| if (m_ancestorsInitialized) |
| return; |
| |
| m_ancestorsInitialized = true; |
| |
| Node* ancestor = m_node.get(); |
| EventTarget* target = eventTargetRespectingSVGTargetRules(ancestor); |
| bool shouldSkipNextAncestor = false; |
| while (true) { |
| bool isSVGShadowRoot = ancestor->isSVGShadowRoot(); |
| if (isSVGShadowRoot || ancestor->isShadowRoot()) { |
| if (behavior == StayInsideShadowDOM) |
| return; |
| #if ENABLE(SVG) |
| ancestor = isSVGShadowRoot ? ancestor->svgShadowHost() : ancestor->shadowHost(); |
| #else |
| ancestor = ancestor->shadowHost(); |
| #endif |
| if (!shouldSkipNextAncestor) |
| target = ancestor; |
| } else |
| ancestor = ancestor->parentNodeGuaranteedHostFree(); |
| |
| if (!ancestor) |
| return; |
| |
| #if ENABLE(SVG) |
| // Skip SVGShadowTreeRootElement. |
| shouldSkipNextAncestor = ancestor->isSVGShadowRoot(); |
| if (shouldSkipNextAncestor) |
| continue; |
| #endif |
| // FIXME: Unroll the extra loop inside eventTargetRespectingSVGTargetRules into this loop. |
| m_ancestors.append(EventContext(ancestor, eventTargetRespectingSVGTargetRules(ancestor), target)); |
| } |
| } |
| |
| bool EventDispatcher::dispatchEvent(PassRefPtr<Event> event) |
| { |
| event->setTarget(eventTargetRespectingSVGTargetRules(m_node.get())); |
| |
| ASSERT(!eventDispatchForbidden()); |
| ASSERT(event->target()); |
| ASSERT(!event->type().isNull()); // JavaScript code can create an event with an empty name, but not null. |
| |
| RefPtr<EventTarget> originalTarget = event->target(); |
| ensureEventAncestors(event.get()); |
| |
| WindowEventContext windowContext(event.get(), m_node.get(), topEventContext()); |
| |
| InspectorInstrumentationCookie cookie = InspectorInstrumentation::willDispatchEvent(m_node->document(), *event, windowContext.window(), m_node.get(), m_ancestors); |
| |
| // Give the target node a chance to do some work before DOM event handlers get a crack. |
| void* data = m_node->preDispatchEventHandler(event.get()); |
| if (event->propagationStopped()) |
| goto doneDispatching; |
| |
| // Trigger capturing event handlers, starting at the top and working our way down. |
| event->setEventPhase(Event::CAPTURING_PHASE); |
| |
| if (windowContext.handleLocalEvents(event.get()) && event->propagationStopped()) |
| goto doneDispatching; |
| |
| for (size_t i = m_ancestors.size(); i; --i) { |
| m_ancestors[i - 1].handleLocalEvents(event.get()); |
| if (event->propagationStopped()) |
| goto doneDispatching; |
| } |
| |
| event->setEventPhase(Event::AT_TARGET); |
| event->setTarget(originalTarget.get()); |
| event->setCurrentTarget(eventTargetRespectingSVGTargetRules(m_node.get())); |
| m_node->handleLocalEvents(event.get()); |
| if (event->propagationStopped()) |
| goto doneDispatching; |
| |
| 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 = 0; i < size; ++i) { |
| m_ancestors[i].handleLocalEvents(event.get()); |
| if (event->propagationStopped() || event->cancelBubble()) |
| goto doneDispatching; |
| } |
| windowContext.handleLocalEvents(event.get()); |
| } |
| |
| doneDispatching: |
| event->setTarget(originalTarget.get()); |
| event->setCurrentTarget(0); |
| event->setEventPhase(0); |
| |
| // Pass the data from the preDispatchEventHandler to the postDispatchEventHandler. |
| m_node->postDispatchEventHandler(event.get(), data); |
| |
| // 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()) |
| goto doneWithDefault; |
| // 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 = 0; i < size; ++i) { |
| m_ancestors[i].node()->defaultEventHandler(event.get()); |
| ASSERT(!event->defaultPrevented()); |
| if (event->defaultHandled()) |
| goto doneWithDefault; |
| } |
| } |
| } |
| |
| doneWithDefault: |
| |
| // Ensure that after event dispatch, the event's target object is the |
| // outermost shadow DOM boundary. |
| event->setTarget(windowContext.target()); |
| event->setCurrentTarget(0); |
| InspectorInstrumentation::didDispatchEvent(cookie); |
| |
| return !event->defaultPrevented(); |
| } |
| |
| const EventContext* EventDispatcher::topEventContext() |
| { |
| return m_ancestors.isEmpty() ? 0 : &m_ancestors.last(); |
| } |
| |
| EventDispatchBehavior EventDispatcher::determineDispatchBehavior(Event* event) |
| { |
| // Per XBL 2.0 spec, mutation events should never cross shadow DOM boundary: |
| // http://dev.w3.org/2006/xbl2/#event-flow-and-targeting-across-shadow-s |
| if (event->isMutationEvent()) |
| return StayInsideShadowDOM; |
| |
| // 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. |
| if (event->type() == eventNames().selectstartEvent) |
| return StayInsideShadowDOM; |
| |
| return RetargetEvent; |
| } |
| |
| } |
| |