Coverage Report

Created: 2026-06-07 07:41

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/serenity/Userland/Libraries/LibWeb/DOM/EventTarget.cpp
Line
Count
Source
1
/*
2
 * Copyright (c) 2020-2022, Andreas Kling <kling@serenityos.org>
3
 * Copyright (c) 2022, Luke Wilde <lukew@serenityos.org>
4
 *
5
 * SPDX-License-Identifier: BSD-2-Clause
6
 */
7
8
#include <AK/StringBuilder.h>
9
#include <LibJS/Parser.h>
10
#include <LibJS/Runtime/AbstractOperations.h>
11
#include <LibJS/Runtime/ECMAScriptFunctionObject.h>
12
#include <LibJS/Runtime/GlobalEnvironment.h>
13
#include <LibJS/Runtime/NativeFunction.h>
14
#include <LibJS/Runtime/ObjectEnvironment.h>
15
#include <LibJS/Runtime/VM.h>
16
#include <LibWeb/Bindings/EventTargetPrototype.h>
17
#include <LibWeb/Bindings/MainThreadVM.h>
18
#include <LibWeb/DOM/AbortSignal.h>
19
#include <LibWeb/DOM/DOMEventListener.h>
20
#include <LibWeb/DOM/Document.h>
21
#include <LibWeb/DOM/Event.h>
22
#include <LibWeb/DOM/EventDispatcher.h>
23
#include <LibWeb/DOM/EventTarget.h>
24
#include <LibWeb/DOM/IDLEventListener.h>
25
#include <LibWeb/HTML/BeforeUnloadEvent.h>
26
#include <LibWeb/HTML/CloseWatcherManager.h>
27
#include <LibWeb/HTML/ErrorEvent.h>
28
#include <LibWeb/HTML/EventHandler.h>
29
#include <LibWeb/HTML/EventNames.h>
30
#include <LibWeb/HTML/FormAssociatedElement.h>
31
#include <LibWeb/HTML/HTMLBodyElement.h>
32
#include <LibWeb/HTML/HTMLFormElement.h>
33
#include <LibWeb/HTML/HTMLFrameSetElement.h>
34
#include <LibWeb/HTML/Window.h>
35
#include <LibWeb/HighResolutionTime/TimeOrigin.h>
36
#include <LibWeb/UIEvents/EventNames.h>
37
#include <LibWeb/UIEvents/KeyCode.h>
38
#include <LibWeb/UIEvents/KeyboardEvent.h>
39
#include <LibWeb/WebIDL/AbstractOperations.h>
40
41
namespace Web::DOM {
42
43
JS_DEFINE_ALLOCATOR(EventTarget);
44
45
EventTarget::EventTarget(JS::Realm& realm, MayInterfereWithIndexedPropertyAccess may_interfere_with_indexed_property_access)
46
0
    : PlatformObject(realm, may_interfere_with_indexed_property_access)
47
0
{
48
0
}
49
50
0
EventTarget::~EventTarget() = default;
51
52
// https://dom.spec.whatwg.org/#dom-eventtarget-eventtarget
53
WebIDL::ExceptionOr<JS::NonnullGCPtr<EventTarget>> EventTarget::construct_impl(JS::Realm& realm)
54
0
{
55
    // The new EventTarget() constructor steps are to do nothing.
56
0
    return realm.heap().allocate<EventTarget>(realm, realm);
57
0
}
58
59
void EventTarget::initialize(JS::Realm& realm)
60
0
{
61
0
    Base::initialize(realm);
62
63
    // FIXME: We can't do this for HTML::Window or HTML::WorkerGlobalScope, as this will run when creating the initial global object.
64
    //        During this time, the ESO is not setup, so it will cause a nullptr dereference in host_defined_intrinsics.
65
0
    if (!is<HTML::WindowOrWorkerGlobalScopeMixin>(this))
66
0
        WEB_SET_PROTOTYPE_FOR_INTERFACE(EventTarget);
67
0
}
68
69
void EventTarget::visit_edges(Cell::Visitor& visitor)
70
0
{
71
0
    Base::visit_edges(visitor);
72
73
0
    if (auto const* data = m_data.ptr()) {
74
0
        visitor.visit(data->event_listener_list);
75
0
        visitor.visit(data->event_handler_map);
76
0
    }
77
0
}
78
79
Vector<JS::Handle<DOMEventListener>> EventTarget::event_listener_list()
80
0
{
81
0
    Vector<JS::Handle<DOMEventListener>> list;
82
0
    if (!m_data)
83
0
        return list;
84
0
    for (auto& listener : m_data->event_listener_list)
85
0
        list.append(*listener);
86
0
    return list;
87
0
}
88
89
// https://dom.spec.whatwg.org/#concept-flatten-options
90
static bool flatten_event_listener_options(Variant<EventListenerOptions, bool> const& options)
91
0
{
92
    // 1. If options is a boolean, then return options.
93
0
    if (options.has<bool>())
94
0
        return options.get<bool>();
95
96
    // 2. Return options["capture"].
97
0
    return options.get<EventListenerOptions>().capture;
98
0
}
99
100
static bool flatten_event_listener_options(Variant<AddEventListenerOptions, bool> const& options)
101
0
{
102
    // 1. If options is a boolean, then return options.
103
0
    if (options.has<bool>())
104
0
        return options.get<bool>();
105
106
    // 2. Return options["capture"].
107
0
    return options.get<AddEventListenerOptions>().capture;
108
0
}
109
110
struct FlattenedAddEventListenerOptions {
111
    bool capture { false };
112
    bool passive { false };
113
    bool once { false };
114
    JS::GCPtr<AbortSignal> signal;
115
};
116
117
// https://dom.spec.whatwg.org/#event-flatten-more
118
static FlattenedAddEventListenerOptions flatten_add_event_listener_options(Variant<AddEventListenerOptions, bool> const& options)
119
0
{
120
    // 1. Let capture be the result of flattening options.
121
0
    bool capture = flatten_event_listener_options(options);
122
123
    // 2. Let once and passive be false.
124
0
    bool once = false;
125
0
    bool passive = false;
126
127
    // 3. Let signal be null.
128
0
    JS::GCPtr<AbortSignal> signal;
129
130
    // 4. If options is a dictionary, then:
131
0
    if (options.has<AddEventListenerOptions>()) {
132
0
        auto& add_event_listener_options = options.get<AddEventListenerOptions>();
133
134
        // 1. Set passive to options["passive"] and once to options["once"].
135
0
        passive = add_event_listener_options.passive;
136
0
        once = add_event_listener_options.once;
137
138
        // 2. If options["signal"] exists, then set signal to options["signal"].
139
0
        if (add_event_listener_options.signal)
140
0
            signal = add_event_listener_options.signal;
141
0
    }
142
143
    // 5. Return capture, passive, once, and signal.
144
0
    return FlattenedAddEventListenerOptions { .capture = capture, .passive = passive, .once = once, .signal = signal.ptr() };
145
0
}
146
147
// https://dom.spec.whatwg.org/#dom-eventtarget-addeventlistener
148
void EventTarget::add_event_listener(FlyString const& type, IDLEventListener* callback, Variant<AddEventListenerOptions, bool> const& options)
149
0
{
150
    // 1. Let capture, passive, once, and signal be the result of flattening more options.
151
0
    auto flattened_options = flatten_add_event_listener_options(options);
152
153
    // 2. Add an event listener with this and an event listener whose type is type, callback is callback, capture is capture, passive is passive,
154
    //    once is once, and signal is signal.
155
156
0
    auto event_listener = heap().allocate_without_realm<DOMEventListener>();
157
0
    event_listener->type = type;
158
0
    event_listener->callback = callback;
159
0
    event_listener->signal = move(flattened_options.signal);
160
0
    event_listener->capture = flattened_options.capture;
161
0
    event_listener->passive = flattened_options.passive;
162
0
    event_listener->once = flattened_options.once;
163
0
    add_an_event_listener(*event_listener);
164
0
}
165
166
void EventTarget::add_event_listener_without_options(FlyString const& type, IDLEventListener& callback)
167
0
{
168
0
    add_event_listener(type, &callback, AddEventListenerOptions {});
169
0
}
170
171
// https://dom.spec.whatwg.org/#add-an-event-listener
172
void EventTarget::add_an_event_listener(DOMEventListener& listener)
173
0
{
174
    // FIXME: 1. If eventTarget is a ServiceWorkerGlobalScope object, its service worker’s script resource’s has ever been evaluated flag is set,
175
    //           and listener’s type matches the type attribute value of any of the service worker events, then report a warning to the console
176
    //           that this might not give the expected results. [SERVICE-WORKERS]
177
178
0
    auto& event_listener_list = ensure_data().event_listener_list;
179
180
    // 2. If listener’s signal is not null and is aborted, then return.
181
0
    if (listener.signal && listener.signal->aborted())
182
0
        return;
183
184
    // 3. If listener’s callback is null, then return.
185
0
    if (!listener.callback)
186
0
        return;
187
188
    // 4. If eventTarget’s event listener list does not contain an event listener whose type is listener’s type, callback is listener’s callback,
189
    //    and capture is listener’s capture, then append listener to eventTarget’s event listener list.
190
0
    auto it = event_listener_list.find_if([&](auto& entry) {
191
0
        return entry->type == listener.type
192
0
            && entry->callback->callback().callback == listener.callback->callback().callback
193
0
            && entry->capture == listener.capture;
194
0
    });
195
0
    if (it == event_listener_list.end())
196
0
        event_listener_list.append(listener);
197
198
    // 5. If listener’s signal is not null, then add the following abort steps to it:
199
0
    if (listener.signal) {
200
        // NOTE: `this` and `listener` are protected by AbortSignal using JS::SafeFunction.
201
0
        listener.signal->add_abort_algorithm([this, &listener] {
202
            // 1. Remove an event listener with eventTarget and listener.
203
0
            remove_an_event_listener(listener);
204
0
        });
205
0
    }
206
0
}
207
208
// https://dom.spec.whatwg.org/#dom-eventtarget-removeeventlistener
209
void EventTarget::remove_event_listener(FlyString const& type, IDLEventListener* callback, Variant<EventListenerOptions, bool> const& options)
210
0
{
211
0
    auto& event_listener_list = ensure_data().event_listener_list;
212
213
    // 1. Let capture be the result of flattening options.
214
0
    bool capture = flatten_event_listener_options(options);
215
216
    // 2. If this’s event listener list contains an event listener whose type is type, callback is callback, and capture is capture,
217
    //    then remove an event listener with this and that event listener.
218
0
    auto callbacks_match = [&](DOMEventListener& entry) {
219
0
        if (!entry.callback && !callback)
220
0
            return true;
221
0
        if (!entry.callback || !callback)
222
0
            return false;
223
0
        return entry.callback->callback().callback == callback->callback().callback;
224
0
    };
225
0
    auto it = event_listener_list.find_if([&](auto& entry) {
226
0
        return entry->type == type
227
0
            && callbacks_match(*entry)
228
0
            && entry->capture == capture;
229
0
    });
230
0
    if (it != event_listener_list.end())
231
0
        remove_an_event_listener(**it);
232
0
}
233
234
void EventTarget::remove_event_listener_without_options(FlyString const& type, IDLEventListener& callback)
235
0
{
236
0
    remove_event_listener(type, &callback, EventListenerOptions {});
237
0
}
238
239
// https://dom.spec.whatwg.org/#remove-an-event-listener
240
void EventTarget::remove_an_event_listener(DOMEventListener& listener)
241
0
{
242
    // FIXME: 1. If eventTarget is a ServiceWorkerGlobalScope object and its service worker’s set of event types to handle contains type,
243
    //           then report a warning to the console that this might not give the expected results. [SERVICE-WORKERS]
244
245
    // 2. Set listener’s removed to true and remove listener from eventTarget’s event listener list.
246
0
    listener.removed = true;
247
0
    VERIFY(m_data);
248
0
    m_data->event_listener_list.remove_first_matching([&](auto& entry) { return entry.ptr() == &listener; });
249
0
}
250
251
void EventTarget::remove_from_event_listener_list(DOMEventListener& listener)
252
0
{
253
0
    if (!m_data)
254
0
        return;
255
0
    m_data->event_listener_list.remove_first_matching([&](auto& entry) { return entry.ptr() == &listener; });
256
0
}
257
258
// https://dom.spec.whatwg.org/#dom-eventtarget-dispatchevent
259
WebIDL::ExceptionOr<bool> EventTarget::dispatch_event_binding(Event& event)
260
0
{
261
    // 1. If event’s dispatch flag is set, or if its initialized flag is not set, then throw an "InvalidStateError" DOMException.
262
0
    if (event.dispatched())
263
0
        return WebIDL::InvalidStateError::create(realm(), "The event is already being dispatched."_string);
264
265
0
    if (!event.initialized())
266
0
        return WebIDL::InvalidStateError::create(realm(), "Cannot dispatch an uninitialized event."_string);
267
268
    // 2. Initialize event’s isTrusted attribute to false.
269
0
    event.set_is_trusted(false);
270
271
    // 3. Return the result of dispatching event to this.
272
0
    return dispatch_event(event);
273
0
}
274
275
// https://html.spec.whatwg.org/multipage/webappapis.html#window-reflecting-body-element-event-handler-set
276
bool is_window_reflecting_body_element_event_handler(FlyString const& name)
277
0
{
278
0
    return name.is_one_of(
279
0
        HTML::EventNames::blur,
280
0
        HTML::EventNames::error,
281
0
        HTML::EventNames::focus,
282
0
        HTML::EventNames::load,
283
0
        UIEvents::EventNames::resize,
284
0
        "scroll");
285
0
}
286
287
// https://html.spec.whatwg.org/multipage/webappapis.html#windoweventhandlers
288
static bool is_window_event_handler(FlyString const& name)
289
0
{
290
0
    return name.is_one_of(
291
0
        HTML::EventNames::afterprint,
292
0
        HTML::EventNames::beforeprint,
293
0
        HTML::EventNames::beforeunload,
294
0
        HTML::EventNames::hashchange,
295
0
        HTML::EventNames::languagechange,
296
0
        HTML::EventNames::message,
297
0
        HTML::EventNames::messageerror,
298
0
        HTML::EventNames::offline,
299
0
        HTML::EventNames::online,
300
0
        HTML::EventNames::pagehide,
301
0
        HTML::EventNames::pageshow,
302
0
        HTML::EventNames::popstate,
303
0
        HTML::EventNames::rejectionhandled,
304
0
        HTML::EventNames::storage,
305
0
        HTML::EventNames::unhandledrejection,
306
0
        HTML::EventNames::unload);
307
0
}
308
309
// https://html.spec.whatwg.org/multipage/webappapis.html#determining-the-target-of-an-event-handler
310
static EventTarget* determine_target_of_event_handler(EventTarget& event_target, FlyString const& name)
311
0
{
312
    // To determine the target of an event handler, given an EventTarget object eventTarget on which the event handler is exposed,
313
    // and an event handler name name, the following steps are taken:
314
315
    // 1. If eventTarget is not a body element or a frameset element, then return eventTarget.
316
0
    if (!is<HTML::HTMLBodyElement>(event_target) && !is<HTML::HTMLFrameSetElement>(event_target))
317
0
        return &event_target;
318
319
0
    auto& event_target_element = static_cast<HTML::HTMLElement&>(event_target);
320
321
    // 2. If name is not the name of an attribute member of the WindowEventHandlers interface mixin and the Window-reflecting
322
    //    body element event handler set does not contain name, then return eventTarget.
323
0
    if (!is_window_event_handler(name) && !is_window_reflecting_body_element_event_handler(name))
324
0
        return &event_target;
325
326
    // 3. If eventTarget's node document is not an active document, then return null.
327
0
    if (!event_target_element.document().is_active())
328
0
        return nullptr;
329
330
    // 4. Return eventTarget's node document's relevant global object.
331
0
    return &verify_cast<EventTarget>(HTML::relevant_global_object(event_target_element.document()));
332
0
}
333
334
// https://html.spec.whatwg.org/multipage/webappapis.html#event-handler-attributes:event-handler-idl-attributes-2
335
WebIDL::CallbackType* EventTarget::event_handler_attribute(FlyString const& name)
336
0
{
337
    // 1. Let eventTarget be the result of determining the target of an event handler given this object and name.
338
0
    auto target = determine_target_of_event_handler(*this, name);
339
340
    // 2. If eventTarget is null, then return null.
341
0
    if (!target)
342
0
        return nullptr;
343
344
    // 3. Return the result of getting the current value of the event handler given eventTarget and name.
345
0
    return target->get_current_value_of_event_handler(name);
346
0
}
347
348
// https://html.spec.whatwg.org/multipage/webappapis.html#getting-the-current-value-of-the-event-handler
349
WebIDL::CallbackType* EventTarget::get_current_value_of_event_handler(FlyString const& name)
350
0
{
351
    // 1. Let handlerMap be eventTarget's event handler map. (NOTE: Not necessary)
352
0
    auto& handler_map = ensure_data().event_handler_map;
353
354
    // 2. Let eventHandler be handlerMap[name].
355
0
    auto event_handler_iterator = handler_map.find(name);
356
357
    // Optimization: The spec creates all the event handlers exposed on an object up front and has the initial value of the handler set to null.
358
    //               If the event handler hasn't been set, null would be returned in step 4.
359
    //               However, this would be very allocation heavy. For example, each DOM::Element includes GlobalEventHandlers, which defines 60+(!) event handler attributes.
360
    //               Plus, the vast majority of these allocations would be likely wasted, as I imagine web content will only use a handful of these attributes on certain elements, if any at all.
361
    //               Thus, we treat the event handler not being in the event handler map as being equivalent to an event handler with an initial null value.
362
0
    if (event_handler_iterator == handler_map.end())
363
0
        return nullptr;
364
365
0
    auto& event_handler = event_handler_iterator->value;
366
367
    // 3. If eventHandler's value is an internal raw uncompiled handler, then:
368
0
    if (event_handler->value.has<ByteString>()) {
369
        // 1. If eventTarget is an element, then let element be eventTarget, and document be element's node document.
370
        //    Otherwise, eventTarget is a Window object, let element be null, and document be eventTarget's associated Document.
371
0
        JS::GCPtr<Element> element;
372
0
        JS::GCPtr<Document> document;
373
374
0
        if (is<Element>(this)) {
375
0
            auto* element_event_target = verify_cast<Element>(this);
376
0
            element = element_event_target;
377
0
            document = &element_event_target->document();
378
0
        } else {
379
0
            VERIFY(is<HTML::Window>(this));
380
0
            auto* window_event_target = verify_cast<HTML::Window>(this);
381
0
            document = &window_event_target->associated_document();
382
0
        }
383
384
0
        VERIFY(document);
385
386
        // 2. If scripting is disabled for document, then return null.
387
0
        if (document->is_scripting_disabled())
388
0
            return nullptr;
389
390
        // 3. Let body be the uncompiled script body in eventHandler's value.
391
0
        auto& body = event_handler->value.get<ByteString>();
392
393
        // FIXME: 4. Let location be the location where the script body originated, as given by eventHandler's value.
394
395
        // 5. If element is not null and element has a form owner, let form owner be that form owner. Otherwise, let form owner be null.
396
0
        JS::GCPtr<HTML::HTMLFormElement> form_owner;
397
0
        if (is<HTML::FormAssociatedElement>(element.ptr())) {
398
0
            auto* form_associated_element = dynamic_cast<HTML::FormAssociatedElement*>(element.ptr());
399
0
            VERIFY(form_associated_element);
400
401
0
            if (form_associated_element->form())
402
0
                form_owner = form_associated_element->form();
403
0
        }
404
405
        // 6. Let settings object be the relevant settings object of document.
406
0
        auto& settings_object = document->relevant_settings_object();
407
408
        // NOTE: ECMAScriptFunctionObject::create expects a parsed body as input, so we must do the spec's sourceText steps here.
409
0
        StringBuilder builder;
410
411
        // sourceText
412
0
        if (name == HTML::EventNames::error && is<HTML::Window>(this)) {
413
            //  -> If name is onerror and eventTarget is a Window object
414
            //      The string formed by concatenating "function ", name, "(event, source, lineno, colno, error) {", U+000A LF, body, U+000A LF, and "}".
415
0
            builder.appendff("function {}(event, source, lineno, colno, error) {{\n{}\n}}", name, body);
416
0
        } else {
417
            //  -> Otherwise
418
            //      The string formed by concatenating "function ", name, "(event) {", U+000A LF, body, U+000A LF, and "}".
419
0
            builder.appendff("function {}(event) {{\n{}\n}}", name, body);
420
0
        }
421
422
0
        auto source_text = builder.to_byte_string();
423
424
0
        auto parser = JS::Parser(JS::Lexer(source_text));
425
426
        // FIXME: This should only be parsing the `body` instead of `source_text` and therefore use `JS::FunctionBody` instead of `JS::FunctionExpression`.
427
        //        However, JS::ECMAScriptFunctionObject::create wants parameters and length and JS::FunctionBody does not inherit JS::FunctionNode.
428
0
        auto program = parser.parse_function_node<JS::FunctionExpression>();
429
430
        // 7. If body is not parsable as FunctionBody or if parsing detects an early error, then follow these substeps:
431
0
        if (parser.has_errors()) {
432
            // 1. Set eventHandler's value to null.
433
            //    Note: This does not deactivate the event handler, which additionally removes the event handler's listener (if present).
434
0
            handler_map.remove(event_handler_iterator);
435
436
            // FIXME: 2. Report the error for the appropriate script and with the appropriate position (line number and column number) given by location, using settings object's global object.
437
            //           If the error is still not handled after this, then the error may be reported to a developer console.
438
439
            // 3. Return null.
440
0
            return nullptr;
441
0
        }
442
443
0
        auto& vm = Bindings::main_thread_vm();
444
445
        // 8. Push settings object's realm execution context onto the JavaScript execution context stack; it is now the running JavaScript execution context.
446
0
        vm.push_execution_context(settings_object.realm_execution_context());
447
448
        // 9. Let function be the result of calling OrdinaryFunctionCreate, with arguments:
449
        // functionPrototype
450
        //  %Function.prototype% (This is enforced by using JS::ECMAScriptFunctionObject)
451
452
        // sourceText was handled above.
453
454
        // ParameterList
455
        //  If name is onerror and eventTarget is a Window object
456
        //    Let the function have five arguments, named event, source, lineno, colno, and error.
457
        //  Otherwise
458
        //    Let the function have a single argument called event.
459
        // (This was handled above for us by the parser using sourceText)
460
461
        // body
462
        //  The result of parsing body above. (This is given by program->body())
463
464
        // thisMode
465
        //  non-lexical-this (For JS::ECMAScriptFunctionObject, this means passing is_arrow_function as false)
466
0
        constexpr bool is_arrow_function = false;
467
468
        // scope
469
        //  1. Let realm be settings object's Realm.
470
0
        auto& realm = settings_object.realm();
471
472
        //  2. Let scope be realm.[[GlobalEnv]].
473
0
        auto scope = JS::NonnullGCPtr<JS::Environment> { realm.global_environment() };
474
475
        // 3. If eventHandler is an element's event handler, then set scope to NewObjectEnvironment(document, true, scope).
476
        //    (Otherwise, eventHandler is a Window object's event handler.)
477
0
        if (is<Element>(this))
478
0
            scope = JS::new_object_environment(*document, true, scope);
479
480
        //  4. If form owner is not null, then set scope to NewObjectEnvironment(form owner, true, scope).
481
0
        if (form_owner)
482
0
            scope = JS::new_object_environment(*form_owner, true, scope);
483
484
        //  5. If element is not null, then set scope to NewObjectEnvironment(element, true, scope).
485
0
        if (element)
486
0
            scope = JS::new_object_environment(*element, true, scope);
487
488
        //  6. Return scope. (NOTE: Not necessary)
489
490
0
        auto function = JS::ECMAScriptFunctionObject::create(realm, name.to_deprecated_fly_string(), builder.to_byte_string(), program->body(), program->parameters(), program->function_length(), program->local_variables_names(), scope, nullptr, JS::FunctionKind::Normal, program->is_strict_mode(),
491
0
            program->parsing_insights(), is_arrow_function);
492
493
        // 10. Remove settings object's realm execution context from the JavaScript execution context stack.
494
0
        VERIFY(vm.execution_context_stack().last() == &settings_object.realm_execution_context());
495
0
        vm.pop_execution_context();
496
497
        // 11. Set function.[[ScriptOrModule]] to null.
498
0
        function->set_script_or_module({});
499
500
        // 12. Set eventHandler's value to the result of creating a Web IDL EventHandler callback function object whose object reference is function and whose callback context is settings object.
501
0
        event_handler->value = JS::GCPtr(realm.heap().allocate_without_realm<WebIDL::CallbackType>(*function, settings_object));
502
0
    }
503
504
    // 4. Return eventHandler's value.
505
0
    VERIFY(event_handler->value.has<JS::GCPtr<WebIDL::CallbackType>>());
506
0
    return *event_handler->value.get_pointer<JS::GCPtr<WebIDL::CallbackType>>();
507
0
}
508
509
// https://html.spec.whatwg.org/multipage/webappapis.html#event-handler-attributes:event-handler-idl-attributes-3
510
void EventTarget::set_event_handler_attribute(FlyString const& name, WebIDL::CallbackType* value)
511
0
{
512
    // 1. Let eventTarget be the result of determining the target of an event handler given this object and name.
513
0
    auto event_target = determine_target_of_event_handler(*this, name);
514
515
    // 2. If eventTarget is null, then return.
516
0
    if (!event_target)
517
0
        return;
518
519
    // 3. If the given value is null, then deactivate an event handler given eventTarget and name.
520
0
    if (!value) {
521
0
        event_target->deactivate_event_handler(name);
522
0
        return;
523
0
    }
524
525
    // 4. Otherwise:
526
    //  1. Let handlerMap be eventTarget's event handler map.
527
0
    auto& handler_map = event_target->ensure_data().event_handler_map;
528
529
    //  2. Let eventHandler be handlerMap[name].
530
0
    auto event_handler_iterator = handler_map.find(name);
531
532
    //  3. Set eventHandler's value to the given value.
533
0
    if (event_handler_iterator == handler_map.end()) {
534
        // NOTE: See the optimization comment in get_current_value_of_event_handler about why this is done.
535
0
        auto new_event_handler = heap().allocate_without_realm<HTML::EventHandler>(*value);
536
537
        //  4. Activate an event handler given eventTarget and name.
538
        // Optimization: We pass in the event handler here instead of having activate_event_handler do another hash map lookup just to get the same object.
539
        //               This handles a new event handler while the other path handles an existing event handler. As such, both paths must have their own
540
        //               unique call to activate_event_handler.
541
0
        event_target->activate_event_handler(name, *new_event_handler);
542
543
0
        handler_map.set(name, new_event_handler);
544
0
        return;
545
0
    }
546
547
0
    auto& event_handler = event_handler_iterator->value;
548
549
0
    event_handler->value = JS::GCPtr(value);
550
551
    //  4. Activate an event handler given eventTarget and name.
552
    //  NOTE: See the optimization comment above.
553
0
    event_target->activate_event_handler(name, *event_handler);
554
0
}
555
556
// https://html.spec.whatwg.org/multipage/webappapis.html#activate-an-event-handler
557
void EventTarget::activate_event_handler(FlyString const& name, HTML::EventHandler& event_handler)
558
0
{
559
    // 1. Let handlerMap be eventTarget's event handler map.
560
    // 2. Let eventHandler be handlerMap[name].
561
    // NOTE: These are achieved by using the passed in event handler.
562
563
    // 3. If eventHandler's listener is not null, then return.
564
0
    if (event_handler.listener)
565
0
        return;
566
567
0
    JS::Realm& realm = shape().realm();
568
569
    // 4. Let callback be the result of creating a Web IDL EventListener instance representing a reference to a function of one argument that executes the steps of the event handler processing algorithm, given eventTarget, name, and its argument.
570
    //    The EventListener's callback context can be arbitrary; it does not impact the steps of the event handler processing algorithm. [DOM]
571
572
    // NOTE: The callback must keep `this` alive. For example:
573
    //          document.body.onunload = () => { console.log("onunload called!"); }
574
    //          document.body.remove();
575
    //          location.reload();
576
    //       The body element is no longer in the DOM and there is no variable holding onto it. However, the onunload handler is still called, meaning the callback keeps the body element alive.
577
0
    auto callback_function = JS::NativeFunction::create(
578
0
        realm, [event_target = this, name](JS::VM& vm) mutable -> JS::ThrowCompletionOr<JS::Value> {
579
            // The event dispatcher should only call this with one argument.
580
0
            VERIFY(vm.argument_count() == 1);
581
582
            // The argument must be an object and it must be an Event.
583
0
            auto event_wrapper_argument = vm.argument(0);
584
0
            VERIFY(event_wrapper_argument.is_object());
585
0
            auto& event = verify_cast<DOM::Event>(event_wrapper_argument.as_object());
586
587
0
            TRY(event_target->process_event_handler_for_event(name, event));
588
0
            return JS::js_undefined();
589
0
        },
590
0
        0, "", &realm);
591
592
    // NOTE: As per the spec, the callback context is arbitrary.
593
0
    auto callback = realm.heap().allocate_without_realm<WebIDL::CallbackType>(*callback_function, Bindings::host_defined_environment_settings_object(realm));
594
595
    // 5. Let listener be a new event listener whose type is the event handler event type corresponding to eventHandler and callback is callback.
596
0
    auto listener = realm.heap().allocate_without_realm<DOMEventListener>();
597
0
    listener->type = name;
598
0
    listener->callback = IDLEventListener::create(realm, *callback);
599
600
    // 6. Add an event listener with eventTarget and listener.
601
0
    add_an_event_listener(*listener);
602
603
    // 7. Set eventHandler's listener to listener.
604
0
    event_handler.listener = listener;
605
0
}
606
607
void EventTarget::deactivate_event_handler(FlyString const& name)
608
0
{
609
    // 1. Let handlerMap be eventTarget's event handler map.
610
0
    auto& handler_map = ensure_data().event_handler_map;
611
612
    // 2. Let eventHandler be handlerMap[name].
613
0
    auto event_handler_iterator = handler_map.find(name);
614
615
    // NOTE: See the optimization comment in get_current_value_of_event_handler about why this is done.
616
0
    if (event_handler_iterator == handler_map.end())
617
0
        return;
618
619
0
    auto& event_handler = event_handler_iterator->value;
620
621
    // 4. Let listener be eventHandler's listener. (NOTE: Not necessary)
622
623
    // 5. If listener is not null, then remove an event listener with eventTarget and listener.
624
0
    if (event_handler->listener) {
625
0
        remove_an_event_listener(*event_handler->listener);
626
0
    }
627
628
    // 6. Set eventHandler's listener to null.
629
0
    event_handler->listener = nullptr;
630
631
    // 3. Set eventHandler's value to null.
632
    // NOTE: This is done out of order since our equivalent of setting value to null is removing the event handler from the map.
633
    //       Given that event_handler is a reference to an entry, this would invalidate event_handler if we did it in order.
634
0
    handler_map.remove(event_handler_iterator);
635
0
}
636
637
// https://html.spec.whatwg.org/multipage/webappapis.html#the-event-handler-processing-algorithm
638
JS::ThrowCompletionOr<void> EventTarget::process_event_handler_for_event(FlyString const& name, Event& event)
639
0
{
640
    // 1. Let callback be the result of getting the current value of the event handler given eventTarget and name.
641
0
    auto* callback = get_current_value_of_event_handler(name);
642
643
    // 2. If callback is null, then return.
644
0
    if (!callback)
645
0
        return {};
646
647
    // 3. Let special error event handling be true if event is an ErrorEvent object, event's type is error, and event's currentTarget implements the WindowOrWorkerGlobalScope mixin.
648
    //    Otherwise, let special error event handling be false.
649
0
    bool special_error_event_handling = is<HTML::ErrorEvent>(event) && event.type() == HTML::EventNames::error && is<HTML::WindowOrWorkerGlobalScopeMixin>(event.current_target().ptr());
650
651
    // 4. Process the Event object event as follows:
652
0
    JS::Completion return_value_or_error;
653
654
0
    if (special_error_event_handling) {
655
        // -> If special error event handling is true
656
        //    Invoke callback with five arguments, the first one having the value of event's message attribute, the second having the value of event's filename attribute, the third having the value of event's lineno attribute,
657
        //    the fourth having the value of event's colno attribute, the fifth having the value of event's error attribute, and with the callback this value set to event's currentTarget.
658
        //    Let return value be the callback's return value. [WEBIDL]
659
0
        auto& error_event = verify_cast<HTML::ErrorEvent>(event);
660
0
        auto wrapped_message = JS::PrimitiveString::create(vm(), error_event.message());
661
0
        auto wrapped_filename = JS::PrimitiveString::create(vm(), error_event.filename());
662
0
        auto wrapped_lineno = JS::Value(error_event.lineno());
663
0
        auto wrapped_colno = JS::Value(error_event.colno());
664
665
        // NOTE: error_event.error() is a JS::Value, so it does not require wrapping.
666
667
        // NOTE: current_target is always non-null here, as the event dispatcher takes care to make sure it's non-null (and uses it as the this value for the callback!)
668
        // FIXME: This is rewrapping the this value of the callback defined in activate_event_handler. While I don't think this is observable as the event dispatcher
669
        //        calls directly into the callback without considering things such as proxies, it is a waste. However, if it observable, then we must reuse the this_value that was given to the callback.
670
0
        auto* this_value = error_event.current_target().ptr();
671
672
0
        return_value_or_error = WebIDL::invoke_callback(*callback, this_value, wrapped_message, wrapped_filename, wrapped_lineno, wrapped_colno, error_event.error());
673
0
    } else {
674
        // -> Otherwise
675
        // Invoke callback with one argument, the value of which is the Event object event, with the callback this value set to event's currentTarget. Let return value be the callback's return value. [WEBIDL]
676
677
        // FIXME: This has the same rewrapping issue as this_value.
678
0
        auto* wrapped_event = &event;
679
680
        // FIXME: The comments about this in the special_error_event_handling path also apply here.
681
0
        auto* this_value = event.current_target().ptr();
682
683
0
        return_value_or_error = WebIDL::invoke_callback(*callback, this_value, wrapped_event);
684
0
    }
685
686
    // If an exception gets thrown by the callback, end these steps and allow the exception to propagate. (It will propagate to the DOM event dispatch logic, which will then report the exception.)
687
0
    if (return_value_or_error.is_error())
688
0
        return return_value_or_error.release_error();
689
690
    // FIXME: Ideally, invoke_callback would convert JS::Value to the appropriate return type for us as per the spec, but it doesn't currently.
691
0
    auto return_value = *return_value_or_error.value();
692
693
    // 5. Process return value as follows:
694
0
    if (is<HTML::BeforeUnloadEvent>(event) && event.type() == "beforeunload") {
695
        // ->  If event is a BeforeUnloadEvent object and event's type is "beforeunload"
696
        //         If return value is not null, then:
697
0
        if (!return_value.is_nullish()) {
698
            // 1. Set event's canceled flag.
699
0
            event.set_cancelled(true);
700
701
            // 2. If event's returnValue attribute's value is the empty string, then set event's returnValue attribute's value to return value.
702
0
            auto& before_unload_event = static_cast<HTML::BeforeUnloadEvent&>(event);
703
0
            if (before_unload_event.return_value().is_empty())
704
0
                before_unload_event.set_return_value(TRY(return_value.to_string(vm())));
705
0
        }
706
0
    }
707
708
0
    if (special_error_event_handling) {
709
        // -> If special error event handling is true
710
        //      If return value is true, then set event's canceled flag.
711
        // NOTE: the return type of EventHandler is `any`, so no coercion happens, meaning we have to check if it's a boolean first.
712
0
        if (return_value.is_boolean() && return_value.as_bool())
713
0
            event.set_cancelled(true);
714
0
    } else {
715
        // -> Otherwise
716
        //      If return value is false, then set event's canceled flag.
717
        // NOTE: the return type of EventHandler is `any`, so no coercion happens, meaning we have to check if it's a boolean first.
718
0
        if (return_value.is_boolean() && !return_value.as_bool())
719
0
            event.set_cancelled(true);
720
0
    }
721
722
0
    return {};
723
0
}
724
725
// https://html.spec.whatwg.org/multipage/webappapis.html#event-handler-attributes:concept-element-attributes-change-ext
726
void EventTarget::element_event_handler_attribute_changed(FlyString const& local_name, Optional<String> const& value)
727
0
{
728
    // NOTE: Step 1 of this algorithm was handled in HTMLElement::attribute_changed.
729
730
    // 2. Let eventTarget be the result of determining the target of an event handler given element and localName.
731
    // NOTE: element is `this`.
732
0
    auto* event_target = determine_target_of_event_handler(*this, local_name);
733
734
    // 3. If eventTarget is null, then return.
735
0
    if (!event_target)
736
0
        return;
737
738
    // 4. If value is null, then deactivate an event handler given eventTarget and localName.
739
0
    if (!value.has_value()) {
740
0
        event_target->deactivate_event_handler(local_name);
741
0
        return;
742
0
    }
743
744
    // 5. Otherwise:
745
    //  FIXME: 1. If the Should element's inline behavior be blocked by Content Security Policy? algorithm returns "Blocked" when executed upon element, "script attribute", and value, then return. [CSP]
746
747
    //  2. Let handlerMap be eventTarget's event handler map.
748
0
    auto& handler_map = event_target->ensure_data().event_handler_map;
749
750
    //  3. Let eventHandler be handlerMap[localName].
751
0
    auto event_handler_iterator = handler_map.find(local_name);
752
753
    //  FIXME: 4. Let location be the script location that triggered the execution of these steps.
754
755
    //  FIXME: 5. Set eventHandler's value to the internal raw uncompiled handler value/location.
756
    //            (This currently sets the value to the uncompiled source code instead of the named struct)
757
758
    // NOTE: See the optimization comments in set_event_handler_attribute.
759
760
0
    if (event_handler_iterator == handler_map.end()) {
761
0
        auto new_event_handler = heap().allocate_without_realm<HTML::EventHandler>(value->to_byte_string());
762
763
        //  6. Activate an event handler given eventTarget and name.
764
0
        event_target->activate_event_handler(local_name, *new_event_handler);
765
766
0
        handler_map.set(local_name, new_event_handler);
767
0
        return;
768
0
    }
769
770
0
    auto& event_handler = event_handler_iterator->value;
771
772
    //  6. Activate an event handler given eventTarget and name.
773
0
    event_handler->value = value->to_byte_string();
774
0
    event_target->activate_event_handler(local_name, *event_handler);
775
0
}
776
777
bool EventTarget::dispatch_event(Event& event)
778
0
{
779
    // https://html.spec.whatwg.org/multipage/interaction.html#activation-triggering-input-event
780
0
    auto is_activation_triggering_input_event = [&]() -> bool {
781
        // An activation triggering input event is any event whose isTrusted attribute is true and whose type is one of:
782
0
        if (!event.is_trusted())
783
0
            return false;
784
785
        // keydown, provided the key is neither the Esc key nor a shortcut key reserved by the user agent.
786
0
        if (event.type() == UIEvents::EventNames::keydown)
787
0
            return static_cast<UIEvents::KeyboardEvent*>(&event)->key_code() != UIEvents::KeyCode::Key_Escape;
788
789
        // mousedown.
790
0
        if (event.type() == UIEvents::EventNames::mousedown)
791
0
            return true;
792
793
        // FIXME:
794
        // pointerdown, provided the event's pointerType is "mouse".
795
        // pointerup, provided the event's pointerType is not "mouse".
796
        // touchend.
797
798
0
        return false;
799
0
    };
800
801
    // https://html.spec.whatwg.org/multipage/interaction.html#user-activation-processing-model
802
    // When a user interaction causes firing of an activation triggering input event in a Document document,
803
    // the user agent must perform the following activation notification steps before dispatching the event:
804
805
    // FIXME: 1. Assert: document is fully active.
806
    // FIXME: 2. Let windows be « document's relevant global object ».
807
    // FIXME: 3. Extend windows with the active window of each of document's ancestor navigables.
808
    // FIXME: 4. Extend windows with the active window of each of document's descendant navigables,
809
    //           filtered to include only those navigables whose active document's origin is same origin with document's origin.
810
    // FIXME: 5. For each window in windows:
811
    // FIXME: 5.1 Set window's last activation timestamp to the current high resolution time.
812
    // FIXME: 5.2 Notify the close watcher manager about user activation given window.
813
814
    // FIXME: This is ad-hoc, but works for now.
815
0
    if (is_activation_triggering_input_event()) {
816
0
        auto unsafe_shared_time = HighResolutionTime::unsafe_shared_current_time();
817
0
        auto current_time = HighResolutionTime::relative_high_resolution_time(unsafe_shared_time, realm().global_object());
818
819
0
        if (is<HTML::Window>(this)) {
820
0
            auto* window = static_cast<HTML::Window*>(this);
821
0
            window->set_last_activation_timestamp(current_time);
822
0
            window->close_watcher_manager()->notify_about_user_activation();
823
0
        } else if (is<DOM::Element>(this)) {
824
0
            auto const* element = static_cast<DOM::Element const*>(this);
825
0
            if (auto window = element->document().window()) {
826
0
                window->set_last_activation_timestamp(current_time);
827
0
                window->close_watcher_manager()->notify_about_user_activation();
828
0
            }
829
0
        }
830
0
    }
831
832
0
    return EventDispatcher::dispatch(*this, event);
833
0
}
834
835
bool EventTarget::has_event_listener(FlyString const& type) const
836
0
{
837
0
    if (!m_data)
838
0
        return false;
839
0
    for (auto& listener : m_data->event_listener_list) {
840
0
        if (listener->type == type)
841
0
            return true;
842
0
    }
843
0
    return false;
844
0
}
845
846
bool EventTarget::has_event_listeners() const
847
0
{
848
0
    return m_data && !m_data->event_listener_list.is_empty();
849
0
}
850
851
bool EventTarget::has_activation_behavior() const
852
0
{
853
0
    return false;
854
0
}
855
856
void EventTarget::activation_behavior(Event const&)
857
0
{
858
0
}
859
860
auto EventTarget::ensure_data() -> Data&
861
0
{
862
0
    if (!m_data)
863
0
        m_data = make<Data>();
864
0
    return *m_data;
865
0
}
866
867
}