summaryrefslogtreecommitdiffstats
path: root/Source/WebCore/dom/EventDispatcher.cpp
diff options
context:
space:
mode:
authorSteve Block <steveblock@google.com>2011-05-25 19:08:45 +0100
committerSteve Block <steveblock@google.com>2011-06-08 13:51:31 +0100
commit2bde8e466a4451c7319e3a072d118917957d6554 (patch)
tree28f4a1b869a513e565c7760d0e6a06e7cf1fe95a /Source/WebCore/dom/EventDispatcher.cpp
parent6939c99b71d9372d14a0c74a772108052e8c48c8 (diff)
downloadexternal_webkit-2bde8e466a4451c7319e3a072d118917957d6554.zip
external_webkit-2bde8e466a4451c7319e3a072d118917957d6554.tar.gz
external_webkit-2bde8e466a4451c7319e3a072d118917957d6554.tar.bz2
Merge WebKit at r82507: Initial merge by git
Change-Id: I60ce9d780725b58b45e54165733a8ffee23b683e
Diffstat (limited to 'Source/WebCore/dom/EventDispatcher.cpp')
-rw-r--r--Source/WebCore/dom/EventDispatcher.cpp396
1 files changed, 396 insertions, 0 deletions
diff --git a/Source/WebCore/dom/EventDispatcher.cpp b/Source/WebCore/dom/EventDispatcher.cpp
new file mode 100644
index 0000000..c8b330d
--- /dev/null
+++ b/Source/WebCore/dom/EventDispatcher.cpp
@@ -0,0 +1,396 @@
+/*
+ * 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 "PlatformWheelEvent.h"
+#include "ScopedEventQueue.h"
+
+#if ENABLE(SVG)
+#include "SVGElementInstance.h"
+#include "SVGNames.h"
+#include "SVGUseElement.h"
+#endif
+
+#include "UIEvent.h"
+#include "UIEventWithKeyState.h"
+#include "WheelEvent.h"
+#include "WindowEventContext.h"
+
+#include <wtf/RefPtr.h>
+
+namespace WebCore {
+
+static HashSet<Node*>* gNodesDispatchingSimulatedClicks = 0;
+
+bool EventDispatcher::dispatchEvent(Node* node, PassRefPtr<Event> prpEvent)
+{
+ RefPtr<Event> event = prpEvent;
+
+ EventDispatcher dispatcher(node);
+ return event->dispatch(&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->isShadowRoot() || !n->isSVGElement())
+ continue;
+
+ Element* shadowTreeParentElement = n->shadowHost();
+ 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)
+{
+ 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);
+}
+
+inline static WheelEvent::Granularity granularity(const PlatformWheelEvent& event)
+{
+ return event.granularity() == ScrollByPageWheelEvent ? WheelEvent::Page : WheelEvent::Pixel;
+}
+
+void EventDispatcher::dispatchWheelEvent(Node* node, PlatformWheelEvent& event)
+{
+ ASSERT(!eventDispatchForbidden());
+ if (!(event.deltaX() || event.deltaY()))
+ return;
+
+ EventDispatcher dispatcher(node);
+
+ if (!dispatcher.m_view)
+ return;
+
+ IntPoint position = dispatcher.m_view->windowToContents(event.pos());
+
+ int adjustedPageX = position.x();
+ int adjustedPageY = position.y();
+ if (Frame* frame = node->document()->frame()) {
+ float pageZoom = frame->pageZoomFactor();
+ if (pageZoom != 1.0f) {
+ adjustedPageX = lroundf(position.x() / pageZoom);
+ adjustedPageY = lroundf(position.y() / pageZoom);
+ }
+ }
+
+ RefPtr<WheelEvent> wheelEvent = WheelEvent::create(event.wheelTicksX(), event.wheelTicksY(), event.deltaX(), event.deltaY(), granularity(event),
+ node->document()->defaultView(), event.globalX(), event.globalY(), adjustedPageX, adjustedPageY,
+ event.ctrlKey(), event.altKey(), event.shiftKey(), event.metaKey());
+
+ wheelEvent->setAbsoluteLocation(position);
+
+ if (!dispatcher.dispatchEvent(wheelEvent) || wheelEvent->defaultHandled())
+ event.accept();
+
+}
+
+// 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.
+static Node* pullOutOfShadow(Node* node)
+{
+ Node* outermostShadowBoundary = node;
+ for (Node* n = node; n; n = n->parentOrHostNode()) {
+ if (n->isShadowRoot())
+ outermostShadowBoundary = n->parentOrHostNode();
+ }
+ return outermostShadowBoundary;
+}
+
+EventDispatcher::EventDispatcher(Node* node)
+ : m_node(node)
+{
+ ASSERT(node);
+ m_view = node->document()->view();
+}
+
+void EventDispatcher::getEventAncestors(EventTarget* originalTarget, EventDispatchBehavior behavior)
+{
+ if (!m_node->inDocument())
+ return;
+
+ if (ancestorsInitialized())
+ return;
+
+ EventTarget* target = originalTarget;
+ Node* ancestor = m_node.get();
+ bool shouldSkipNextAncestor = false;
+ while (true) {
+ if (ancestor->isShadowRoot()) {
+ if (behavior == StayInsideShadowDOM)
+ return;
+ ancestor = ancestor->shadowHost();
+ if (!shouldSkipNextAncestor)
+ target = ancestor;
+ } else
+ ancestor = ancestor->parentNodeGuaranteedHostFree();
+
+ if (!ancestor)
+ return;
+
+#if ENABLE(SVG)
+ // Skip SVGShadowTreeRootElement.
+ shouldSkipNextAncestor = ancestor->isSVGElement() && ancestor->isShadowRoot();
+ 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();
+ getEventAncestors(originalTarget.get(), determineDispatchBehavior(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();
+}
+bool EventDispatcher::dispatchMouseEvent(Node* node, const PlatformMouseEvent& event, const AtomicString& eventType,
+ int detail, Node* relatedTargetArg)
+{
+ ASSERT(!eventDispatchForbidden());
+ ASSERT(event.eventType() == MouseEventMoved || event.button() != NoButton);
+
+ if (node->disabled()) // Don't even send DOM events for disabled controls..
+ return true;
+
+ if (eventType.isEmpty())
+ return false; // Shouldn't happen.
+
+ EventDispatcher dispatcher(node);
+
+ // Attempting to dispatch with a non-EventTarget relatedTarget causes the relatedTarget to be silently ignored.
+ RefPtr<Node> relatedTarget = pullOutOfShadow(relatedTargetArg);
+
+ IntPoint contentsPosition;
+ if (FrameView* view = node->document()->view())
+ contentsPosition = view->windowToContents(event.pos());
+
+ IntPoint adjustedPagePosition = contentsPosition;
+ if (Frame* frame = node->document()->frame()) {
+ float pageZoom = frame->pageZoomFactor();
+ if (pageZoom != 1.0f) {
+ // Adjust our pageX and pageY to account for the page zoom.
+ adjustedPagePosition.setX(lroundf(contentsPosition.x() / pageZoom));
+ adjustedPagePosition.setY(lroundf(contentsPosition.y() / pageZoom));
+ }
+ }
+
+ RefPtr<MouseEvent> mouseEvent = MouseEvent::create(eventType, node->document()->defaultView(), event, adjustedPagePosition, detail, relatedTarget);
+ mouseEvent->setAbsoluteLocation(contentsPosition);
+
+ bool swallowEvent = false;
+
+ dispatcher.dispatchEvent(mouseEvent);
+ bool defaultHandled = mouseEvent->defaultHandled();
+ bool defaultPrevented = mouseEvent->defaultPrevented();
+ if (defaultHandled || defaultPrevented)
+ swallowEvent = true;
+
+ // Special case: If it's a double click event, we also send the dblclick event. This is not part
+ // of the DOM specs, but is used for compatibility with the ondblclick="" attribute. This is treated
+ // as a separate event in other DOM-compliant browsers like Firefox, and so we do the same.
+ if (eventType == eventNames().clickEvent && detail == 2) {
+ RefPtr<Event> doubleClickEvent = MouseEvent::create(eventNames().dblclickEvent, node->document()->defaultView(), event, adjustedPagePosition, detail, relatedTarget);
+ if (defaultHandled)
+ doubleClickEvent->setDefaultHandled();
+ dispatcher.dispatchEvent(doubleClickEvent);
+ if (doubleClickEvent->defaultHandled() || doubleClickEvent->defaultPrevented())
+ swallowEvent = true;
+ }
+
+ return swallowEvent;
+}
+
+const EventContext* EventDispatcher::topEventContext()
+{
+ return m_ancestors.isEmpty() ? 0 : &m_ancestors.last();
+}
+
+bool EventDispatcher::ancestorsInitialized() const
+{
+ return m_ancestors.size();
+}
+
+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;
+}
+
+}
+