diff options
Diffstat (limited to 'src/javascript/jsc/bindings/webcore/EventTarget.cpp')
-rw-r--r-- | src/javascript/jsc/bindings/webcore/EventTarget.cpp | 391 |
1 files changed, 391 insertions, 0 deletions
diff --git a/src/javascript/jsc/bindings/webcore/EventTarget.cpp b/src/javascript/jsc/bindings/webcore/EventTarget.cpp new file mode 100644 index 000000000..3b7128b7a --- /dev/null +++ b/src/javascript/jsc/bindings/webcore/EventTarget.cpp @@ -0,0 +1,391 @@ +/* + * 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 "Event.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 = is<JSEventListener>(listener) && !downcast<JSEventListener>(listener.get()).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) +{ + + 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 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); +} + +void EventTarget::invalidateEventListenerRegions() +{ +} + +} // namespace WebCore |