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 | |
56 | namespace WebCore { |
57 | |
58 | WTF_MAKE_ISO_ALLOCATED_IMPL(EventTarget); |
59 | WTF_MAKE_ISO_ALLOCATED_IMPL(EventTargetWithInlineData); |
60 | |
61 | bool EventTarget::isNode() const |
62 | { |
63 | return false; |
64 | } |
65 | |
66 | bool EventTarget::isPaymentRequest() const |
67 | { |
68 | return false; |
69 | } |
70 | |
71 | bool EventTarget::addEventListener(const AtomicString& 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 | |
103 | void EventTarget::addEventListenerForBindings(const AtomicString& 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 | |
117 | void EventTarget::removeEventListenerForBindings(const AtomicString& 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 | |
131 | bool EventTarget::removeEventListener(const AtomicString& 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 | |
142 | bool EventTarget::setAttributeEventListener(const AtomicString& 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 | |
167 | EventListener* EventTarget::attributeEventListener(const AtomicString& 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 | |
182 | bool EventTarget::hasActiveEventListeners(const AtomicString& eventType) const |
183 | { |
184 | auto* data = eventTargetData(); |
185 | return data && data->eventListenerMap.containsActive(eventType); |
186 | } |
187 | |
188 | ExceptionOr<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 | |
202 | void 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 | |
217 | void EventTarget::uncaughtExceptionInEventHandler() |
218 | { |
219 | } |
220 | |
221 | static const AtomicString& 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 |
243 | void 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> (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 AtomicString& legacyTypeName = legacyType(event); |
264 | if (!legacyTypeName.isNull()) { |
265 | if (auto* legacyListenersVector = data->eventListenerMap.find(legacyTypeName)) { |
266 | AtomicString 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 |
277 | void 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 | |
329 | Vector<AtomicString> EventTarget::eventTypes() |
330 | { |
331 | if (auto* data = eventTargetData()) |
332 | return data->eventListenerMap.eventTypes(); |
333 | return { }; |
334 | } |
335 | |
336 | const EventListenerVector& EventTarget::eventListeners(const AtomicString& 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 | |
344 | void 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 | |
358 | void 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 | |