diff options
Diffstat (limited to 'Source/WebCore/dom/EventTarget.cpp')
-rw-r--r-- | Source/WebCore/dom/EventTarget.cpp | 357 |
1 files changed, 165 insertions, 192 deletions
diff --git a/Source/WebCore/dom/EventTarget.cpp b/Source/WebCore/dom/EventTarget.cpp index d1a2e0cec..f18722904 100644 --- a/Source/WebCore/dom/EventTarget.cpp +++ b/Source/WebCore/dom/EventTarget.cpp @@ -2,7 +2,7 @@ * 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) 2004-2017 Apple Inc. All rights reserved. * Copyright (C) 2006 Alexey Proskuryakov (ap@webkit.org) * (C) 2007, 2008 Nikolas Zimmermann <zimmermann@kde.org> * @@ -15,10 +15,10 @@ * 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 + * THIS SOFTWARE IS PROVIDED BY APPLE 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 + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE 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 @@ -32,12 +32,19 @@ #include "config.h" #include "EventTarget.h" -#include "EventException.h" +#include "DOMWrapperWorld.h" +#include "EventNames.h" +#include "ExceptionCode.h" #include "InspectorInstrumentation.h" +#include "JSEventListener.h" +#include "NoEventDispatchAssertion.h" #include "ScriptController.h" +#include "WebKitAnimationEvent.h" #include "WebKitTransitionEvent.h" #include <wtf/MainThread.h> +#include <wtf/NeverDestroyed.h> #include <wtf/Ref.h> +#include <wtf/SetForScope.h> #include <wtf/StdLibExtras.h> #include <wtf/Vector.h> @@ -45,26 +52,14 @@ using namespace WTF; namespace WebCore { -EventTargetData::EventTargetData() -{ -} - -EventTargetData::~EventTargetData() -{ -} - -EventTarget::~EventTarget() -{ -} - Node* EventTarget::toNode() { - return 0; + return nullptr; } DOMWindow* EventTarget::toDOMWindow() { - return 0; + return nullptr; } bool EventTarget::isMessagePort() const @@ -72,79 +67,87 @@ bool EventTarget::isMessagePort() const return false; } -bool EventTarget::addEventListener(const AtomicString& eventType, PassRefPtr<EventListener> listener, bool useCapture) +bool EventTarget::addEventListener(const AtomicString& eventType, Ref<EventListener>&& listener, const AddEventListenerOptions& options) { - return ensureEventTargetData().eventListenerMap.add(eventType, listener, useCapture); + return ensureEventTargetData().eventListenerMap.add(eventType, WTFMove(listener), { options.capture, options.passive, options.once }); } -bool EventTarget::removeEventListener(const AtomicString& eventType, EventListener* listener, bool useCapture) +void EventTarget::addEventListenerForBindings(const AtomicString& eventType, RefPtr<EventListener>&& listener, AddEventListenerOptionsOrBoolean&& variant) { - EventTargetData* d = eventTargetData(); - if (!d) - return false; + if (!listener) + return; - size_t indexOfRemovedListener; + auto visitor = WTF::makeVisitor([&](const AddEventListenerOptions& options) { + addEventListener(eventType, listener.releaseNonNull(), options); + }, [&](bool capture) { + addEventListener(eventType, listener.releaseNonNull(), capture); + }); - if (!d->eventListenerMap.remove(eventType, listener, useCapture, indexOfRemovedListener)) - return false; + WTF::visit(visitor, variant); +} - // Notify firing events planning to invoke the listener at 'index' that - // they have one less listener to invoke. - if (!d->firingEventIterators) - return true; - for (size_t i = 0; i < d->firingEventIterators->size(); ++i) { - FiringEventIterator& firingIterator = d->firingEventIterators->at(i); - if (eventType != firingIterator.eventType) - continue; +void EventTarget::removeEventListenerForBindings(const AtomicString& eventType, RefPtr<EventListener>&& listener, ListenerOptionsOrBoolean&& variant) +{ + if (!listener) + return; - if (indexOfRemovedListener >= firingIterator.size) - continue; + auto visitor = WTF::makeVisitor([&](const ListenerOptions& options) { + removeEventListener(eventType, *listener, options); + }, [&](bool capture) { + removeEventListener(eventType, *listener, capture); + }); - --firingIterator.size; - if (indexOfRemovedListener <= firingIterator.iterator) - --firingIterator.iterator; - } + WTF::visit(visitor, variant); +} - return true; +bool EventTarget::removeEventListener(const AtomicString& eventType, EventListener& listener, const ListenerOptions& options) +{ + auto* data = eventTargetData(); + return data && data->eventListenerMap.remove(eventType, listener, options.capture); } -bool EventTarget::setAttributeEventListener(const AtomicString& eventType, PassRefPtr<EventListener> listener) +bool EventTarget::setAttributeEventListener(const AtomicString& eventType, RefPtr<EventListener>&& listener, DOMWrapperWorld& isolatedWorld) { - clearAttributeEventListener(eventType); - if (!listener) + auto* existingListener = attributeEventListener(eventType, isolatedWorld); + if (!listener) { + if (existingListener) + removeEventListener(eventType, *existingListener, false); return false; - return addEventListener(eventType, listener, false); + } + if (existingListener) { + eventTargetData()->eventListenerMap.replace(eventType, *existingListener, listener.releaseNonNull(), { }); + return true; + } + return addEventListener(eventType, listener.releaseNonNull()); } -EventListener* EventTarget::getAttributeEventListener(const AtomicString& eventType) +EventListener* EventTarget::attributeEventListener(const AtomicString& eventType, DOMWrapperWorld& isolatedWorld) { - const EventListenerVector& entry = getEventListeners(eventType); - for (size_t i = 0; i < entry.size(); ++i) { - if (entry[i].listener->isAttribute()) - return entry[i].listener.get(); + for (auto& eventListener : eventListeners(eventType)) { + auto& listener = eventListener->callback(); + if (!listener.isAttribute()) + continue; + + auto& listenerWorld = downcast<JSEventListener>(listener).isolatedWorld(); + if (&listenerWorld == &isolatedWorld) + return &listener; } - return 0; + + return nullptr; } -bool EventTarget::clearAttributeEventListener(const AtomicString& eventType) +bool EventTarget::hasActiveEventListeners(const AtomicString& eventType) const { - EventListener* listener = getAttributeEventListener(eventType); - if (!listener) - return false; - return removeEventListener(eventType, listener, false); + auto* data = eventTargetData(); + return data && data->eventListenerMap.containsActive(eventType); } -bool EventTarget::dispatchEvent(PassRefPtr<Event> event, ExceptionCode& ec) +ExceptionOr<bool> EventTarget::dispatchEventForBindings(Event& event) { - if (!event || event->type().isEmpty()) { - ec = EventException::UNSPECIFIED_EVENT_TYPE_ERR; - return false; - } + event.setUntrusted(); - if (event->isBeingDispatched()) { - ec = EventException::DISPATCH_REQUEST_ERR; - return false; - } + if (!event.isInitialized() || event.isBeingDispatched()) + return Exception { INVALID_STATE_ERR }; if (!scriptExecutionContext()) return false; @@ -152,13 +155,17 @@ bool EventTarget::dispatchEvent(PassRefPtr<Event> event, ExceptionCode& ec) return dispatchEvent(event); } -bool EventTarget::dispatchEvent(PassRefPtr<Event> event) +bool EventTarget::dispatchEvent(Event& event) { - event->setTarget(this); - event->setCurrentTarget(this); - event->setEventPhase(Event::AT_TARGET); - bool defaultPrevented = fireEventListeners(event.get()); - event->setEventPhase(0); + ASSERT(event.isInitialized()); + ASSERT(!event.isBeingDispatched()); + + event.setTarget(this); + event.setCurrentTarget(this); + event.setEventPhase(Event::AT_TARGET); + bool defaultPrevented = fireEventListeners(event); + event.resetPropagationFlags(); + event.setEventPhase(Event::NONE); return defaultPrevented; } @@ -166,164 +173,130 @@ void EventTarget::uncaughtExceptionInEventHandler() { } -static const AtomicString& legacyType(const Event* event) +static const AtomicString& legacyType(const Event& event) { - if (event->type() == eventNames().transitionendEvent) - return eventNames().webkitTransitionEndEvent; + if (event.type() == eventNames().animationendEvent) + return eventNames().webkitAnimationEndEvent; - if (event->type() == eventNames().wheelEvent) - return eventNames().mousewheelEvent; + if (event.type() == eventNames().animationstartEvent) + return eventNames().webkitAnimationStartEvent; - return emptyAtom; -} - -static inline bool shouldObserveLegacyType(const AtomicString& legacyTypeName, bool hasLegacyTypeListeners, bool hasNewTypeListeners, FeatureObserver::Feature& feature) -{ - if (legacyTypeName == eventNames().webkitTransitionEndEvent) { - if (hasLegacyTypeListeners) { - if (hasNewTypeListeners) - feature = FeatureObserver::PrefixedAndUnprefixedTransitionEndEvent; - else - feature = FeatureObserver::PrefixedTransitionEndEvent; - } else { - ASSERT(hasNewTypeListeners); - feature = FeatureObserver::UnprefixedTransitionEndEvent; - } - return true; - } - return false; -} - -void EventTarget::setupLegacyTypeObserverIfNeeded(const AtomicString& legacyTypeName, bool hasLegacyTypeListeners, bool hasNewTypeListeners) -{ - ASSERT(!legacyTypeName.isEmpty()); - ASSERT(hasLegacyTypeListeners || hasNewTypeListeners); + if (event.type() == eventNames().animationiterationEvent) + return eventNames().webkitAnimationIterationEvent; - ScriptExecutionContext* context = scriptExecutionContext(); - if (!context || !context->isDocument()) - return; + if (event.type() == eventNames().transitionendEvent) + return eventNames().webkitTransitionEndEvent; - Document* document = toDocument(context); - if (!document->domWindow()) - return; + // FIXME: This legacy name is not part of the specification (https://dom.spec.whatwg.org/#dispatching-events). + if (event.type() == eventNames().wheelEvent) + return eventNames().mousewheelEvent; - FeatureObserver::Feature feature; - if (shouldObserveLegacyType(legacyTypeName, hasLegacyTypeListeners, hasNewTypeListeners, feature)) - FeatureObserver::observe(document->domWindow(), feature); + return nullAtom; } -bool EventTarget::fireEventListeners(Event* event) +bool EventTarget::fireEventListeners(Event& event) { - ASSERT(!NoEventDispatchAssertion::isEventDispatchForbidden()); - ASSERT(event && !event->type().isEmpty()); + ASSERT_WITH_SECURITY_IMPLICATION(NoEventDispatchAssertion::isEventAllowedInMainThread()); + ASSERT(event.isInitialized()); - EventTargetData* d = eventTargetData(); - if (!d) + auto* data = eventTargetData(); + if (!data) return true; - EventListenerVector* legacyListenersVector = 0; - const AtomicString& legacyTypeName = legacyType(event); - if (!legacyTypeName.isEmpty()) - legacyListenersVector = d->eventListenerMap.find(legacyTypeName); - - EventListenerVector* listenersVector = d->eventListenerMap.find(event->type()); - - if (listenersVector) - fireEventListeners(event, d, *listenersVector); - else if (legacyListenersVector) { - AtomicString typeName = event->type(); - event->setType(legacyTypeName); - fireEventListeners(event, d, *legacyListenersVector); - event->setType(typeName); + SetForScope<bool> firingEventListenersScope(data->isFiringEventListeners, true); + + if (auto* listenersVector = data->eventListenerMap.find(event.type())) { + fireEventListeners(event, *listenersVector); + return !event.defaultPrevented(); } - if (!legacyTypeName.isEmpty() && (legacyListenersVector || listenersVector)) - setupLegacyTypeObserverIfNeeded(legacyTypeName, !!legacyListenersVector, !!listenersVector); + // Only fall back to legacy types for trusted events. + if (!event.isTrusted()) + return !event.defaultPrevented(); - return !event->defaultPrevented(); + const AtomicString& legacyTypeName = legacyType(event); + if (!legacyTypeName.isNull()) { + if (auto* legacyListenersVector = data->eventListenerMap.find(legacyTypeName)) { + AtomicString typeName = event.type(); + event.setType(legacyTypeName); + fireEventListeners(event, *legacyListenersVector); + event.setType(typeName); + } + } + return !event.defaultPrevented(); } - -void EventTarget::fireEventListeners(Event* event, EventTargetData* d, EventListenerVector& entry) -{ - Ref<EventTarget> protect(*this); - - // Fire all listeners registered for this event. Don't fire listeners removed during event dispatch. - // Also, don't fire event listeners added during event dispatch. Conveniently, all new event listeners will be added - // after or at index |size|, so iterating up to (but not including) |size| naturally excludes new event listeners. - bool userEventWasHandled = false; - size_t i = 0; - size_t size = entry.size(); - if (!d->firingEventIterators) - d->firingEventIterators = adoptPtr(new FiringEventIteratorVector); - d->firingEventIterators->append(FiringEventIterator(event->type(), i, size)); +// Intentionally creates a copy of the listeners vector to avoid event listeners added after this point from being run. +// Note that removal still has an effect due to the removed field in RegisteredEventListener. +void EventTarget::fireEventListeners(Event& event, EventListenerVector listeners) +{ + Ref<EventTarget> protectedThis(*this); + ASSERT(!listeners.isEmpty()); - ScriptExecutionContext* context = scriptExecutionContext(); - Document* document = nullptr; + auto* context = scriptExecutionContext(); + bool contextIsDocument = is<Document>(context); InspectorInstrumentationCookie willDispatchEventCookie; - if (context && context->isDocument()) { - document = toDocument(context); - willDispatchEventCookie = InspectorInstrumentation::willDispatchEvent(document, *event, size > 0); - } + if (contextIsDocument) + willDispatchEventCookie = InspectorInstrumentation::willDispatchEvent(downcast<Document>(*context), event, true); - for (; i < size; ++i) { - RegisteredEventListener& registeredListener = entry[i]; - if (event->eventPhase() == Event::CAPTURING_PHASE && !registeredListener.useCapture) + for (auto& registeredListener : listeners) { + if (UNLIKELY(registeredListener->wasRemoved())) continue; - if (event->eventPhase() == Event::BUBBLING_PHASE && registeredListener.useCapture) + + if (event.eventPhase() == Event::CAPTURING_PHASE && !registeredListener->useCapture()) + continue; + if (event.eventPhase() == Event::BUBBLING_PHASE && registeredListener->useCapture()) continue; // If stopImmediatePropagation has been called, we just break out immediately, without // handling any more events on this target. - if (event->immediatePropagationStopped()) + if (event.immediatePropagationStopped()) break; - InspectorInstrumentationCookie cookie = InspectorInstrumentation::willHandleEvent(context, event); - // To match Mozilla, the AT_TARGET phase fires both capturing and bubbling - // event listeners, even though that violates some versions of the DOM spec. - registeredListener.listener->handleEvent(context, event); - if (!userEventWasHandled && ScriptController::processingUserGesture()) - userEventWasHandled = true; - InspectorInstrumentation::didHandleEvent(cookie); + // Do this before invocation to avoid reentrancy issues. + if (registeredListener->isOnce()) + removeEventListener(event.type(), registeredListener->callback(), ListenerOptions(registeredListener->useCapture())); + + if (registeredListener->isPassive()) + event.setInPassiveListener(true); + + InspectorInstrumentation::willHandleEvent(context, event); + registeredListener->callback().handleEvent(context, &event); + + if (registeredListener->isPassive()) + event.setInPassiveListener(false); } - d->firingEventIterators->removeLast(); - if (userEventWasHandled && document) - document->resetLastHandledUserGestureTimestamp(); - if (document) + if (contextIsDocument) InspectorInstrumentation::didDispatchEvent(willDispatchEventCookie); } -const EventListenerVector& EventTarget::getEventListeners(const AtomicString& eventType) +const EventListenerVector& EventTarget::eventListeners(const AtomicString& eventType) { - DEFINE_STATIC_LOCAL(EventListenerVector, emptyVector, ()); - - EventTargetData* d = eventTargetData(); - if (!d) - return emptyVector; - - EventListenerVector* listenerVector = d->eventListenerMap.find(eventType); - if (!listenerVector) - return emptyVector; - - return *listenerVector; + auto* data = eventTargetData(); + auto* listenerVector = data ? data->eventListenerMap.find(eventType) : nullptr; + static NeverDestroyed<EventListenerVector> emptyVector; + return listenerVector ? *listenerVector : emptyVector.get(); } void EventTarget::removeAllEventListeners() { - EventTargetData* d = eventTargetData(); - if (!d) + auto* data = eventTargetData(); + if (!data) return; - d->eventListenerMap.clear(); - - // Notify firing events planning to invoke the listener at 'index' that - // they have one less listener to invoke. - if (d->firingEventIterators) { - for (size_t i = 0; i < d->firingEventIterators->size(); ++i) { - d->firingEventIterators->at(i).iterator = 0; - d->firingEventIterators->at(i).size = 0; - } - } + data->eventListenerMap.clear(); +} + +void EventTarget::visitJSEventListeners(JSC::SlotVisitor& visitor) +{ + EventTargetData* data = eventTargetDataConcurrently(); + if (!data) + return; + + auto locker = holdLock(data->eventListenerMap.lock()); + EventListenerIterator iterator(&data->eventListenerMap); + while (auto* listener = iterator.nextListener()) + listener->visitJSFunction(visitor); } } // namespace WebCore |