| /* |
| * 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 Apple Computer, Inc. |
| * |
| * 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., 59 Temple Place - Suite 330, |
| * Boston, MA 02111-1307, USA. |
| * |
| */ |
| |
| #include "config.h" |
| #include "EventTargetNode.h" |
| |
| #include "Document.h" |
| #include "Element.h" |
| #include "Event.h" |
| #include "EventListener.h" |
| #include "EventNames.h" |
| #include "Frame.h" |
| #include "HTMLNames.h" |
| #include "TextStream.h" |
| #include "KeyboardEvent.h" |
| #include "MouseEvent.h" |
| #include "MutationEvent.h" |
| #include "PlatformMouseEvent.h" |
| #include "PlatformWheelEvent.h" |
| #include "RegisteredEventListener.h" |
| #include "UIEvent.h" |
| #include "WheelEvent.h" |
| #include "kjs_proxy.h" |
| |
| namespace WebCore { |
| |
| using namespace EventNames; |
| using namespace HTMLNames; |
| |
| #ifndef NDEBUG |
| static int gEventDispatchForbidden = 0; |
| #endif |
| |
| EventTargetNode::EventTargetNode(Document *doc) |
| : Node(doc) |
| , m_regdListeners(0) |
| { |
| } |
| |
| EventTargetNode::~EventTargetNode() |
| { |
| if (m_regdListeners && !m_regdListeners->isEmpty() && !inDocument()) |
| document()->unregisterDisconnectedNodeWithEventListeners(this); |
| delete m_regdListeners; |
| } |
| |
| void EventTargetNode::insertedIntoDocument() |
| { |
| if (m_regdListeners && !m_regdListeners->isEmpty()) |
| document()->unregisterDisconnectedNodeWithEventListeners(this); |
| |
| Node::insertedIntoDocument(); |
| } |
| |
| void EventTargetNode::removedFromDocument() |
| { |
| if (m_regdListeners && !m_regdListeners->isEmpty()) |
| document()->registerDisconnectedNodeWithEventListeners(this); |
| |
| Node::removedFromDocument(); |
| } |
| |
| void EventTargetNode::addEventListener(const AtomicString &eventType, PassRefPtr<EventListener> listener, const bool useCapture) |
| { |
| if (!document()->attached()) |
| return; |
| |
| 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; |
| |
| if (type) |
| document()->addListenerType(type); |
| |
| if (!m_regdListeners) |
| m_regdListeners = new RegisteredEventListenerList; |
| |
| // Remove existing identical listener set with identical arguments. |
| // The DOM2 spec says that "duplicate instances are discarded" in this case. |
| removeEventListener(eventType, listener.get(), useCapture); |
| |
| // adding the first one |
| if (m_regdListeners->isEmpty() && !inDocument()) |
| document()->registerDisconnectedNodeWithEventListeners(this); |
| |
| m_regdListeners->append(new RegisteredEventListener(eventType, listener.get(), useCapture)); |
| } |
| |
| void EventTargetNode::removeEventListener(const AtomicString &eventType, EventListener *listener, bool useCapture) |
| { |
| if (!m_regdListeners) // nothing to remove |
| return; |
| |
| RegisteredEventListener rl(eventType, listener, useCapture); |
| |
| RegisteredEventListenerList::Iterator end = m_regdListeners->end(); |
| for (RegisteredEventListenerList::Iterator it = m_regdListeners->begin(); it != end; ++it) |
| if (*(*it).get() == rl) { |
| (*it)->setRemoved(true); |
| |
| it = m_regdListeners->remove(it); |
| // removed last |
| if (m_regdListeners->isEmpty() && !inDocument()) |
| document()->unregisterDisconnectedNodeWithEventListeners(this); |
| return; |
| } |
| } |
| |
| void EventTargetNode::removeAllEventListeners() |
| { |
| delete m_regdListeners; |
| m_regdListeners = 0; |
| } |
| |
| void EventTargetNode::handleLocalEvents(Event *evt, bool useCapture) |
| { |
| if (!m_regdListeners) |
| return; |
| |
| if (disabled() && evt->isMouseEvent()) |
| return; |
| |
| RegisteredEventListenerList listenersCopy = *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); |
| } |
| |
| bool EventTargetNode::dispatchGenericEvent(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; |
| Node *n; |
| for (n = this; n; n = n->parentNode()) { |
| n->ref(); |
| nodeChain.prepend(n); |
| } |
| |
| 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); |
| |
| for (; it.current() && it.current() != this && !evt->propagationStopped(); ++it) { |
| evt->setCurrentTarget(it.current()); |
| EventTargetNodeCast(it.current())->handleLocalEvents(evt.get(), true); |
| } |
| |
| // dispatch to the actual target node |
| it.toLast(); |
| if (!evt->propagationStopped()) { |
| evt->setEventPhase(Event::AT_TARGET); |
| evt->setCurrentTarget(it.current()); |
| |
| // We do want capturing event listeners to be invoked here, even though |
| // that violates the specification since Mozilla does it. |
| EventTargetNodeCast(it.current())->handleLocalEvents(evt.get(), true); |
| |
| EventTargetNodeCast(it.current())->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->getCancelBubble(); --it) { |
| evt->setCurrentTarget(it.current()); |
| EventTargetNodeCast(it.current())->handleLocalEvents(evt.get(), false); |
| } |
| // Handle window events for bubbling phase, except load events, this quirk is needed |
| // because Mozilla used to never propagate load events at all |
| |
| it.toFirst(); |
| if (evt->type() != loadEvent && it.current()->isDocumentNode() && !evt->propagationStopped() && !evt->getCancelBubble()) { |
| evt->setCurrentTarget(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 khtml) |
| |
| 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 = document()->frame(); |
| if (tempEvent && frame && frame->jScript()) |
| frame->jScript()->finishedWithEvent(evt.get()); |
| |
| return !evt->defaultPrevented(); // ### what if defaultPrevented was called before dispatchEvent? |
| } |
| |
| bool EventTargetNode::dispatchEvent(PassRefPtr<Event> e, ExceptionCode& ec, bool tempEvent) |
| { |
| RefPtr<Event> evt(e); |
| assert(!eventDispatchForbidden()); |
| if (!evt || evt->type().isEmpty()) { |
| ec = UNSPECIFIED_EVENT_TYPE_ERR; |
| return false; |
| } |
| evt->setTarget(this); |
| |
| RefPtr<FrameView> view = document()->view(); |
| |
| return dispatchGenericEvent(evt.release(), ec, tempEvent); |
| } |
| |
| bool EventTargetNode::dispatchSubtreeModifiedEvent(bool sendChildrenChanged) |
| { |
| assert(!eventDispatchForbidden()); |
| |
| // FIXME: Pull this whole if clause out of this function. |
| if (sendChildrenChanged) { |
| notifyNodeListsChildrenChanged(); |
| childrenChanged(); |
| } else |
| notifyNodeListsAttributeChanged(); // FIXME: Can do better some day. Really only care about the name attribute changing. |
| |
| if (!document()->hasListenerType(Document::DOMSUBTREEMODIFIED_LISTENER)) |
| return false; |
| ExceptionCode ec = 0; |
| return dispatchEvent(new MutationEvent(DOMSubtreeModifiedEvent, |
| true,false,0,String(),String(),String(),0),ec,true); |
| } |
| |
| void EventTargetNode::dispatchWindowEvent(const AtomicString &eventType, bool canBubbleArg, bool cancelableArg) |
| { |
| assert(!eventDispatchForbidden()); |
| ExceptionCode ec = 0; |
| RefPtr<Event> evt = new Event(eventType, canBubbleArg, cancelableArg); |
| RefPtr<Document> doc = document(); |
| evt->setTarget(doc.get()); |
| doc->handleWindowEvent(evt.get(), true); |
| doc->handleWindowEvent(evt.get(), false); |
| |
| if (eventType == loadEvent) { |
| // For onload events, send a separate load event to the enclosing frame only. |
| // This is a DOM extension and is independent of bubbling/capturing rules of |
| // the DOM. |
| Element* ownerElement = doc->ownerElement(); |
| if (ownerElement) { |
| RefPtr<Event> ownerEvent = new Event(eventType, false, cancelableArg); |
| ownerEvent->setTarget(ownerElement); |
| ownerElement->dispatchGenericEvent(ownerEvent.release(), ec, true); |
| } |
| } |
| } |
| |
| bool EventTargetNode::dispatchUIEvent(const AtomicString &eventType, int detail) |
| { |
| assert(!eventDispatchForbidden()); |
| assert(eventType == DOMFocusInEvent || eventType == DOMFocusOutEvent || eventType == DOMActivateEvent); |
| |
| bool cancelable = eventType == DOMActivateEvent; |
| |
| ExceptionCode ec = 0; |
| UIEvent* evt = new UIEvent(eventType, true, cancelable, document()->defaultView(), detail); |
| return dispatchEvent(evt, ec, true); |
| } |
| |
| bool EventTargetNode::dispatchKeyEvent(const PlatformKeyboardEvent& key) |
| { |
| assert(!eventDispatchForbidden()); |
| ExceptionCode ec = 0; |
| RefPtr<KeyboardEvent> keyboardEvent = new KeyboardEvent(key, document()->defaultView()); |
| bool r = dispatchEvent(keyboardEvent,ec,true); |
| |
| // we want to return false if default is prevented (already taken care of) |
| // or if the element is default-handled by the DOM. Otherwise we let it just |
| // let it get handled by AppKit |
| if (keyboardEvent->defaultHandled()) |
| r = false; |
| |
| return r; |
| } |
| |
| bool EventTargetNode::dispatchMouseEvent(const PlatformMouseEvent& _mouse, const AtomicString& eventType, |
| int detail, Node* relatedTarget) |
| { |
| assert(!eventDispatchForbidden()); |
| |
| IntPoint contentsPos; |
| if (FrameView* view = document()->view()) |
| contentsPos = view->windowToContents(_mouse.pos()); |
| |
| return dispatchMouseEvent(eventType, _mouse.button(), detail, |
| contentsPos.x(), contentsPos.y(), _mouse.globalX(), _mouse.globalY(), |
| _mouse.ctrlKey(), _mouse.altKey(), _mouse.shiftKey(), _mouse.metaKey(), |
| false, relatedTarget); |
| } |
| |
| bool EventTargetNode::dispatchSimulatedMouseEvent(const AtomicString &eventType) |
| { |
| assert(!eventDispatchForbidden()); |
| // Like Gecko, we just pass 0 for everything when we make a fake mouse event. |
| // Internet Explorer instead gives the current mouse position and state. |
| return dispatchMouseEvent(eventType, 0, 0, 0, 0, 0, 0, false, false, false, false, true); |
| } |
| |
| bool EventTargetNode::dispatchMouseEvent(const AtomicString& eventType, int button, int detail, |
| int pageX, int pageY, int screenX, int screenY, |
| bool ctrlKey, bool altKey, bool shiftKey, bool metaKey, |
| bool isSimulated, Node* relatedTargetArg) |
| { |
| assert(!eventDispatchForbidden()); |
| if (disabled()) // Don't even send DOM events for disabled controls.. |
| return true; |
| |
| if (eventType.isEmpty()) |
| return false; // Shouldn't happen. |
| |
| // Dispatching the first event can easily result in this node being destroyed. |
| // Since we dispatch up to three events here, we need to make sure we're referenced |
| // so the pointer will be good for the two subsequent ones. |
| RefPtr<Node> protect(this); |
| |
| bool cancelable = eventType != mousemoveEvent; |
| |
| ExceptionCode ec = 0; |
| |
| bool swallowEvent = false; |
| |
| // Attempting to dispatch with a non-EventTarget relatedTarget causes the relatedTarget to be silently ignored. |
| EventTargetNode *relatedTarget = (relatedTargetArg && relatedTargetArg->isEventTargetNode()) ? static_cast<EventTargetNode*>(relatedTargetArg) : 0; |
| |
| RefPtr<Event> me = new MouseEvent(eventType, true, cancelable, document()->defaultView(), |
| detail, screenX, screenY, pageX, pageY, |
| ctrlKey, altKey, shiftKey, metaKey, button, |
| relatedTarget, 0, isSimulated); |
| |
| dispatchEvent(me, ec, true); |
| bool defaultHandled = me->defaultHandled(); |
| bool defaultPrevented = me->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 == clickEvent && detail == 2) { |
| me = new MouseEvent(dblclickEvent, true, cancelable, document()->defaultView(), |
| detail, screenX, screenY, pageX, pageY, |
| ctrlKey, altKey, shiftKey, metaKey, button, |
| relatedTarget, 0, isSimulated); |
| if (defaultHandled) |
| me->setDefaultHandled(); |
| dispatchEvent(me, ec, true); |
| if (me->defaultHandled() || me->defaultPrevented()) |
| swallowEvent = true; |
| } |
| |
| // Also send a DOMActivate event, which causes things like form submissions to occur. |
| if (eventType == clickEvent && !defaultPrevented) |
| dispatchUIEvent(DOMActivateEvent, detail); |
| |
| return swallowEvent; |
| } |
| |
| void EventTargetNode::dispatchWheelEvent(PlatformWheelEvent& e) |
| { |
| assert(!eventDispatchForbidden()); |
| if (e.deltaX() == 0 && e.deltaY() == 0) |
| return; |
| |
| FrameView* view = document()->view(); |
| if (!view) |
| return; |
| |
| IntPoint pos = view->windowToContents(e.pos()); |
| |
| RefPtr<WheelEvent> we = new WheelEvent(e.deltaX(), e.deltaY(), |
| document()->defaultView(), e.globalX(), e.globalY(), pos.x(), pos.y(), |
| e.ctrlKey(), e.altKey(), e.shiftKey(), e.metaKey()); |
| ExceptionCode ec = 0; |
| if (!dispatchEvent(we, ec, true)) |
| e.accept(); |
| } |
| |
| |
| void EventTargetNode::dispatchFocusEvent() |
| { |
| dispatchHTMLEvent(focusEvent, false, false); |
| } |
| |
| void EventTargetNode::dispatchBlurEvent() |
| { |
| dispatchHTMLEvent(blurEvent, false, false); |
| } |
| |
| bool EventTargetNode::dispatchHTMLEvent(const AtomicString &eventType, bool canBubbleArg, bool cancelableArg) |
| { |
| assert(!eventDispatchForbidden()); |
| ExceptionCode ec = 0; |
| return dispatchEvent(new Event(eventType, canBubbleArg, cancelableArg), ec, true); |
| } |
| |
| void EventTargetNode::removeHTMLEventListener(const AtomicString &eventType) |
| { |
| if (!m_regdListeners) // nothing to remove |
| return; |
| |
| RegisteredEventListenerList::Iterator end = m_regdListeners->end(); |
| for (RegisteredEventListenerList::Iterator it = m_regdListeners->begin(); it != end; ++it) |
| if ((*it)->eventType() == eventType && (*it)->listener()->isHTMLEventListener()) { |
| it = m_regdListeners->remove(it); |
| // removed last |
| if (m_regdListeners->isEmpty() && !inDocument()) |
| document()->unregisterDisconnectedNodeWithEventListeners(this); |
| return; |
| } |
| } |
| |
| void EventTargetNode::setHTMLEventListener(const AtomicString &eventType, PassRefPtr<EventListener> listener) |
| { |
| // In case we are the only one holding a reference to it, we don't want removeHTMLEventListener to destroy it. |
| removeHTMLEventListener(eventType); |
| if (listener) |
| addEventListener(eventType, listener.get(), false); |
| } |
| |
| EventListener *EventTargetNode::getHTMLEventListener(const AtomicString &eventType) |
| { |
| if (!m_regdListeners) |
| return 0; |
| |
| RegisteredEventListenerList::Iterator end = m_regdListeners->end(); |
| for (RegisteredEventListenerList::Iterator it = m_regdListeners->begin(); it != end; ++it) |
| if ((*it)->eventType() == eventType && (*it)->listener()->isHTMLEventListener()) |
| return (*it)->listener(); |
| return 0; |
| } |
| |
| bool EventTargetNode::disabled() const |
| { |
| return false; |
| } |
| |
| void EventTargetNode::defaultEventHandler(Event* event) |
| { |
| if (event->type() == keypressEvent && event->isKeyboardEvent()) { |
| KeyboardEvent* keyEvent = static_cast<KeyboardEvent*>(event); |
| if (keyEvent->keyIdentifier() == "U+000009") { |
| Frame* frame = document()->frame(); |
| if (frame && frame->view() && frame->view()->advanceFocus(!keyEvent->shiftKey())) |
| event->setDefaultHandled(); |
| } |
| } |
| } |
| |
| #ifndef NDEBUG |
| |
| void EventTargetNode::dump(TextStream* stream, DeprecatedString ind) const |
| { |
| if (m_regdListeners) |
| *stream << " #regdListeners=" << m_regdListeners->count(); // ### more detail |
| |
| Node::dump(stream,ind); |
| } |
| |
| void forbidEventDispatch() |
| { |
| ++gEventDispatchForbidden; |
| } |
| |
| void allowEventDispatch() |
| { |
| if (gEventDispatchForbidden > 0) |
| --gEventDispatchForbidden; |
| } |
| |
| bool eventDispatchForbidden() |
| { |
| return gEventDispatchForbidden > 0; |
| } |
| |
| #endif |
| |
| } // namespace WebCore |