1/*
2 * Copyright (C) 1999 Lars Knoll (knoll@kde.org)
3 * (C) 1999 Antti Koivisto (koivisto@kde.org)
4 * (C) 2001 Dirk Mueller (mueller@kde.org)
5 * Copyright (C) 2004-2017 Apple Inc. All rights reserved.
6 * Copyright (C) 2006 Alexey Proskuryakov (ap@webkit.org)
7 * (C) 2007, 2008 Nikolas Zimmermann <zimmermann@kde.org>
8 *
9 * Redistribution and use in source and binary forms, with or without
10 * modification, are permitted provided that the following conditions
11 * are met:
12 * 1. Redistributions of source code must retain the above copyright
13 * notice, this list of conditions and the following disclaimer.
14 * 2. Redistributions in binary form must reproduce the above copyright
15 * notice, this list of conditions and the following disclaimer in the
16 * documentation and/or other materials provided with the distribution.
17 *
18 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
19 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
21 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
22 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
23 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
24 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
25 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
26 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 *
30 */
31
32#include "config.h"
33#include "EventTarget.h"
34
35#include "DOMWrapperWorld.h"
36#include "EventNames.h"
37#include "HTMLBodyElement.h"
38#include "HTMLHtmlElement.h"
39#include "InspectorInstrumentation.h"
40#include "JSEventListener.h"
41#include "JSLazyEventListener.h"
42#include "RuntimeEnabledFeatures.h"
43#include "ScriptController.h"
44#include "ScriptDisallowedScope.h"
45#include "Settings.h"
46#include "WebKitAnimationEvent.h"
47#include "WebKitTransitionEvent.h"
48#include <wtf/IsoMallocInlines.h>
49#include <wtf/MainThread.h>
50#include <wtf/NeverDestroyed.h>
51#include <wtf/Ref.h>
52#include <wtf/SetForScope.h>
53#include <wtf/StdLibExtras.h>
54#include <wtf/Vector.h>
55
56namespace WebCore {
57
58WTF_MAKE_ISO_ALLOCATED_IMPL(EventTarget);
59WTF_MAKE_ISO_ALLOCATED_IMPL(EventTargetWithInlineData);
60
61bool EventTarget::isNode() const
62{
63 return false;
64}
65
66bool EventTarget::isPaymentRequest() const
67{
68 return false;
69}
70
71bool EventTarget::addEventListener(const AtomString& eventType, Ref<EventListener>&& listener, const AddEventListenerOptions& options)
72{
73#if !ASSERT_DISABLED
74 listener->checkValidityForEventTarget(*this);
75#endif
76
77 auto passive = options.passive;
78
79 if (!passive.hasValue() && eventNames().isTouchScrollBlockingEventType(eventType)) {
80 if (is<DOMWindow>(*this)) {
81 auto& window = downcast<DOMWindow>(*this);
82 if (auto* document = window.document())
83 passive = document->settings().passiveTouchListenersAsDefaultOnDocument();
84 } else if (is<Node>(*this)) {
85 auto& node = downcast<Node>(*this);
86 if (is<Document>(node) || node.document().documentElement() == &node || node.document().body() == &node)
87 passive = node.document().settings().passiveTouchListenersAsDefaultOnDocument();
88 }
89 }
90
91 bool listenerCreatedFromScript = listener->type() == EventListener::JSEventListenerType && !listener->wasCreatedFromMarkup();
92 auto listenerRef = listener.copyRef();
93
94 if (!ensureEventTargetData().eventListenerMap.add(eventType, WTFMove(listener), { options.capture, passive.valueOr(false), options.once }))
95 return false;
96
97 if (listenerCreatedFromScript)
98 InspectorInstrumentation::didAddEventListener(*this, eventType, listenerRef.get(), options.capture);
99
100 return true;
101}
102
103void EventTarget::addEventListenerForBindings(const AtomString& eventType, RefPtr<EventListener>&& listener, AddEventListenerOptionsOrBoolean&& variant)
104{
105 if (!listener)
106 return;
107
108 auto visitor = WTF::makeVisitor([&](const AddEventListenerOptions& options) {
109 addEventListener(eventType, listener.releaseNonNull(), options);
110 }, [&](bool capture) {
111 addEventListener(eventType, listener.releaseNonNull(), capture);
112 });
113
114 WTF::visit(visitor, variant);
115}
116
117void EventTarget::removeEventListenerForBindings(const AtomString& eventType, RefPtr<EventListener>&& listener, ListenerOptionsOrBoolean&& variant)
118{
119 if (!listener)
120 return;
121
122 auto visitor = WTF::makeVisitor([&](const ListenerOptions& options) {
123 removeEventListener(eventType, *listener, options);
124 }, [&](bool capture) {
125 removeEventListener(eventType, *listener, capture);
126 });
127
128 WTF::visit(visitor, variant);
129}
130
131bool EventTarget::removeEventListener(const AtomString& eventType, EventListener& listener, const ListenerOptions& options)
132{
133 auto* data = eventTargetData();
134 if (!data)
135 return false;
136
137 InspectorInstrumentation::willRemoveEventListener(*this, eventType, listener, options.capture);
138
139 return data->eventListenerMap.remove(eventType, listener, options.capture);
140}
141
142bool EventTarget::setAttributeEventListener(const AtomString& eventType, RefPtr<EventListener>&& listener, DOMWrapperWorld& isolatedWorld)
143{
144 auto* existingListener = attributeEventListener(eventType, isolatedWorld);
145 if (!listener) {
146 if (existingListener)
147 removeEventListener(eventType, *existingListener, false);
148 return false;
149 }
150 if (existingListener) {
151 InspectorInstrumentation::willRemoveEventListener(*this, eventType, *existingListener, false);
152
153#if !ASSERT_DISABLED
154 listener->checkValidityForEventTarget(*this);
155#endif
156
157 auto listenerPointer = listener.copyRef();
158 eventTargetData()->eventListenerMap.replace(eventType, *existingListener, listener.releaseNonNull(), { });
159
160 InspectorInstrumentation::didAddEventListener(*this, eventType, *listenerPointer, false);
161
162 return true;
163 }
164 return addEventListener(eventType, listener.releaseNonNull());
165}
166
167EventListener* EventTarget::attributeEventListener(const AtomString& eventType, DOMWrapperWorld& isolatedWorld)
168{
169 for (auto& eventListener : eventListeners(eventType)) {
170 auto& listener = eventListener->callback();
171 if (!listener.isAttribute())
172 continue;
173
174 auto& listenerWorld = downcast<JSEventListener>(listener).isolatedWorld();
175 if (&listenerWorld == &isolatedWorld)
176 return &listener;
177 }
178
179 return nullptr;
180}
181
182bool EventTarget::hasActiveEventListeners(const AtomString& eventType) const
183{
184 auto* data = eventTargetData();
185 return data && data->eventListenerMap.containsActive(eventType);
186}
187
188ExceptionOr<bool> EventTarget::dispatchEventForBindings(Event& event)
189{
190 event.setUntrusted();
191
192 if (!event.isInitialized() || event.isBeingDispatched())
193 return Exception { InvalidStateError };
194
195 if (!scriptExecutionContext())
196 return false;
197
198 dispatchEvent(event);
199 return event.legacyReturnValue();
200}
201
202void EventTarget::dispatchEvent(Event& event)
203{
204 // FIXME: We should always use EventDispatcher.
205 ASSERT(event.isInitialized());
206 ASSERT(!event.isBeingDispatched());
207
208 event.setTarget(this);
209 event.setCurrentTarget(this);
210 event.setEventPhase(Event::AT_TARGET);
211 event.resetBeforeDispatch();
212 fireEventListeners(event, EventInvokePhase::Capturing);
213 fireEventListeners(event, EventInvokePhase::Bubbling);
214 event.resetAfterDispatch();
215}
216
217void EventTarget::uncaughtExceptionInEventHandler()
218{
219}
220
221static const AtomString& legacyType(const Event& event)
222{
223 if (event.type() == eventNames().animationendEvent)
224 return eventNames().webkitAnimationEndEvent;
225
226 if (event.type() == eventNames().animationstartEvent)
227 return eventNames().webkitAnimationStartEvent;
228
229 if (event.type() == eventNames().animationiterationEvent)
230 return eventNames().webkitAnimationIterationEvent;
231
232 if (event.type() == eventNames().transitionendEvent)
233 return eventNames().webkitTransitionEndEvent;
234
235 // FIXME: This legacy name is not part of the specification (https://dom.spec.whatwg.org/#dispatching-events).
236 if (event.type() == eventNames().wheelEvent)
237 return eventNames().mousewheelEvent;
238
239 return nullAtom();
240}
241
242// https://dom.spec.whatwg.org/#concept-event-listener-invoke
243void EventTarget::fireEventListeners(Event& event, EventInvokePhase phase)
244{
245 ASSERT_WITH_SECURITY_IMPLICATION(ScriptDisallowedScope::isEventAllowedInMainThread());
246 ASSERT(event.isInitialized());
247
248 auto* data = eventTargetData();
249 if (!data)
250 return;
251
252 SetForScope<bool> firingEventListenersScope(data->isFiringEventListeners, true);
253
254 if (auto* listenersVector = data->eventListenerMap.find(event.type())) {
255 innerInvokeEventListeners(event, *listenersVector, phase);
256 return;
257 }
258
259 // Only fall back to legacy types for trusted events.
260 if (!event.isTrusted())
261 return;
262
263 const AtomString& legacyTypeName = legacyType(event);
264 if (!legacyTypeName.isNull()) {
265 if (auto* legacyListenersVector = data->eventListenerMap.find(legacyTypeName)) {
266 AtomString typeName = event.type();
267 event.setType(legacyTypeName);
268 innerInvokeEventListeners(event, *legacyListenersVector, phase);
269 event.setType(typeName);
270 }
271 }
272}
273
274// Intentionally creates a copy of the listeners vector to avoid event listeners added after this point from being run.
275// Note that removal still has an effect due to the removed field in RegisteredEventListener.
276// https://dom.spec.whatwg.org/#concept-event-listener-inner-invoke
277void EventTarget::innerInvokeEventListeners(Event& event, EventListenerVector listeners, EventInvokePhase phase)
278{
279 Ref<EventTarget> protectedThis(*this);
280 ASSERT(!listeners.isEmpty());
281 ASSERT(scriptExecutionContext());
282
283 auto& context = *scriptExecutionContext();
284 bool contextIsDocument = is<Document>(context);
285 InspectorInstrumentationCookie willDispatchEventCookie;
286 if (contextIsDocument)
287 willDispatchEventCookie = InspectorInstrumentation::willDispatchEvent(downcast<Document>(context), event, true);
288
289 for (auto& registeredListener : listeners) {
290 if (UNLIKELY(registeredListener->wasRemoved()))
291 continue;
292
293 if (phase == EventInvokePhase::Capturing && !registeredListener->useCapture())
294 continue;
295 if (phase == EventInvokePhase::Bubbling && registeredListener->useCapture())
296 continue;
297
298 if (InspectorInstrumentation::isEventListenerDisabled(*this, event.type(), registeredListener->callback(), registeredListener->useCapture()))
299 continue;
300
301 // If stopImmediatePropagation has been called, we just break out immediately, without
302 // handling any more events on this target.
303 if (event.immediatePropagationStopped())
304 break;
305
306 // Do this before invocation to avoid reentrancy issues.
307 if (registeredListener->isOnce())
308 removeEventListener(event.type(), registeredListener->callback(), ListenerOptions(registeredListener->useCapture()));
309
310 if (registeredListener->isPassive())
311 event.setInPassiveListener(true);
312
313#if !ASSERT_DISABLED
314 registeredListener->callback().checkValidityForEventTarget(*this);
315#endif
316
317 InspectorInstrumentation::willHandleEvent(context, event, *registeredListener);
318 registeredListener->callback().handleEvent(context, event);
319 InspectorInstrumentation::didHandleEvent(context);
320
321 if (registeredListener->isPassive())
322 event.setInPassiveListener(false);
323 }
324
325 if (contextIsDocument)
326 InspectorInstrumentation::didDispatchEvent(willDispatchEventCookie, event.defaultPrevented());
327}
328
329Vector<AtomString> EventTarget::eventTypes()
330{
331 if (auto* data = eventTargetData())
332 return data->eventListenerMap.eventTypes();
333 return { };
334}
335
336const EventListenerVector& EventTarget::eventListeners(const AtomString& eventType)
337{
338 auto* data = eventTargetData();
339 auto* listenerVector = data ? data->eventListenerMap.find(eventType) : nullptr;
340 static NeverDestroyed<EventListenerVector> emptyVector;
341 return listenerVector ? *listenerVector : emptyVector.get();
342}
343
344void EventTarget::removeAllEventListeners()
345{
346 auto& threadData = threadGlobalData();
347 RELEASE_ASSERT(!threadData.isInRemoveAllEventListeners());
348
349 threadData.setIsInRemoveAllEventListeners(true);
350
351 auto* data = eventTargetData();
352 if (data)
353 data->eventListenerMap.clear();
354
355 threadData.setIsInRemoveAllEventListeners(false);
356}
357
358void EventTarget::visitJSEventListeners(JSC::SlotVisitor& visitor)
359{
360 EventTargetData* data = eventTargetDataConcurrently();
361 if (!data)
362 return;
363
364 auto locker = holdLock(data->eventListenerMap.lock());
365 EventListenerIterator iterator(&data->eventListenerMap);
366 while (auto* listener = iterator.nextListener())
367 listener->visitJSFunction(visitor);
368}
369
370} // namespace WebCore
371