blob: 13ac29078e62e44bc9bf61f346224702bd5cf205 [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-2021 Apple Inc. All rights reserved.
* Copyright (C) 2006 Alexey Proskuryakov (ap@webkit.org)
* (C) 2007, 2008 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 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 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 "AddEventListenerOptions.h"
#include "DOMWrapperWorld.h"
#include "EventNames.h"
#include "EventTargetConcrete.h"
#include "HTMLBodyElement.h"
#include "HTMLHtmlElement.h"
#include "InspectorInstrumentation.h"
#include "JSErrorHandler.h"
#include "JSEventListener.h"
#include "Logging.h"
#include "Quirks.h"
#include "ScriptController.h"
#include "ScriptDisallowedScope.h"
#include "Settings.h"
#include <wtf/IsoMallocInlines.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>
namespace WebCore {
WTF_MAKE_ISO_ALLOCATED_IMPL(EventTarget);
WTF_MAKE_ISO_ALLOCATED_IMPL(EventTargetWithInlineData);
Ref<EventTarget> EventTarget::create(ScriptExecutionContext& context)
{
return EventTargetConcrete::create(context);
}
EventTarget::~EventTarget() = default;
bool EventTarget::isNode() const
{
return false;
}
bool EventTarget::isPaymentRequest() const
{
return false;
}
bool EventTarget::addEventListener(const AtomString& eventType, Ref<EventListener>&& listener, const AddEventListenerOptions& options)
{
#if ASSERT_ENABLED
listener->checkValidityForEventTarget(*this);
#endif
if (options.signal && options.signal->aborted())
return false;
auto passive = options.passive;
if (!passive.has_value() && Quirks::shouldMakeEventListenerPassive(*this, eventType, listener.get()))
passive = true;
bool listenerCreatedFromScript = listener->type() == EventListener::JSEventListenerType && !listener->wasCreatedFromMarkup();
if (!ensureEventTargetData().eventListenerMap.add(eventType, listener.copyRef(), { options.capture, passive.value_or(false), options.once }))
return false;
if (options.signal) {
options.signal->addAlgorithm([weakThis = WeakPtr { *this }, eventType, listener = WeakPtr { listener }, capture = options.capture] {
if (weakThis && listener)
weakThis->removeEventListener(eventType, *listener, capture);
});
}
if (listenerCreatedFromScript)
InspectorInstrumentation::didAddEventListener(*this, eventType, listener.get(), options.capture);
if (eventNames().isWheelEventType(eventType))
invalidateEventListenerRegions();
eventListenersDidChange();
return true;
}
void EventTarget::addEventListenerForBindings(const AtomString& eventType, RefPtr<EventListener>&& listener, AddEventListenerOptionsOrBoolean&& variant)
{
if (!listener)
return;
auto visitor = WTF::makeVisitor([&](const AddEventListenerOptions& options) {
addEventListener(eventType, listener.releaseNonNull(), options);
}, [&](bool capture) {
addEventListener(eventType, listener.releaseNonNull(), capture);
});
std::visit(visitor, variant);
}
void EventTarget::removeEventListenerForBindings(const AtomString& eventType, RefPtr<EventListener>&& listener, EventListenerOptionsOrBoolean&& variant)
{
if (!listener)
return;
auto visitor = WTF::makeVisitor([&](const EventListenerOptions& options) {
removeEventListener(eventType, *listener, options);
}, [&](bool capture) {
removeEventListener(eventType, *listener, capture);
});
std::visit(visitor, variant);
}
bool EventTarget::removeEventListener(const AtomString& eventType, EventListener& listener, const EventListenerOptions& options)
{
auto* data = eventTargetData();
if (!data)
return false;
InspectorInstrumentation::willRemoveEventListener(*this, eventType, listener, options.capture);
if (data->eventListenerMap.remove(eventType, listener, options.capture)) {
if (eventNames().isWheelEventType(eventType))
invalidateEventListenerRegions();
eventListenersDidChange();
return true;
}
return false;
}
template<typename JSMaybeErrorEventListener>
void EventTarget::setAttributeEventListener(const AtomString& eventType, JSC::JSValue listener, JSC::JSObject& jsEventTarget)
{
auto& isolatedWorld = worldForDOMObject(jsEventTarget);
auto* existingListener = attributeEventListener(eventType, isolatedWorld);
if (!listener.isObject()) {
if (existingListener)
removeEventListener(eventType, *existingListener, false);
} else if (existingListener) {
bool capture = false;
InspectorInstrumentation::willRemoveEventListener(*this, eventType, *existingListener, capture);
existingListener->replaceJSFunctionForAttributeListener(asObject(listener), &jsEventTarget);
InspectorInstrumentation::didAddEventListener(*this, eventType, *existingListener, capture);
} else
addEventListener(eventType, JSMaybeErrorEventListener::create(*asObject(listener), jsEventTarget, true, isolatedWorld), { });
}
template void EventTarget::setAttributeEventListener<JSErrorHandler>(const AtomString& eventType, JSC::JSValue listener, JSC::JSObject& jsEventTarget);
template void EventTarget::setAttributeEventListener<JSEventListener>(const AtomString& eventType, JSC::JSValue listener, JSC::JSObject& jsEventTarget);
bool EventTarget::setAttributeEventListener(const AtomString& eventType, RefPtr<EventListener>&& listener, DOMWrapperWorld& isolatedWorld)
{
auto* existingListener = attributeEventListener(eventType, isolatedWorld);
if (!listener) {
if (existingListener)
removeEventListener(eventType, *existingListener, false);
return false;
}
if (existingListener) {
InspectorInstrumentation::willRemoveEventListener(*this, eventType, *existingListener, false);
#if ASSERT_ENABLED
listener->checkValidityForEventTarget(*this);
#endif
auto listenerPointer = listener.copyRef();
eventTargetData()->eventListenerMap.replace(eventType, *existingListener, listener.releaseNonNull(), { });
InspectorInstrumentation::didAddEventListener(*this, eventType, *listenerPointer, false);
return true;
}
return addEventListener(eventType, listener.releaseNonNull(), { });
}
JSEventListener* EventTarget::attributeEventListener(const AtomString& eventType, DOMWrapperWorld& isolatedWorld)
{
for (auto& eventListener : eventListeners(eventType)) {
auto& listener = eventListener->callback();
if (listener.type() != EventListener::JSEventListenerType)
continue;
auto& jsListener = downcast<JSEventListener>(listener);
if (jsListener.isAttribute() && &jsListener.isolatedWorld() == &isolatedWorld)
return &jsListener;
}
return nullptr;
}
bool EventTarget::hasActiveEventListeners(const AtomString& eventType) const
{
auto* data = eventTargetData();
return data && data->eventListenerMap.containsActive(eventType);
}
ExceptionOr<bool> EventTarget::dispatchEventForBindings(Event& event)
{
if (!event.isInitialized() || event.isBeingDispatched())
return Exception { InvalidStateError };
if (!scriptExecutionContext())
return false;
event.setUntrusted();
dispatchEvent(event);
return event.legacyReturnValue();
}
void EventTarget::dispatchEvent(Event& event)
{
// FIXME: We should always use EventDispatcher.
ASSERT(event.isInitialized());
ASSERT(!event.isBeingDispatched());
event.setTarget(this);
event.setCurrentTarget(this);
event.setEventPhase(Event::AT_TARGET);
event.resetBeforeDispatch();
fireEventListeners(event, EventInvokePhase::Capturing);
fireEventListeners(event, EventInvokePhase::Bubbling);
event.resetAfterDispatch();
}
void EventTarget::uncaughtExceptionInEventHandler()
{
}
static const AtomString& legacyType(const Event& event)
{
if (event.type() == eventNames().animationendEvent)
return eventNames().webkitAnimationEndEvent;
if (event.type() == eventNames().animationstartEvent)
return eventNames().webkitAnimationStartEvent;
if (event.type() == eventNames().animationiterationEvent)
return eventNames().webkitAnimationIterationEvent;
if (event.type() == eventNames().transitionendEvent)
return eventNames().webkitTransitionEndEvent;
// 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;
return nullAtom();
}
// https://dom.spec.whatwg.org/#concept-event-listener-invoke
void EventTarget::fireEventListeners(Event& event, EventInvokePhase phase)
{
ASSERT_WITH_SECURITY_IMPLICATION(ScriptDisallowedScope::isEventAllowedInMainThread());
ASSERT(event.isInitialized());
auto* data = eventTargetData();
if (!data)
return;
SetForScope<bool> firingEventListenersScope(data->isFiringEventListeners, true);
if (auto* listenersVector = data->eventListenerMap.find(event.type())) {
innerInvokeEventListeners(event, *listenersVector, phase);
return;
}
// Only fall back to legacy types for trusted events.
if (!event.isTrusted())
return;
const AtomString& legacyTypeName = legacyType(event);
if (!legacyTypeName.isNull()) {
if (auto* legacyListenersVector = data->eventListenerMap.find(legacyTypeName)) {
AtomString typeName = event.type();
event.setType(legacyTypeName);
innerInvokeEventListeners(event, *legacyListenersVector, phase);
event.setType(typeName);
}
}
}
// 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.
// https://dom.spec.whatwg.org/#concept-event-listener-inner-invoke
void EventTarget::innerInvokeEventListeners(Event& event, EventListenerVector listeners, EventInvokePhase phase)
{
Ref<EventTarget> protectedThis(*this);
ASSERT(!listeners.isEmpty());
ASSERT(scriptExecutionContext());
auto& context = *scriptExecutionContext();
bool contextIsDocument = is<Document>(context);
if (contextIsDocument)
InspectorInstrumentation::willDispatchEvent(downcast<Document>(context), event);
for (auto& registeredListener : listeners) {
if (UNLIKELY(registeredListener->wasRemoved()))
continue;
if (phase == EventInvokePhase::Capturing && !registeredListener->useCapture())
continue;
if (phase == EventInvokePhase::Bubbling && registeredListener->useCapture())
continue;
if (InspectorInstrumentation::isEventListenerDisabled(*this, event.type(), registeredListener->callback(), registeredListener->useCapture()))
continue;
// If stopImmediatePropagation has been called, we just break out immediately, without
// handling any more events on this target.
if (event.immediatePropagationStopped())
break;
// Make sure the JS wrapper and function stay alive until the end of this scope. Otherwise,
// event listeners with 'once' flag may get collected as soon as they get unregistered below,
// before we call the js function.
JSC::EnsureStillAliveScope wrapperProtector(registeredListener->callback().wrapper());
JSC::EnsureStillAliveScope jsFunctionProtector(registeredListener->callback().jsFunction());
// Do this before invocation to avoid reentrancy issues.
if (registeredListener->isOnce())
removeEventListener(event.type(), registeredListener->callback(), registeredListener->useCapture());
if (registeredListener->isPassive())
event.setInPassiveListener(true);
#if ASSERT_ENABLED
registeredListener->callback().checkValidityForEventTarget(*this);
#endif
InspectorInstrumentation::willHandleEvent(context, event, *registeredListener);
registeredListener->callback().handleEvent(context, event);
InspectorInstrumentation::didHandleEvent(context, event, *registeredListener);
if (registeredListener->isPassive())
event.setInPassiveListener(false);
}
if (contextIsDocument)
InspectorInstrumentation::didDispatchEvent(downcast<Document>(context), event);
}
Vector<AtomString> EventTarget::eventTypes()
{
if (auto* data = eventTargetData())
return data->eventListenerMap.eventTypes();
return { };
}
const EventListenerVector& EventTarget::eventListeners(const AtomString& eventType)
{
auto* data = eventTargetData();
auto* listenerVector = data ? data->eventListenerMap.find(eventType) : nullptr;
static NeverDestroyed<EventListenerVector> emptyVector;
return listenerVector ? *listenerVector : emptyVector.get();
}
void EventTarget::removeAllEventListeners()
{
auto& threadData = threadGlobalData();
RELEASE_ASSERT(!threadData.isInRemoveAllEventListeners());
threadData.setIsInRemoveAllEventListeners(true);
auto* data = eventTargetData();
if (data && !data->eventListenerMap.isEmpty()) {
if (data->eventListenerMap.contains(eventNames().wheelEvent) || data->eventListenerMap.contains(eventNames().mousewheelEvent))
invalidateEventListenerRegions();
data->eventListenerMap.clear();
eventListenersDidChange();
}
threadData.setIsInRemoveAllEventListeners(false);
}
template<typename Visitor>
void EventTarget::visitJSEventListeners(Visitor& visitor)
{
EventTargetData* data = eventTargetDataConcurrently();
if (!data)
return;
Locker locker { data->eventListenerMap.lock() };
EventListenerIterator iterator(&data->eventListenerMap);
while (auto* listener = iterator.nextListener())
listener->visitJSFunction(visitor);
}
template void EventTarget::visitJSEventListeners(JSC::AbstractSlotVisitor&);
template void EventTarget::visitJSEventListeners(JSC::SlotVisitor&);
void EventTarget::invalidateEventListenerRegions()
{
if (is<Element>(*this)) {
downcast<Element>(*this).invalidateEventListenerRegions();
return;
}
auto* document = [&]() -> Document* {
if (is<Document>(*this))
return &downcast<Document>(*this);
if (is<DOMWindow>(*this))
return downcast<DOMWindow>(*this).document();
return nullptr;
}();
if (document)
document->invalidateEventListenerRegions();
}
} // namespace WebCore