blob: 607fda9f5b5b36c2c4f1d0023c9ec224d008490c [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, 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 "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 <wtf/RefPtr.h>
#include <wtf/UnusedParam.h>
#if ENABLE(SVG)
#include "SVGElementInstance.h"
#include "SVGNames.h"
#include "SVGUseElement.h"
#endif
namespace WebCore {
static HashSet<Node*>* gNodesDispatchingSimulatedClicks = 0;
EventRelatedTargetAdjuster::EventRelatedTargetAdjuster(PassRefPtr<Node> node, PassRefPtr<Node> relatedTarget)
: m_node(node)
, m_relatedTarget(relatedTarget)
{
ASSERT(m_node);
ASSERT(m_relatedTarget);
}
void EventRelatedTargetAdjuster::adjust(Vector<EventContext>& ancestors)
{
Vector<EventTarget*> relatedTargetStack;
TreeScope* lastTreeScope = 0;
Node* lastNode = 0;
for (ComposedShadowTreeParentWalker walker(m_relatedTarget.get()); walker.get(); walker.parentIncludingInsertionPointAndShadowRoot()) {
Node* node = walker.get();
if (relatedTargetStack.isEmpty())
relatedTargetStack.append(node);
else if (isInsertionPoint(node) && toInsertionPoint(node)->contains(lastNode))
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;
lastNode = node;
if (node->isShadowRoot()) {
ASSERT(!relatedTargetStack.isEmpty());
relatedTargetStack.removeLast();
}
}
lastTreeScope = 0;
EventTarget* adjustedRelatedTarget = 0;
for (Vector<EventContext>::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<TreeScope*> parentTreeScopes;
EventTarget* relatedTarget = 0;
while (scope) {
parentTreeScopes.append(scope);
RelatedTargetMap::const_iterator found = m_relatedTargetMap.find(scope);
if (found != m_relatedTargetMap.end()) {
relatedTarget = found->second;
break;
}
scope = scope->parentTreeScope();
}
for (Vector<TreeScope*>::iterator iter = parentTreeScopes.begin(); iter < parentTreeScopes.end(); ++iter)
m_relatedTargetMap.add(*iter, relatedTarget);
return relatedTarget;
}
bool EventDispatcher::dispatchEvent(Node* node, PassRefPtr<EventDispatchMediator> mediator)
{
ASSERT(!eventDispatchForbidden());
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-<use> shadow trees.
if (!shadowHostElement || !shadowHostElement->hasTagName(SVGNames::useTag))
return referenceNode;
SVGUseElement* useElement = static_cast<SVGUseElement*>(shadowHostElement);
if (SVGElementInstance* instance = useElement->instanceForShadowTreeElement(referenceNode))
return instance;
#endif
return referenceNode;
}
void EventDispatcher::dispatchScopedEvent(Node* node, PassRefPtr<EventDispatchMediator> 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, 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);
}
void EventDispatcher::adjustRelatedTarget(Event* event, PassRefPtr<EventTarget> prpRelatedTarget)
{
if (!prpRelatedTarget)
return;
RefPtr<Node> 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)
, m_shouldPreventDispatch(false)
{
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<EventTarget*> targetStack;
Node* last = 0;
for (ComposedShadowTreeParentWalker walker(m_node.get()); walker.get(); walker.parentIncludingInsertionPointAndShadowRoot()) {
Node* node = walker.get();
if (targetStack.isEmpty())
targetStack.append(eventTargetRespectingSVGTargetRules(node));
else if (isInsertionPoint(node) && toInsertionPoint(node)->contains(last))
targetStack.append(targetStack.last());
m_ancestors.append(EventContext(node, eventTargetRespectingSVGTargetRules(node), targetStack.last()));
if (!inDocument)
return;
last = node;
if (!node->isShadowRoot())
continue;
if (determineDispatchBehavior(event, toShadowRoot(node)) == StayInsideShadowDOM)
return;
if (!isSVGElement) {
ASSERT(!targetStack.isEmpty());
targetStack.removeLast();
}
}
}
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 (m_ancestors.isEmpty() || m_shouldPreventDispatch || 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() - 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())
goto doneDispatching;
}
event->setEventPhase(Event::AT_TARGET);
event->setTarget(originalTarget.get());
event->setCurrentTarget(eventTargetRespectingSVGTargetRules(m_node.get()));
m_ancestors[0].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 = 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())
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 = 1; 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, ShadowRoot* shadowRoot)
{
#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.
if (event->type() == eventNames().selectstartEvent)
return StayInsideShadowDOM;
return RetargetEvent;
}
}