blob: 50d4997a6e76bf1a09a6a9834a6157f609c3fec6 [file] [log] [blame]
/*
* 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;
}
}