diff --git a/Tests/LibWeb/Text/expected/all-window-properties.txt b/Tests/LibWeb/Text/expected/all-window-properties.txt index 235ef988edd..a384a840793 100644 --- a/Tests/LibWeb/Text/expected/all-window-properties.txt +++ b/Tests/LibWeb/Text/expected/all-window-properties.txt @@ -21,6 +21,7 @@ AudioScheduledSourceNode AudioTrack AudioTrackList BaseAudioContext +BeforeUnloadEvent BigInt BigInt64Array BigUint64Array diff --git a/Userland/Libraries/LibWeb/CMakeLists.txt b/Userland/Libraries/LibWeb/CMakeLists.txt index 20c98892ba9..e948a8f5bad 100644 --- a/Userland/Libraries/LibWeb/CMakeLists.txt +++ b/Userland/Libraries/LibWeb/CMakeLists.txt @@ -163,6 +163,7 @@ set(SOURCES DOM/AccessibilityTreeNode.cpp DOM/AdoptedStyleSheets.cpp DOM/Attr.cpp + DOM/BeforeUnloadEvent.cpp DOM/CDATASection.cpp DOM/CharacterData.cpp DOM/Comment.cpp diff --git a/Userland/Libraries/LibWeb/DOM/BeforeUnloadEvent.cpp b/Userland/Libraries/LibWeb/DOM/BeforeUnloadEvent.cpp new file mode 100644 index 00000000000..998f256d11f --- /dev/null +++ b/Userland/Libraries/LibWeb/DOM/BeforeUnloadEvent.cpp @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2024, Tim Ledbetter + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include + +namespace Web::DOM { + +JS_DEFINE_ALLOCATOR(BeforeUnloadEvent); + +JS::NonnullGCPtr BeforeUnloadEvent::create(JS::Realm& realm, FlyString const& event_name, DOM::EventInit const& event_init) +{ + return realm.heap().allocate(realm, realm, event_name, event_init); +} + +BeforeUnloadEvent::BeforeUnloadEvent(JS::Realm& realm, FlyString const& event_name, DOM::EventInit const& event_init) + : DOM::Event(realm, event_name, event_init) +{ +} + +BeforeUnloadEvent::~BeforeUnloadEvent() = default; + +void BeforeUnloadEvent::initialize(JS::Realm& realm) +{ + Base::initialize(realm); + WEB_SET_PROTOTYPE_FOR_INTERFACE(BeforeUnloadEvent); +} + +} diff --git a/Userland/Libraries/LibWeb/DOM/BeforeUnloadEvent.h b/Userland/Libraries/LibWeb/DOM/BeforeUnloadEvent.h new file mode 100644 index 00000000000..7dc554cf7e8 --- /dev/null +++ b/Userland/Libraries/LibWeb/DOM/BeforeUnloadEvent.h @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2024, Tim Ledbetter + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include + +namespace Web::DOM { + +class BeforeUnloadEvent final : public DOM::Event { + WEB_PLATFORM_OBJECT(BeforeUnloadEvent, DOM::Event); + JS_DECLARE_ALLOCATOR(BeforeUnloadEvent); + +public: + [[nodiscard]] static JS::NonnullGCPtr create(JS::Realm&, FlyString const& event_name, DOM::EventInit const& = {}); + + BeforeUnloadEvent(JS::Realm&, FlyString const& event_name, DOM::EventInit const&); + + virtual ~BeforeUnloadEvent() override; + + String const& return_value() const { return m_return_value; } + void set_return_value(String const& return_value) { m_return_value = return_value; } + +private: + virtual void initialize(JS::Realm&) override; + + String m_return_value; +}; + +} diff --git a/Userland/Libraries/LibWeb/DOM/BeforeUnloadEvent.idl b/Userland/Libraries/LibWeb/DOM/BeforeUnloadEvent.idl new file mode 100644 index 00000000000..a7ce14c0836 --- /dev/null +++ b/Userland/Libraries/LibWeb/DOM/BeforeUnloadEvent.idl @@ -0,0 +1,7 @@ +#import + +// https://html.spec.whatwg.org/multipage/nav-history-apis.html#the-beforeunloadevent-interface +[Exposed=Window] +interface BeforeUnloadEvent : Event { + attribute DOMString returnValue; +}; diff --git a/Userland/Libraries/LibWeb/DOM/Document.cpp b/Userland/Libraries/LibWeb/DOM/Document.cpp index 64f55c00fd6..949849becc9 100644 --- a/Userland/Libraries/LibWeb/DOM/Document.cpp +++ b/Userland/Libraries/LibWeb/DOM/Document.cpp @@ -38,6 +38,7 @@ #include #include #include +#include #include #include #include @@ -1746,7 +1747,7 @@ WebIDL::ExceptionOr> Document::create_event(StringView i // 2. If interface is an ASCII case-insensitive match for any of the strings in the first column in the following table, // then set constructor to the interface in the second column on the same row as the matching string: if (Infra::is_ascii_case_insensitive_match(interface, "beforeunloadevent"sv)) { - event = Event::create(realm, FlyString {}); // FIXME: Create BeforeUnloadEvent + event = BeforeUnloadEvent::create(realm, FlyString {}); } else if (Infra::is_ascii_case_insensitive_match(interface, "compositionevent"sv)) { event = Event::create(realm, FlyString {}); // FIXME: Create CompositionEvent } else if (Infra::is_ascii_case_insensitive_match(interface, "customevent"sv)) { diff --git a/Userland/Libraries/LibWeb/DOM/EventTarget.cpp b/Userland/Libraries/LibWeb/DOM/EventTarget.cpp index f5069856c0c..8b8a2ff29ae 100644 --- a/Userland/Libraries/LibWeb/DOM/EventTarget.cpp +++ b/Userland/Libraries/LibWeb/DOM/EventTarget.cpp @@ -16,6 +16,7 @@ #include #include #include +#include #include #include #include @@ -690,10 +691,20 @@ JS::ThrowCompletionOr EventTarget::process_event_handler_for_event(FlyStri // FIXME: Ideally, invoke_callback would convert JS::Value to the appropriate return type for us as per the spec, but it doesn't currently. auto return_value = *return_value_or_error.value(); - // FIXME: If event is a BeforeUnloadEvent object and event's type is beforeunload - // 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) - // 1. Set event's canceled flag. - // 2. If event's returnValue attribute's value is the empty string, then set event's returnValue attribute's value to return value. + // 5. Process return value as follows: + if (is(event) && event.type() == "beforeunload") { + // -> If event is a BeforeUnloadEvent object and event's type is "beforeunload" + // If return value is not null, then: + if (!return_value.is_nullish()) { + // 1. Set event's canceled flag. + event.set_cancelled(true); + + // 2. If event's returnValue attribute's value is the empty string, then set event's returnValue attribute's value to return value. + auto& before_unload_event = static_cast(event); + if (before_unload_event.return_value().is_empty()) + before_unload_event.set_return_value(TRY(return_value.to_string(vm()))); + } + } if (special_error_event_handling) { // -> If special error event handling is true diff --git a/Userland/Libraries/LibWeb/Forward.h b/Userland/Libraries/LibWeb/Forward.h index cf565a45914..0154fb1610c 100644 --- a/Userland/Libraries/LibWeb/Forward.h +++ b/Userland/Libraries/LibWeb/Forward.h @@ -244,6 +244,7 @@ class AbortSignal; class AbstractRange; class AccessibilityTreeNode; class Attr; +class BeforeUnloadEvent; class CDATASection; class CharacterData; class Comment; diff --git a/Userland/Libraries/LibWeb/idl_files.cmake b/Userland/Libraries/LibWeb/idl_files.cmake index edd9fa7d4fd..2e1f8feb0de 100644 --- a/Userland/Libraries/LibWeb/idl_files.cmake +++ b/Userland/Libraries/LibWeb/idl_files.cmake @@ -46,6 +46,7 @@ libweb_js_bindings(DOM/AbstractRange) libweb_js_bindings(DOM/Attr) libweb_js_bindings(DOM/AbortController) libweb_js_bindings(DOM/AbortSignal) +libweb_js_bindings(DOM/BeforeUnloadEvent) libweb_js_bindings(DOM/CDATASection) libweb_js_bindings(DOM/CharacterData) libweb_js_bindings(DOM/Comment)