EventTarget.cpp   [plain text]


/*
 * This file is part of the DOM implementation for KDE.
 *
 * 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 Apple Inc. All rights reserved.
 * Copyright (C) 2006 Alexey Proskuryakov (ap@webkit.org)
 *           (C) 2007 Nikolas Zimmermann <zimmermann@kde.org>
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
 *
 */

#include "config.h"
#include "EventTarget.h"

#include "WebKitAnimationEvent.h"
#include "Node.h"
#include "NodeList.h"
#include "Document.h"
#include "Event.h"
#include "EventListener.h"
#include "EventNames.h"
#include "Frame.h"
#include "FrameView.h"
#include "kjs_proxy.h"
#include "RegisteredEventListener.h"
#include "WebCoreThread.h"

namespace WebCore {

using namespace EventNames;

#ifndef NDEBUG
static int gEventDispatchForbidden = 0;
#endif

EventTarget::~EventTarget()
{
}

EventTargetNode* EventTarget::toNode()
{
    return 0;
}

XMLHttpRequest* EventTarget::toXMLHttpRequest()
{
    return 0;
}

#if ENABLE(OFFLINE_WEB_APPLICATIONS)
DOMApplicationCache* EventTarget::toDOMApplicationCache()
{
    return 0;
}
#endif

#if ENABLE(SVG)
SVGElementInstance* EventTarget::toSVGElementInstance()
{
    return 0;
}
#endif

static inline void addListenerTypeToDocumentIfNeeded(const AtomicString& eventType, Document* document)
{
    Document::ListenerType type = static_cast<Document::ListenerType>(0);

    if (eventType == DOMSubtreeModifiedEvent)
        type = Document::DOMSUBTREEMODIFIED_LISTENER;
    else if (eventType == DOMNodeInsertedEvent)
        type = Document::DOMNODEINSERTED_LISTENER;
    else if (eventType == DOMNodeRemovedEvent)
        type = Document::DOMNODEREMOVED_LISTENER;
    else if (eventType == DOMNodeRemovedFromDocumentEvent)
        type = Document::DOMNODEREMOVEDFROMDOCUMENT_LISTENER;
    else if (eventType == DOMNodeInsertedIntoDocumentEvent)
        type = Document::DOMNODEINSERTEDINTODOCUMENT_LISTENER;
    else if (eventType == DOMAttrModifiedEvent)
        type = Document::DOMATTRMODIFIED_LISTENER;
    else if (eventType == DOMCharacterDataModifiedEvent)
        type = Document::DOMCHARACTERDATAMODIFIED_LISTENER;
    else if (eventType == overflowchangedEvent)
        type = Document::OVERFLOWCHANGED_LISTENER;
    else if (eventType == webkitAnimationStartEvent)
        type = Document::ANIMATIONSTART_LISTENER;
    else if (eventType == webkitAnimationEndEvent)
        type = Document::ANIMATIONEND_LISTENER;
    else if (eventType == webkitAnimationIterationEvent)
        type = Document::ANIMATIONITERATION_LISTENER;
    else if (eventType == webkitTransitionEndEvent)
        type = Document::TRANSITIONEND_LISTENER;

    if (type)
        document->addListenerType(type);
}

void EventTarget::addEventListener(EventTargetNode* referenceNode, const AtomicString& eventType, PassRefPtr<EventListener> listener, bool useCapture)
{
    ASSERT(referenceNode);

    // orientationchange and resize are window-only events, but we allow them to be registered in HTML via <body onresize=""> and <body onorientationchange="">.
    // If a page author adds a listener for one of these events via JavaScript, the event listener will be added for the node directly instead of added to the window.
    // Handle that case here by installing the event listener on the window.  This is similar to what HTMLBodyElement::parseMappedAttribute() does.
    if (eventType == orientationchangeEvent || eventType == resizeEvent) {
        referenceNode->document()->addWindowEventListener(eventType, listener, useCapture);
        return;
    }

    if (!referenceNode->document()->attached())
        return;

    addListenerTypeToDocumentIfNeeded(eventType, referenceNode->document());

    if (!referenceNode->m_regdListeners)
        referenceNode->m_regdListeners = new RegisteredEventListenerList;

    if (referenceNode == referenceNode->document() && eventType == scrollEvent)
        referenceNode->document()->incrementScrollEventListenersCount();

    // Remove existing identical listener set with identical arguments.
    // The DOM2 spec says that "duplicate instances are discarded" in this case.
    removeEventListener(referenceNode, eventType, listener.get(), useCapture);

    // adding the first one
    if (referenceNode->m_regdListeners->isEmpty() && !referenceNode->inDocument())
        referenceNode->document()->registerDisconnectedNodeWithEventListeners(referenceNode);

    referenceNode->m_regdListeners->append(new RegisteredEventListener(eventType, listener.get(), useCapture));

    HashMap<Node *, int> * listeners = NULL;

    if (eventType == mousewheelEvent)
        listeners = referenceNode->WebThreadGetCurrentDOMMouseWheelEventListeners();
    else if (eventType == mousemoveEvent || eventType == mouseoverEvent || eventType == mouseoutEvent)
        listeners = referenceNode->WebThreadGetCurrentDOMMouseMoveEventListeners();
    else if (eventType == mouseupEvent || eventType == mousedownEvent || eventType == clickEvent)
        listeners = referenceNode->WebThreadGetCurrentDOMMouseClickEventListeners();

    if (listeners)
        listeners->set(referenceNode, listeners->contains(referenceNode) ? listeners->get(referenceNode) + 1 : 1);

#if ENABLE(TOUCH_EVENTS)        
    if (eventType == touchstartEvent || eventType == touchmoveEvent || eventType == touchendEvent || eventType == touchcancelEvent ||
        eventType == gesturestartEvent || eventType == gesturechangeEvent || eventType == gestureendEvent)
        referenceNode->document()->addTouchEventListener(referenceNode);
#endif
}

void EventTarget::removeEventListener(EventTargetNode* referenceNode, const AtomicString& eventType, EventListener* listener, bool useCapture)
{
    ASSERT(referenceNode);

    // orientationchange and resize are window-only events, but we allow them to be registered in HTML via <body onresize=""> and <body onorientationchange="">.
    // If a page author adds a listener for one of these events via JavaScript, the event listener will be added for the node directly instead of added to the window.
    // Handle that case here by removing the event listener from the window, if the window's current event listener matches the one being removed.
    if (eventType == orientationchangeEvent || eventType == resizeEvent) {
        referenceNode->document()->removeWindowEventListener(eventType, listener, useCapture);
        return;
    }

    if (!referenceNode->m_regdListeners)
        return;

    RegisteredEventListener rl(eventType, listener, useCapture);

    RegisteredEventListenerList::Iterator end = referenceNode->m_regdListeners->end();
    for (RegisteredEventListenerList::Iterator it = referenceNode->m_regdListeners->begin(); it != end; ++it) {
         if (*(*it).get() == rl) {

            HashMap<Node *, int> * listeners = NULL;

            if (eventType == mousewheelEvent)
                listeners = referenceNode->WebThreadGetCurrentDOMMouseWheelEventListeners();
            else if (eventType == mousemoveEvent || eventType == mouseoverEvent || eventType == mouseoutEvent)
                listeners = referenceNode->WebThreadGetCurrentDOMMouseMoveEventListeners();
            else if (eventType == mouseupEvent || eventType == mousedownEvent || eventType == clickEvent)
                listeners = referenceNode->WebThreadGetCurrentDOMMouseClickEventListeners();

            if (listeners) {
                int count = listeners->get(referenceNode) - 1;
                if (!count) listeners->remove(referenceNode);
                else listeners->set(referenceNode, count);
            }

            if (referenceNode == referenceNode->document() && eventType == scrollEvent)
                referenceNode->document()->decrementScrollEventListenersCount();

#if ENABLE(TOUCH_EVENTS)
            if (eventType == touchstartEvent || eventType == touchmoveEvent || eventType == touchendEvent || eventType == touchcancelEvent ||
                eventType == gesturestartEvent || eventType == gesturechangeEvent || eventType == gestureendEvent)
                referenceNode->document()->removeTouchEventListener(referenceNode);
#endif

            (*it)->setRemoved(true);
            it = referenceNode->m_regdListeners->remove(it);

            // removed last
            if (referenceNode->m_regdListeners->isEmpty() && !referenceNode->inDocument())
                referenceNode->document()->unregisterDisconnectedNodeWithEventListeners(referenceNode);

            return;
        }
    }
}

bool EventTarget::dispatchGenericEvent(EventTargetNode* referenceNode, PassRefPtr<Event> e, ExceptionCode&, bool tempEvent)
{
    RefPtr<Event> evt(e);

    ASSERT(!eventDispatchForbidden());
    ASSERT(evt->target());
    ASSERT(!evt->type().isNull()); // JavaScript code could create an event with an empty name

    // work out what nodes to send event to
    DeprecatedPtrList<Node> nodeChain;

    if (referenceNode->inDocument()) {
        for (Node* n = referenceNode; n; n = n->eventParentNode()) {
            n->ref();
            nodeChain.prepend(n);
        }
    } else {
        // if node is not in the document just send event to itself 
        referenceNode->ref();
        nodeChain.prepend(referenceNode);
    }

    DeprecatedPtrListIterator<Node> it(nodeChain);

    // Before we begin dispatching events, give the target node a chance to do some work prior
    // to the DOM event handlers getting a crack.
    void* data = preDispatchEventHandler(evt.get());

    // trigger any capturing event handlers on our way down
    evt->setEventPhase(Event::CAPTURING_PHASE);
    it.toFirst();

    // Handle window events for capture phase, except load events, this quirk is needed
    // because Mozilla used to never propagate load events to the window object
    if (evt->type() != loadEvent && it.current()->isDocumentNode() && !evt->propagationStopped())
        static_cast<Document*>(it.current())->handleWindowEvent(evt.get(), true);

    EventTargetNode* eventTargetNode = 0;
    for (; it.current() && it.current() != referenceNode && !evt->propagationStopped(); ++it) {
        eventTargetNode = EventTargetNodeCast(it.current());
        evt->setCurrentTarget(eventTargetRespectingSVGTargetRules(eventTargetNode));

        eventTargetNode->handleLocalEvents(evt.get(), true);
    }

    // dispatch to the actual target node
    it.toLast();

    if (!evt->propagationStopped()) {
        evt->setEventPhase(Event::AT_TARGET);

        eventTargetNode = EventTargetNodeCast(it.current());
        evt->setCurrentTarget(eventTargetRespectingSVGTargetRules(eventTargetNode));

        // We do want capturing event listeners to be invoked here, even though
        // that violates the specification since Mozilla does it.
        eventTargetNode->handleLocalEvents(evt.get(), true);

        eventTargetNode->handleLocalEvents(evt.get(), false);
    }

    --it;

    // ok, now bubble up again (only non-capturing event handlers will be called)
    // ### recalculate the node chain here? (e.g. if target node moved in document by previous event handlers)
    // no. the DOM specs says:
    // The chain of EventTargets from the event target to the top of the tree
    // is determined before the initial dispatch of the event.
    // If modifications occur to the tree during event processing,
    // event flow will proceed based on the initial state of the tree.
    //
    // since the initial dispatch is before the capturing phase,
    // there's no need to recalculate the node chain.
    // (tobias)

    if (evt->bubbles()) {
        evt->setEventPhase(Event::BUBBLING_PHASE);

        for (; it.current() && !evt->propagationStopped() && !evt->cancelBubble(); --it) {
            eventTargetNode = EventTargetNodeCast(it.current());
            evt->setCurrentTarget(eventTargetRespectingSVGTargetRules(eventTargetNode));

            eventTargetNode->handleLocalEvents(evt.get(), false);
        }

        it.toFirst();

        // Handle window events for bubbling phase, except load events, this quirk is needed
        // because Mozilla used to never propagate load events at all
        if (evt->type() != loadEvent && it.current()->isDocumentNode() && !evt->propagationStopped() && !evt->cancelBubble()) {
            evt->setCurrentTarget(EventTargetNodeCast(it.current()));
            static_cast<Document*>(it.current())->handleWindowEvent(evt.get(), false);
        } 
    }

    evt->setCurrentTarget(0);
    evt->setEventPhase(0); // I guess this is correct, the spec does not seem to say
                           // anything about the default event handler phase.


    // Now call the post dispatch.
    postDispatchEventHandler(evt.get(), data);

    // now we call all default event handlers (this is not part of DOM - it is internal to WebCore)
    it.toLast();

    if (evt->bubbles())
        for (; it.current() && !evt->defaultPrevented() && !evt->defaultHandled(); --it)
            EventTargetNodeCast(it.current())->defaultEventHandler(evt.get());
    else if (!evt->defaultPrevented() && !evt->defaultHandled())
        EventTargetNodeCast(it.current())->defaultEventHandler(evt.get());

    // deref all nodes in chain
    it.toFirst();
    for (; it.current(); ++it)
        it.current()->deref(); // this may delete us

    Document::updateDocumentsRendering();

    // If tempEvent is true, this means that the DOM implementation
    // will not be storing a reference to the event, i.e.  there is no
    // way to retrieve it from javascript if a script does not already
    // have a reference to it in a variable.  So there is no need for
    // the interpreter to keep the event in it's cache
    Frame* frame = referenceNode->document()->frame();
    if (tempEvent && frame && frame->scriptProxy()->isEnabled())
        frame->scriptProxy()->finishedWithEvent(evt.get());

    return !evt->defaultPrevented(); // ### what if defaultPrevented was called before dispatchEvent?
}

void EventTarget::removeAllEventListeners(EventTargetNode* referenceNode)
{
    referenceNode->WebThreadGetCurrentDOMMouseMoveEventListeners()->remove(referenceNode);
    referenceNode->WebThreadGetCurrentDOMMouseWheelEventListeners()->remove(referenceNode);
    referenceNode->WebThreadGetCurrentDOMMouseClickEventListeners()->remove(referenceNode);
#if ENABLE(TOUCH_EVENTS)
    referenceNode->document()->removeTouchEventListener(referenceNode, true);
#endif
    delete referenceNode->m_regdListeners;
    referenceNode->m_regdListeners = 0;
}

void EventTarget::insertedIntoDocument(EventTargetNode* referenceNode)
{
    if (referenceNode && referenceNode->m_regdListeners && !referenceNode->m_regdListeners->isEmpty())
        referenceNode->document()->unregisterDisconnectedNodeWithEventListeners(referenceNode);
}

void EventTarget::removedFromDocument(EventTargetNode* referenceNode)
{
    if (referenceNode && referenceNode->m_regdListeners && !referenceNode->m_regdListeners->isEmpty())
        referenceNode->document()->registerDisconnectedNodeWithEventListeners(referenceNode);
}

void EventTarget::handleLocalEvents(EventTargetNode* referenceNode, Event* evt, bool useCapture)
{
    ASSERT(referenceNode);
    if (!referenceNode->m_regdListeners || referenceNode->m_regdListeners->isEmpty())
        return;

    RegisteredEventListenerList listenersCopy = *referenceNode->m_regdListeners;
    RegisteredEventListenerList::Iterator end = listenersCopy.end();

    for (RegisteredEventListenerList::Iterator it = listenersCopy.begin(); it != end; ++it) {
        if ((*it)->eventType() == evt->type() && (*it)->useCapture() == useCapture && !(*it)->removed())
            (*it)->listener()->handleEvent(evt, false);
    }
}

EventTarget* EventTarget::eventTargetRespectingSVGTargetRules(EventTargetNode*& referenceNode)
{
    // TODO: SVG will add logic here soon.
    return referenceNode;
}

#ifndef NDEBUG
void forbidEventDispatch()
{
    ++gEventDispatchForbidden;
}

void allowEventDispatch()
{
    if (gEventDispatchForbidden > 0)
        --gEventDispatchForbidden;
}

bool eventDispatchForbidden()
{
    return gEventDispatchForbidden > 0;
}
#endif // NDEBUG

} // end namespace