浏览代码

LibWeb: Implement `BeforeUnloadEvent`

This is required to support legacy behavior of the `returnValue`
attribute.
Tim Ledbetter 10 月之前
父节点
当前提交
99ef078c97

+ 1 - 0
Tests/LibWeb/Text/expected/all-window-properties.txt

@@ -21,6 +21,7 @@ AudioScheduledSourceNode
 AudioTrack
 AudioTrackList
 BaseAudioContext
+BeforeUnloadEvent
 BigInt
 BigInt64Array
 BigUint64Array

+ 1 - 0
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

+ 33 - 0
Userland/Libraries/LibWeb/DOM/BeforeUnloadEvent.cpp

@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) 2024, Tim Ledbetter <tim.ledbetter@ladybird.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <LibWeb/Bindings/BeforeUnloadEventPrototype.h>
+#include <LibWeb/Bindings/Intrinsics.h>
+#include <LibWeb/DOM/BeforeUnloadEvent.h>
+
+namespace Web::DOM {
+
+JS_DEFINE_ALLOCATOR(BeforeUnloadEvent);
+
+JS::NonnullGCPtr<BeforeUnloadEvent> BeforeUnloadEvent::create(JS::Realm& realm, FlyString const& event_name, DOM::EventInit const& event_init)
+{
+    return realm.heap().allocate<BeforeUnloadEvent>(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);
+}
+
+}

+ 34 - 0
Userland/Libraries/LibWeb/DOM/BeforeUnloadEvent.h

@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2024, Tim Ledbetter <tim.ledbetter@ladybird.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#pragma once
+
+#include <AK/FlyString.h>
+#include <LibWeb/DOM/Event.h>
+
+namespace Web::DOM {
+
+class BeforeUnloadEvent final : public DOM::Event {
+    WEB_PLATFORM_OBJECT(BeforeUnloadEvent, DOM::Event);
+    JS_DECLARE_ALLOCATOR(BeforeUnloadEvent);
+
+public:
+    [[nodiscard]] static JS::NonnullGCPtr<BeforeUnloadEvent> 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;
+};
+
+}

+ 7 - 0
Userland/Libraries/LibWeb/DOM/BeforeUnloadEvent.idl

@@ -0,0 +1,7 @@
+#import <DOM/Event.idl>
+
+// https://html.spec.whatwg.org/multipage/nav-history-apis.html#the-beforeunloadevent-interface
+[Exposed=Window]
+interface BeforeUnloadEvent : Event {
+    attribute DOMString returnValue;
+};

+ 2 - 1
Userland/Libraries/LibWeb/DOM/Document.cpp

@@ -38,6 +38,7 @@
 #include <LibWeb/Cookie/ParsedCookie.h>
 #include <LibWeb/DOM/AdoptedStyleSheets.h>
 #include <LibWeb/DOM/Attr.h>
+#include <LibWeb/DOM/BeforeUnloadEvent.h>
 #include <LibWeb/DOM/CDATASection.h>
 #include <LibWeb/DOM/Comment.h>
 #include <LibWeb/DOM/CustomEvent.h>
@@ -1746,7 +1747,7 @@ WebIDL::ExceptionOr<JS::NonnullGCPtr<Event>> 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)) {

+ 15 - 4
Userland/Libraries/LibWeb/DOM/EventTarget.cpp

@@ -16,6 +16,7 @@
 #include <LibWeb/Bindings/EventTargetPrototype.h>
 #include <LibWeb/Bindings/MainThreadVM.h>
 #include <LibWeb/DOM/AbortSignal.h>
+#include <LibWeb/DOM/BeforeUnloadEvent.h>
 #include <LibWeb/DOM/DOMEventListener.h>
 #include <LibWeb/DOM/Document.h>
 #include <LibWeb/DOM/Event.h>
@@ -690,10 +691,20 @@ JS::ThrowCompletionOr<void> 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<BeforeUnloadEvent>(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<BeforeUnloadEvent&>(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

+ 1 - 0
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;

+ 1 - 0
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)