Serenity Operating System
at master 752 lines 36 kB view raw
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/Interpreter.h> 10#include <LibJS/Parser.h> 11#include <LibJS/Runtime/AbstractOperations.h> 12#include <LibJS/Runtime/ECMAScriptFunctionObject.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/ErrorEvent.h> 26#include <LibWeb/HTML/EventHandler.h> 27#include <LibWeb/HTML/EventNames.h> 28#include <LibWeb/HTML/FormAssociatedElement.h> 29#include <LibWeb/HTML/HTMLBodyElement.h> 30#include <LibWeb/HTML/HTMLFormElement.h> 31#include <LibWeb/HTML/HTMLFrameSetElement.h> 32#include <LibWeb/HTML/Window.h> 33#include <LibWeb/UIEvents/EventNames.h> 34#include <LibWeb/WebIDL/AbstractOperations.h> 35 36namespace Web::DOM { 37 38EventTarget::EventTarget(JS::Realm& realm) 39 : PlatformObject(realm) 40{ 41} 42 43EventTarget::~EventTarget() = default; 44 45void EventTarget::visit_edges(Cell::Visitor& visitor) 46{ 47 Base::visit_edges(visitor); 48 49 for (auto& event_listener : m_event_listener_list) 50 visitor.visit(event_listener.ptr()); 51 52 for (auto& it : m_event_handler_map) 53 visitor.visit(it.value.ptr()); 54} 55 56Vector<JS::Handle<DOMEventListener>> EventTarget::event_listener_list() 57{ 58 Vector<JS::Handle<DOMEventListener>> list; 59 for (auto& listener : m_event_listener_list) 60 list.append(*listener); 61 return list; 62} 63 64// https://dom.spec.whatwg.org/#concept-flatten-options 65static bool flatten_event_listener_options(Variant<EventListenerOptions, bool> const& options) 66{ 67 // 1. If options is a boolean, then return options. 68 if (options.has<bool>()) 69 return options.get<bool>(); 70 71 // 2. Return options["capture"]. 72 return options.get<EventListenerOptions>().capture; 73} 74 75static bool flatten_event_listener_options(Variant<AddEventListenerOptions, bool> const& options) 76{ 77 // 1. If options is a boolean, then return options. 78 if (options.has<bool>()) 79 return options.get<bool>(); 80 81 // 2. Return options["capture"]. 82 return options.get<AddEventListenerOptions>().capture; 83} 84 85struct FlattenedAddEventListenerOptions { 86 bool capture { false }; 87 bool passive { false }; 88 bool once { false }; 89 JS::GCPtr<AbortSignal> signal; 90}; 91 92// https://dom.spec.whatwg.org/#event-flatten-more 93static FlattenedAddEventListenerOptions flatten_add_event_listener_options(Variant<AddEventListenerOptions, bool> const& options) 94{ 95 // 1. Let capture be the result of flattening options. 96 bool capture = flatten_event_listener_options(options); 97 98 // 2. Let once and passive be false. 99 bool once = false; 100 bool passive = false; 101 102 // 3. Let signal be null. 103 JS::GCPtr<AbortSignal> signal; 104 105 // 4. If options is a dictionary, then: 106 if (options.has<AddEventListenerOptions>()) { 107 auto& add_event_listener_options = options.get<AddEventListenerOptions>(); 108 109 // 1. Set passive to options["passive"] and once to options["once"]. 110 passive = add_event_listener_options.passive; 111 once = add_event_listener_options.once; 112 113 // 2. If options["signal"] exists, then set signal to options["signal"]. 114 if (add_event_listener_options.signal.has_value()) 115 signal = add_event_listener_options.signal.value().ptr(); 116 } 117 118 // 5. Return capture, passive, once, and signal. 119 return FlattenedAddEventListenerOptions { .capture = capture, .passive = passive, .once = once, .signal = signal.ptr() }; 120} 121 122// https://dom.spec.whatwg.org/#dom-eventtarget-addeventlistener 123void EventTarget::add_event_listener(DeprecatedFlyString const& type, IDLEventListener* callback, Variant<AddEventListenerOptions, bool> const& options) 124{ 125 // 1. Let capture, passive, once, and signal be the result of flattening more options. 126 auto flattened_options = flatten_add_event_listener_options(options); 127 128 // 2. Add an event listener with this and an event listener whose type is type, callback is callback, capture is capture, passive is passive, 129 // once is once, and signal is signal. 130 131 auto event_listener = heap().allocate_without_realm<DOMEventListener>(); 132 event_listener->type = type; 133 event_listener->callback = callback; 134 event_listener->signal = move(flattened_options.signal); 135 event_listener->capture = flattened_options.capture; 136 event_listener->passive = flattened_options.passive; 137 event_listener->once = flattened_options.once; 138 add_an_event_listener(*event_listener); 139} 140 141void EventTarget::add_event_listener_without_options(DeprecatedFlyString const& type, IDLEventListener& callback) 142{ 143 add_event_listener(type, &callback, AddEventListenerOptions {}); 144} 145 146// https://dom.spec.whatwg.org/#add-an-event-listener 147void EventTarget::add_an_event_listener(DOMEventListener& listener) 148{ 149 // FIXME: 1. If eventTarget is a ServiceWorkerGlobalScope object, its service worker’s script resource’s has ever been evaluated flag is set, 150 // and listener’s type matches the type attribute value of any of the service worker events, then report a warning to the console 151 // that this might not give the expected results. [SERVICE-WORKERS] 152 153 // 2. If listener’s signal is not null and is aborted, then return. 154 if (listener.signal && listener.signal->aborted()) 155 return; 156 157 // 3. If listener’s callback is null, then return. 158 if (!listener.callback) 159 return; 160 161 // 4. If eventTarget’s event listener list does not contain an event listener whose type is listener’s type, callback is listener’s callback, 162 // and capture is listener’s capture, then append listener to eventTarget’s event listener list. 163 auto it = m_event_listener_list.find_if([&](auto& entry) { 164 return entry->type == listener.type 165 && &entry->callback->callback().callback == &listener.callback->callback().callback 166 && entry->capture == listener.capture; 167 }); 168 if (it == m_event_listener_list.end()) 169 m_event_listener_list.append(listener); 170 171 // 5. If listener’s signal is not null, then add the following abort steps to it: 172 if (listener.signal) { 173 // NOTE: `this` and `listener` are protected by AbortSignal using JS::SafeFunction. 174 listener.signal->add_abort_algorithm([this, &listener] { 175 // 1. Remove an event listener with eventTarget and listener. 176 remove_an_event_listener(listener); 177 }); 178 } 179} 180 181// https://dom.spec.whatwg.org/#dom-eventtarget-removeeventlistener 182void EventTarget::remove_event_listener(DeprecatedFlyString const& type, IDLEventListener* callback, Variant<EventListenerOptions, bool> const& options) 183{ 184 // 1. Let capture be the result of flattening options. 185 bool capture = flatten_event_listener_options(options); 186 187 // 2. If this’s event listener list contains an event listener whose type is type, callback is callback, and capture is capture, 188 // then remove an event listener with this and that event listener. 189 auto callbacks_match = [&](DOMEventListener& entry) { 190 if (!entry.callback && !callback) 191 return true; 192 if (!entry.callback || !callback) 193 return false; 194 return &entry.callback->callback().callback == &callback->callback().callback; 195 }; 196 auto it = m_event_listener_list.find_if([&](auto& entry) { 197 return entry->type == type 198 && callbacks_match(*entry) 199 && entry->capture == capture; 200 }); 201 if (it != m_event_listener_list.end()) 202 remove_an_event_listener(**it); 203} 204 205void EventTarget::remove_event_listener_without_options(DeprecatedFlyString const& type, IDLEventListener& callback) 206{ 207 remove_event_listener(type, &callback, EventListenerOptions {}); 208} 209 210// https://dom.spec.whatwg.org/#remove-an-event-listener 211void EventTarget::remove_an_event_listener(DOMEventListener& listener) 212{ 213 // FIXME: 1. If eventTarget is a ServiceWorkerGlobalScope object and its service worker’s set of event types to handle contains type, 214 // then report a warning to the console that this might not give the expected results. [SERVICE-WORKERS] 215 216 // 2. Set listener’s removed to true and remove listener from eventTarget’s event listener list. 217 listener.removed = true; 218 m_event_listener_list.remove_first_matching([&](auto& entry) { return entry.ptr() == &listener; }); 219} 220 221void EventTarget::remove_from_event_listener_list(DOMEventListener& listener) 222{ 223 m_event_listener_list.remove_first_matching([&](auto& entry) { return entry.ptr() == &listener; }); 224} 225 226// https://dom.spec.whatwg.org/#dom-eventtarget-dispatchevent 227WebIDL::ExceptionOr<bool> EventTarget::dispatch_event_binding(Event& event) 228{ 229 // 1. If event’s dispatch flag is set, or if its initialized flag is not set, then throw an "InvalidStateError" DOMException. 230 if (event.dispatched()) 231 return WebIDL::InvalidStateError::create(realm(), "The event is already being dispatched."); 232 233 if (!event.initialized()) 234 return WebIDL::InvalidStateError::create(realm(), "Cannot dispatch an uninitialized event."); 235 236 // 2. Initialize event’s isTrusted attribute to false. 237 event.set_is_trusted(false); 238 239 // 3. Return the result of dispatching event to this. 240 return dispatch_event(event); 241} 242 243// https://html.spec.whatwg.org/multipage/webappapis.html#window-reflecting-body-element-event-handler-set 244bool is_window_reflecting_body_element_event_handler(DeprecatedFlyString const& name) 245{ 246 return name.is_one_of( 247 HTML::EventNames::blur, 248 HTML::EventNames::error, 249 HTML::EventNames::focus, 250 HTML::EventNames::load, 251 UIEvents::EventNames::resize, 252 "scroll"); 253} 254 255// https://html.spec.whatwg.org/multipage/webappapis.html#windoweventhandlers 256static bool is_window_event_handler(DeprecatedFlyString const& name) 257{ 258 return name.is_one_of( 259 HTML::EventNames::afterprint, 260 HTML::EventNames::beforeprint, 261 HTML::EventNames::beforeunload, 262 HTML::EventNames::hashchange, 263 HTML::EventNames::languagechange, 264 HTML::EventNames::message, 265 HTML::EventNames::messageerror, 266 HTML::EventNames::offline, 267 HTML::EventNames::online, 268 HTML::EventNames::pagehide, 269 HTML::EventNames::pageshow, 270 HTML::EventNames::popstate, 271 HTML::EventNames::rejectionhandled, 272 HTML::EventNames::storage, 273 HTML::EventNames::unhandledrejection, 274 HTML::EventNames::unload); 275} 276 277// https://html.spec.whatwg.org/multipage/webappapis.html#determining-the-target-of-an-event-handler 278static EventTarget* determine_target_of_event_handler(EventTarget& event_target, DeprecatedFlyString const& name) 279{ 280 // To determine the target of an event handler, given an EventTarget object eventTarget on which the event handler is exposed, 281 // and an event handler name name, the following steps are taken: 282 283 // 1. If eventTarget is not a body element or a frameset element, then return eventTarget. 284 if (!is<HTML::HTMLBodyElement>(event_target) && !is<HTML::HTMLFrameSetElement>(event_target)) 285 return &event_target; 286 287 auto& event_target_element = static_cast<HTML::HTMLElement&>(event_target); 288 289 // 2. If name is not the name of an attribute member of the WindowEventHandlers interface mixin and the Window-reflecting 290 // body element event handler set does not contain name, then return eventTarget. 291 if (!is_window_event_handler(name) && !is_window_reflecting_body_element_event_handler(name)) 292 return &event_target; 293 294 // 3. If eventTarget's node document is not an active document, then return null. 295 if (!event_target_element.document().is_active()) 296 return nullptr; 297 298 // 4. Return eventTarget's node document's relevant global object. 299 return &event_target_element.document().window(); 300} 301 302// https://html.spec.whatwg.org/multipage/webappapis.html#event-handler-attributes:event-handler-idl-attributes-2 303WebIDL::CallbackType* EventTarget::event_handler_attribute(DeprecatedFlyString const& name) 304{ 305 // 1. Let eventTarget be the result of determining the target of an event handler given this object and name. 306 auto target = determine_target_of_event_handler(*this, name); 307 308 // 2. If eventTarget is null, then return null. 309 if (!target) 310 return nullptr; 311 312 // 3. Return the result of getting the current value of the event handler given eventTarget and name. 313 return target->get_current_value_of_event_handler(name); 314} 315 316// https://html.spec.whatwg.org/multipage/webappapis.html#getting-the-current-value-of-the-event-handler 317WebIDL::CallbackType* EventTarget::get_current_value_of_event_handler(DeprecatedFlyString const& name) 318{ 319 // 1. Let handlerMap be eventTarget's event handler map. (NOTE: Not necessary) 320 321 // 2. Let eventHandler be handlerMap[name]. 322 auto event_handler_iterator = m_event_handler_map.find(name); 323 324 // 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. 325 // If the event handler hasn't been set, null would be returned in step 4. 326 // However, this would be very allocation heavy. For example, each DOM::Element includes GlobalEventHandlers, which defines 60+(!) event handler attributes. 327 // 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. 328 // 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. 329 if (event_handler_iterator == m_event_handler_map.end()) 330 return nullptr; 331 332 auto& event_handler = event_handler_iterator->value; 333 334 // 3. If eventHandler's value is an internal raw uncompiled handler, then: 335 if (event_handler->value.has<DeprecatedString>()) { 336 // 1. If eventTarget is an element, then let element be eventTarget, and document be element's node document. 337 // Otherwise, eventTarget is a Window object, let element be null, and document be eventTarget's associated Document. 338 JS::GCPtr<Element> element; 339 JS::GCPtr<Document> document; 340 341 if (is<Element>(this)) { 342 auto* element_event_target = verify_cast<Element>(this); 343 element = element_event_target; 344 document = &element_event_target->document(); 345 } else { 346 VERIFY(is<HTML::Window>(this)); 347 auto* window_event_target = verify_cast<HTML::Window>(this); 348 document = &window_event_target->associated_document(); 349 } 350 351 VERIFY(document); 352 353 // 2. If scripting is disabled for document, then return null. 354 if (document->is_scripting_disabled()) 355 return nullptr; 356 357 // 3. Let body be the uncompiled script body in eventHandler's value. 358 auto& body = event_handler->value.get<DeprecatedString>(); 359 360 // FIXME: 4. Let location be the location where the script body originated, as given by eventHandler's value. 361 362 // 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. 363 JS::GCPtr<HTML::HTMLFormElement> form_owner; 364 if (is<HTML::FormAssociatedElement>(element.ptr())) { 365 auto* form_associated_element = dynamic_cast<HTML::FormAssociatedElement*>(element.ptr()); 366 VERIFY(form_associated_element); 367 368 if (form_associated_element->form()) 369 form_owner = form_associated_element->form(); 370 } 371 372 // 6. Let settings object be the relevant settings object of document. 373 auto& settings_object = document->relevant_settings_object(); 374 375 // NOTE: ECMAScriptFunctionObject::create expects a parsed body as input, so we must do the spec's sourceText steps here. 376 StringBuilder builder; 377 378 // sourceText 379 if (name == HTML::EventNames::error && is<HTML::Window>(this)) { 380 // -> If name is onerror and eventTarget is a Window object 381 // The string formed by concatenating "function ", name, "(event, source, lineno, colno, error) {", U+000A LF, body, U+000A LF, and "}". 382 builder.appendff("function {}(event, source, lineno, colno, error) {{\n{}\n}}", name, body); 383 } else { 384 // -> Otherwise 385 // The string formed by concatenating "function ", name, "(event) {", U+000A LF, body, U+000A LF, and "}". 386 builder.appendff("function {}(event) {{\n{}\n}}", name, body); 387 } 388 389 auto source_text = builder.to_deprecated_string(); 390 391 auto parser = JS::Parser(JS::Lexer(source_text)); 392 393 // FIXME: This should only be parsing the `body` instead of `source_text` and therefore use `JS::FunctionBody` instead of `JS::FunctionExpression`. 394 // However, JS::ECMAScriptFunctionObject::create wants parameters and length and JS::FunctionBody does not inherit JS::FunctionNode. 395 auto program = parser.parse_function_node<JS::FunctionExpression>(); 396 397 // 7. If body is not parsable as FunctionBody or if parsing detects an early error, then follow these substeps: 398 if (parser.has_errors()) { 399 // 1. Set eventHandler's value to null. 400 // Note: This does not deactivate the event handler, which additionally removes the event handler's listener (if present). 401 m_event_handler_map.remove(event_handler_iterator); 402 403 // 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. 404 // If the error is still not handled after this, then the error may be reported to a developer console. 405 406 // 3. Return null. 407 return nullptr; 408 } 409 410 auto& vm = Bindings::main_thread_vm(); 411 412 // 8. Push settings object's realm execution context onto the JavaScript execution context stack; it is now the running JavaScript execution context. 413 vm.push_execution_context(settings_object.realm_execution_context()); 414 415 // 9. Let function be the result of calling OrdinaryFunctionCreate, with arguments: 416 // functionPrototype 417 // %Function.prototype% (This is enforced by using JS::ECMAScriptFunctionObject) 418 419 // sourceText was handled above. 420 421 // ParameterList 422 // If name is onerror and eventTarget is a Window object 423 // Let the function have five arguments, named event, source, lineno, colno, and error. 424 // Otherwise 425 // Let the function have a single argument called event. 426 // (This was handled above for us by the parser using sourceText) 427 428 // body 429 // The result of parsing body above. (This is given by program->body()) 430 431 // thisMode 432 // non-lexical-this (For JS::ECMAScriptFunctionObject, this means passing is_arrow_function as false) 433 constexpr bool is_arrow_function = false; 434 435 // scope 436 // 1. Let realm be settings object's Realm. 437 auto& realm = settings_object.realm(); 438 439 // 2. Let scope be realm.[[GlobalEnv]]. 440 auto scope = JS::NonnullGCPtr<JS::Environment> { realm.global_environment() }; 441 442 // 3. If eventHandler is an element's event handler, then set scope to NewObjectEnvironment(document, true, scope). 443 // (Otherwise, eventHandler is a Window object's event handler.) 444 if (is<Element>(this)) 445 scope = JS::new_object_environment(*document, true, scope); 446 447 // 4. If form owner is not null, then set scope to NewObjectEnvironment(form owner, true, scope). 448 if (form_owner) 449 scope = JS::new_object_environment(*form_owner, true, scope); 450 451 // 5. If element is not null, then set scope to NewObjectEnvironment(element, true, scope). 452 if (element) 453 scope = JS::new_object_environment(*element, true, scope); 454 455 // 6. Return scope. (NOTE: Not necessary) 456 457 auto function = JS::ECMAScriptFunctionObject::create(realm, name, builder.to_deprecated_string(), program->body(), program->parameters(), program->function_length(), scope, nullptr, JS::FunctionKind::Normal, program->is_strict_mode(), program->might_need_arguments_object(), is_arrow_function); 458 459 // 10. Remove settings object's realm execution context from the JavaScript execution context stack. 460 VERIFY(vm.execution_context_stack().last() == &settings_object.realm_execution_context()); 461 vm.pop_execution_context(); 462 463 // 11. Set function.[[ScriptOrModule]] to null. 464 function->set_script_or_module({}); 465 466 // 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. 467 event_handler->value = realm.heap().allocate_without_realm<WebIDL::CallbackType>(*function, settings_object).ptr(); 468 } 469 470 // 4. Return eventHandler's value. 471 VERIFY(event_handler->value.has<WebIDL::CallbackType*>()); 472 return *event_handler->value.get_pointer<WebIDL::CallbackType*>(); 473} 474 475// https://html.spec.whatwg.org/multipage/webappapis.html#event-handler-attributes:event-handler-idl-attributes-3 476void EventTarget::set_event_handler_attribute(DeprecatedFlyString const& name, WebIDL::CallbackType* value) 477{ 478 // 1. Let eventTarget be the result of determining the target of an event handler given this object and name. 479 auto event_target = determine_target_of_event_handler(*this, name); 480 481 // 2. If eventTarget is null, then return. 482 if (!event_target) 483 return; 484 485 // 3. If the given value is null, then deactivate an event handler given eventTarget and name. 486 if (!value) { 487 event_target->deactivate_event_handler(name); 488 return; 489 } 490 491 // 4. Otherwise: 492 // 1. Let handlerMap be eventTarget's event handler map. 493 auto& handler_map = event_target->m_event_handler_map; 494 495 // 2. Let eventHandler be handlerMap[name]. 496 auto event_handler_iterator = handler_map.find(name); 497 498 // 3. Set eventHandler's value to the given value. 499 if (event_handler_iterator == handler_map.end()) { 500 // NOTE: See the optimization comment in get_current_value_of_event_handler about why this is done. 501 auto new_event_handler = heap().allocate_without_realm<HTML::EventHandler>(*value); 502 503 // 4. Activate an event handler given eventTarget and name. 504 // 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. 505 // This handles a new event handler while the other path handles an existing event handler. As such, both paths must have their own 506 // unique call to activate_event_handler. 507 event_target->activate_event_handler(name, *new_event_handler); 508 509 handler_map.set(name, new_event_handler); 510 return; 511 } 512 513 auto& event_handler = event_handler_iterator->value; 514 515 event_handler->value = value; 516 517 // 4. Activate an event handler given eventTarget and name. 518 // NOTE: See the optimization comment above. 519 event_target->activate_event_handler(name, *event_handler); 520} 521 522// https://html.spec.whatwg.org/multipage/webappapis.html#activate-an-event-handler 523void EventTarget::activate_event_handler(DeprecatedFlyString const& name, HTML::EventHandler& event_handler) 524{ 525 // 1. Let handlerMap be eventTarget's event handler map. 526 // 2. Let eventHandler be handlerMap[name]. 527 // NOTE: These are achieved by using the passed in event handler. 528 529 // 3. If eventHandler's listener is not null, then return. 530 if (event_handler.listener) 531 return; 532 533 JS::Realm& realm = shape().realm(); 534 535 // 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. 536 // The EventListener's callback context can be arbitrary; it does not impact the steps of the event handler processing algorithm. [DOM] 537 538 // NOTE: The callback must keep `this` alive. For example: 539 // document.body.onunload = () => { console.log("onunload called!"); } 540 // document.body.remove(); 541 // location.reload(); 542 // 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. 543 auto callback_function = JS::NativeFunction::create( 544 realm, [event_target = JS::make_handle(*this), name](JS::VM& vm) mutable -> JS::ThrowCompletionOr<JS::Value> { 545 // The event dispatcher should only call this with one argument. 546 VERIFY(vm.argument_count() == 1); 547 548 // The argument must be an object and it must be an Event. 549 auto event_wrapper_argument = vm.argument(0); 550 VERIFY(event_wrapper_argument.is_object()); 551 auto& event = verify_cast<DOM::Event>(event_wrapper_argument.as_object()); 552 553 TRY(event_target->process_event_handler_for_event(name, event)); 554 return JS::js_undefined(); 555 }, 556 0, "", &realm); 557 558 // NOTE: As per the spec, the callback context is arbitrary. 559 auto callback = realm.heap().allocate_without_realm<WebIDL::CallbackType>(*callback_function, Bindings::host_defined_environment_settings_object(realm)); 560 561 // 5. Let listener be a new event listener whose type is the event handler event type corresponding to eventHandler and callback is callback. 562 auto listener = realm.heap().allocate_without_realm<DOMEventListener>(); 563 listener->type = name; 564 listener->callback = IDLEventListener::create(realm, *callback).release_value_but_fixme_should_propagate_errors(); 565 566 // 6. Add an event listener with eventTarget and listener. 567 add_an_event_listener(*listener); 568 569 // 7. Set eventHandler's listener to listener. 570 event_handler.listener = listener; 571} 572 573void EventTarget::deactivate_event_handler(DeprecatedFlyString const& name) 574{ 575 // 1. Let handlerMap be eventTarget's event handler map. (NOTE: Not necessary) 576 577 // 2. Let eventHandler be handlerMap[name]. 578 auto event_handler_iterator = m_event_handler_map.find(name); 579 580 // NOTE: See the optimization comment in get_current_value_of_event_handler about why this is done. 581 if (event_handler_iterator == m_event_handler_map.end()) 582 return; 583 584 auto& event_handler = event_handler_iterator->value; 585 586 // 4. Let listener be eventHandler's listener. (NOTE: Not necessary) 587 588 // 5. If listener is not null, then remove an event listener with eventTarget and listener. 589 if (event_handler->listener) { 590 remove_an_event_listener(*event_handler->listener); 591 } 592 593 // 6. Set eventHandler's listener to null. 594 event_handler->listener = nullptr; 595 596 // 3. Set eventHandler's value to null. 597 // NOTE: This is done out of order since our equivalent of setting value to null is removing the event handler from the map. 598 // Given that event_handler is a reference to an entry, this would invalidate event_handler if we did it in order. 599 m_event_handler_map.remove(event_handler_iterator); 600} 601 602// https://html.spec.whatwg.org/multipage/webappapis.html#the-event-handler-processing-algorithm 603JS::ThrowCompletionOr<void> EventTarget::process_event_handler_for_event(DeprecatedFlyString const& name, Event& event) 604{ 605 // 1. Let callback be the result of getting the current value of the event handler given eventTarget and name. 606 auto* callback = get_current_value_of_event_handler(name); 607 608 // 2. If callback is null, then return. 609 if (!callback) 610 return {}; 611 612 // 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. 613 // Otherwise, let special error event handling be false. 614 // FIXME: This doesn't check for WorkerGlobalScape as we don't currently have it. 615 bool special_error_event_handling = is<HTML::ErrorEvent>(event) && event.type() == HTML::EventNames::error && is<HTML::Window>(event.current_target().ptr()); 616 617 // 4. Process the Event object event as follows: 618 JS::Completion return_value_or_error; 619 620 if (special_error_event_handling) { 621 // -> If special error event handling is true 622 // 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, 623 // 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. 624 // Let return value be the callback's return value. [WEBIDL] 625 auto& error_event = verify_cast<HTML::ErrorEvent>(event); 626 auto wrapped_message = JS::PrimitiveString::create(vm(), error_event.message()); 627 auto wrapped_filename = JS::PrimitiveString::create(vm(), error_event.filename()); 628 auto wrapped_lineno = JS::Value(error_event.lineno()); 629 auto wrapped_colno = JS::Value(error_event.colno()); 630 631 // NOTE: error_event.error() is a JS::Value, so it does not require wrapping. 632 633 // 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!) 634 // 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 635 // 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. 636 auto* this_value = error_event.current_target().ptr(); 637 638 return_value_or_error = WebIDL::invoke_callback(*callback, this_value, wrapped_message, wrapped_filename, wrapped_lineno, wrapped_colno, error_event.error()); 639 } else { 640 // -> Otherwise 641 // 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] 642 643 // FIXME: This has the same rewrapping issue as this_value. 644 auto* wrapped_event = &event; 645 646 // FIXME: The comments about this in the special_error_event_handling path also apply here. 647 auto* this_value = event.current_target().ptr(); 648 649 return_value_or_error = WebIDL::invoke_callback(*callback, this_value, wrapped_event); 650 } 651 652 // 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.) 653 if (return_value_or_error.is_error()) 654 return return_value_or_error.release_error(); 655 656 // FIXME: Ideally, invoke_callback would convert JS::Value to the appropriate return type for us as per the spec, but it doesn't currently. 657 auto return_value = *return_value_or_error.value(); 658 659 // FIXME: If event is a BeforeUnloadEvent object and event's type is beforeunload 660 // If return value is not null, then: (NOTE: When implementing, if we still return a JS::Value from invoke_callback, use is_nullish instead of is_null, as "null" refers to IDL null, which is JS null or undefined) 661 // 1. Set event's canceled flag. 662 // 2. If event's returnValue attribute's value is the empty string, then set event's returnValue attribute's value to return value. 663 664 if (special_error_event_handling) { 665 // -> If special error event handling is true 666 // If return value is true, then set event's canceled flag. 667 // NOTE: the return type of EventHandler is `any`, so no coercion happens, meaning we have to check if it's a boolean first. 668 if (return_value.is_boolean() && return_value.as_bool()) 669 event.set_cancelled(true); 670 } else { 671 // -> Otherwise 672 // If return value is false, then set event's canceled flag. 673 // NOTE: the return type of EventHandler is `any`, so no coercion happens, meaning we have to check if it's a boolean first. 674 if (return_value.is_boolean() && !return_value.as_bool()) 675 event.set_cancelled(true); 676 } 677 678 return {}; 679} 680 681// https://html.spec.whatwg.org/multipage/webappapis.html#event-handler-attributes:concept-element-attributes-change-ext 682void EventTarget::element_event_handler_attribute_changed(DeprecatedFlyString const& local_name, DeprecatedString const& value) 683{ 684 // NOTE: Step 1 of this algorithm was handled in HTMLElement::parse_attribute. 685 686 // 2. Let eventTarget be the result of determining the target of an event handler given element and localName. 687 // NOTE: element is `this`. 688 auto* event_target = determine_target_of_event_handler(*this, local_name); 689 690 // 3. If eventTarget is null, then return. 691 if (!event_target) 692 return; 693 694 // 4. If value is null, then deactivate an event handler given eventTarget and localName. 695 if (value.is_null()) { 696 event_target->deactivate_event_handler(local_name); 697 return; 698 } 699 700 // 5. Otherwise: 701 // 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] 702 703 // 2. Let handlerMap be eventTarget's event handler map. 704 auto& handler_map = event_target->m_event_handler_map; 705 706 // 3. Let eventHandler be handlerMap[localName]. 707 auto event_handler_iterator = handler_map.find(local_name); 708 709 // FIXME: 4. Let location be the script location that triggered the execution of these steps. 710 711 // FIXME: 5. Set eventHandler's value to the internal raw uncompiled handler value/location. 712 // (This currently sets the value to the uncompiled source code instead of the named struct) 713 714 // NOTE: See the optimization comments in set_event_handler_attribute. 715 716 if (event_handler_iterator == handler_map.end()) { 717 auto new_event_handler = heap().allocate_without_realm<HTML::EventHandler>(value); 718 719 // 6. Activate an event handler given eventTarget and name. 720 event_target->activate_event_handler(local_name, *new_event_handler); 721 722 handler_map.set(local_name, new_event_handler); 723 return; 724 } 725 726 auto& event_handler = event_handler_iterator->value; 727 728 // 6. Activate an event handler given eventTarget and name. 729 event_handler->value = value; 730 event_target->activate_event_handler(local_name, *event_handler); 731} 732 733bool EventTarget::dispatch_event(Event& event) 734{ 735 return EventDispatcher::dispatch(*this, event); 736} 737 738bool EventTarget::has_event_listener(DeprecatedFlyString const& type) const 739{ 740 for (auto& listener : m_event_listener_list) { 741 if (listener->type == type) 742 return true; 743 } 744 return false; 745} 746 747bool EventTarget::has_event_listeners() const 748{ 749 return !m_event_listener_list.is_empty(); 750} 751 752}