瀏覽代碼

LibWeb: Add initial implementation of structured clone

This implementation only works for cloning Numbers, and does not try to
do all the spec steps for structured serialize and deserialize.

Co-Authored-By: Andrew Kaster <akaster@serenityos.org>
Daniel Ehrenberg 3 年之前
父節點
當前提交
09841f56ed

+ 1 - 0
Userland/Libraries/LibWeb/CMakeLists.txt

@@ -281,6 +281,7 @@ set(SOURCES
     HTML/Scripting/Script.cpp
     HTML/Scripting/Script.cpp
     HTML/Scripting/WindowEnvironmentSettingsObject.cpp
     HTML/Scripting/WindowEnvironmentSettingsObject.cpp
     HTML/Storage.cpp
     HTML/Storage.cpp
+    HTML/StructuredSerialize.cpp
     HTML/SubmitEvent.cpp
     HTML/SubmitEvent.cpp
     HTML/SyntaxHighlighter/SyntaxHighlighter.cpp
     HTML/SyntaxHighlighter/SyntaxHighlighter.cpp
     HTML/TagNames.cpp
     HTML/TagNames.cpp

+ 159 - 0
Userland/Libraries/LibWeb/HTML/StructuredSerialize.cpp

@@ -0,0 +1,159 @@
+/*
+ * Copyright (c) 2022, Daniel Ehrenberg <dan@littledan.dev>
+ * Copyright (c) 2022, Andrew Kaster <akaster@serenityos.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <AK/HashTable.h>
+#include <AK/Vector.h>
+#include <LibJS/Forward.h>
+#include <LibWeb/HTML/StructuredSerialize.h>
+#include <LibWeb/WebIDL/ExceptionOr.h>
+
+namespace Web::HTML {
+
+// Binary format:
+// A list of adjacent shallow values, which may contain references to other
+// values (noted by their position in the list, one value following another).
+// This list represents the "memory" in the StructuredSerialize algorithm.
+// The first item in the list is the root, i.e., the value of everything.
+// The format is generally u32-aligned (hence this leaking out into the type)
+// Each value has a length based on its type, as defined below.
+//
+// (Should more redundancy be added, e.g., for lengths/positions of values?)
+
+enum ValueTag {
+    // Unused, for ease of catching bugs
+    Empty,
+
+    // Following two u32s are the double value
+    NumberPrimitive,
+
+    // TODO: Define many more types
+
+    // This tag or higher are understood to be errors
+    ValueTagMax,
+};
+
+// Serializing and deserializing are each two passes:
+// 1. Fill up the memory with all the values, but without translating references
+// 2. Translate all the references into the appropriate form
+
+class Serializer {
+public:
+    Serializer(JS::VM& vm)
+        : m_vm(vm)
+    {
+    }
+
+    void serialize(JS::Value value)
+    {
+        if (value.is_number()) {
+            m_serialized.append(ValueTag::NumberPrimitive);
+            double number = value.as_double();
+            m_serialized.append(bit_cast<u32*>(&number), 2);
+        } else {
+            // TODO: Define many more types
+            m_error = "Unsupported type"sv;
+        }
+    }
+
+    WebIDL::ExceptionOr<Vector<u32>> result()
+    {
+        if (m_error.is_null())
+            return m_serialized;
+        return throw_completion(WebIDL::DataCloneError::create(*m_vm.current_realm(), m_error));
+    }
+
+private:
+    AK::StringView m_error;
+    SerializationMemory m_memory; // JS value -> index
+    SerializationRecord m_serialized;
+    JS::VM& m_vm;
+};
+
+class Deserializer {
+public:
+    Deserializer(JS::VM& vm, JS::Realm& target_realm, SerializationRecord const& v)
+        : m_vm(vm)
+        , m_vector(v)
+        , m_memory(target_realm.heap())
+    {
+    }
+
+    void deserialize()
+    {
+        // First pass: fill up the memory with new values
+        u32 position = 0;
+        while (position < m_vector.size()) {
+            switch (m_vector[position++]) {
+            case ValueTag::NumberPrimitive: {
+                u32 bits[2];
+                bits[0] = m_vector[position++];
+                bits[1] = m_vector[position++];
+                double value = *bit_cast<double*>(&bits);
+                m_memory.append(JS::Value(value));
+                break;
+            }
+            default:
+                m_error = "Unsupported type"sv;
+                return;
+            }
+        }
+
+        // Second pass: Update the objects to point to other objects in memory
+    }
+
+    WebIDL::ExceptionOr<JS::Value> result()
+    {
+        if (m_error.is_null())
+            return m_memory[0];
+        return throw_completion(WebIDL::DataCloneError::create(*m_vm.current_realm(), m_error));
+    }
+
+private:
+    JS::VM& m_vm;
+    SerializationRecord const& m_vector;
+    JS::MarkedVector<JS::Value> m_memory; // Index -> JS value
+    StringView m_error;
+};
+
+// https://html.spec.whatwg.org/multipage/structured-data.html#structuredserialize
+WebIDL::ExceptionOr<SerializationRecord> structured_serialize(JS::VM& vm, JS::Value value)
+{
+    // 1. Return ? StructuredSerializeInternal(value, false).
+    return structured_serialize_internal(vm, value, false, {});
+}
+
+// https://html.spec.whatwg.org/multipage/structured-data.html#structuredserializeforstorage
+WebIDL::ExceptionOr<SerializationRecord> structured_serialize_for_storage(JS::VM& vm, JS::Value value)
+{
+    // 1. Return ? StructuredSerializeInternal(value, true).
+    return structured_serialize_internal(vm, value, true, {});
+}
+
+// https://html.spec.whatwg.org/multipage/structured-data.html#structuredserializeinternal
+WebIDL::ExceptionOr<SerializationRecord> structured_serialize_internal(JS::VM& vm, JS::Value value, bool for_storage, Optional<SerializationMemory> memory)
+{
+    // FIXME: Do the spec steps
+    (void)for_storage;
+    (void)memory;
+
+    Serializer serializer(vm);
+    serializer.serialize(value);
+    return serializer.result(); // TODO: Avoid several copies of vector
+}
+
+// https://html.spec.whatwg.org/multipage/structured-data.html#structureddeserialize
+WebIDL::ExceptionOr<JS::Value> structured_deserialize(JS::VM& vm, SerializationRecord const& serialized, JS::Realm& target_realm, Optional<SerializationMemory> memory)
+{
+    // FIXME: Do the spec steps
+    (void)memory;
+
+    Deserializer deserializer(vm, target_realm, serialized);
+    deserializer.deserialize();
+    return deserializer.result();
+}
+
+}

+ 34 - 0
Userland/Libraries/LibWeb/HTML/StructuredSerialize.h

@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2022, Daniel Ehrenberg <dan@littledan.dev>
+ * Copyright (c) 2022, Andrew Kaster <akaster@serenityos.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#pragma once
+
+#include <AK/Result.h>
+#include <AK/Types.h>
+#include <AK/Vector.h>
+#include <LibJS/Forward.h>
+#include <LibWeb/WebIDL/ExceptionOr.h>
+
+// Structured serialize is an entirely different format from IPC because:
+// - It contains representation of type information
+// - It may contain circularities
+// - It is restricted to JS values
+
+namespace Web::HTML {
+
+using SerializationRecord = Vector<u32>;
+using SerializationMemory = HashMap<JS::Handle<JS::Value>, u32>;
+
+WebIDL::ExceptionOr<SerializationRecord> structured_serialize(JS::VM& vm, JS::Value);
+WebIDL::ExceptionOr<SerializationRecord> structured_serialize_for_storage(JS::VM& vm, JS::Value);
+WebIDL::ExceptionOr<SerializationRecord> structured_serialize_internal(JS::VM& vm, JS::Value, bool for_storage, Optional<SerializationMemory>);
+
+WebIDL::ExceptionOr<JS::Value> structured_deserialize(JS::VM& vm, SerializationRecord const& serialized, JS::Realm& target_realm, Optional<SerializationMemory>);
+
+// TODO: structured_[de]serialize_with_transfer
+
+}

+ 17 - 0
Userland/Libraries/LibWeb/HTML/Window.cpp

@@ -44,6 +44,7 @@
 #include <LibWeb/HTML/Scripting/Environments.h>
 #include <LibWeb/HTML/Scripting/Environments.h>
 #include <LibWeb/HTML/Scripting/ExceptionReporter.h>
 #include <LibWeb/HTML/Scripting/ExceptionReporter.h>
 #include <LibWeb/HTML/Storage.h>
 #include <LibWeb/HTML/Storage.h>
+#include <LibWeb/HTML/StructuredSerialize.h>
 #include <LibWeb/HTML/Timer.h>
 #include <LibWeb/HTML/Timer.h>
 #include <LibWeb/HTML/Window.h>
 #include <LibWeb/HTML/Window.h>
 #include <LibWeb/HTML/WindowProxy.h>
 #include <LibWeb/HTML/WindowProxy.h>
@@ -915,6 +916,13 @@ WebIDL::ExceptionOr<void> Window::post_message_impl(JS::Value message, String co
     return {};
     return {};
 }
 }
 
 
+// https://html.spec.whatwg.org/multipage/structured-data.html#dom-structuredclone
+WebIDL::ExceptionOr<JS::Value> Window::structured_clone_impl(JS::VM& vm, JS::Value message)
+{
+    auto serialized = TRY(structured_serialize(vm, message));
+    return MUST(structured_deserialize(vm, serialized, *vm.current_realm(), {}));
+}
+
 // https://html.spec.whatwg.org/multipage/window-object.html#dom-name
 // https://html.spec.whatwg.org/multipage/window-object.html#dom-name
 String Window::name() const
 String Window::name() const
 {
 {
@@ -1100,6 +1108,7 @@ void Window::initialize_web_interfaces(Badge<WindowEnvironmentSettingsObject>)
     define_native_function(realm, "getSelection", get_selection, 0, attr);
     define_native_function(realm, "getSelection", get_selection, 0, attr);
 
 
     define_native_function(realm, "postMessage", post_message, 1, attr);
     define_native_function(realm, "postMessage", post_message, 1, attr);
+    define_native_function(realm, "structuredClone", structured_clone, 1, attr);
 
 
     define_native_function(realm, "fetch", Bindings::fetch, 1, attr);
     define_native_function(realm, "fetch", Bindings::fetch, 1, attr);
 
 
@@ -1824,6 +1833,14 @@ JS_DEFINE_NATIVE_FUNCTION(Window::post_message)
     return JS::js_undefined();
     return JS::js_undefined();
 }
 }
 
 
+JS_DEFINE_NATIVE_FUNCTION(Window::structured_clone)
+{
+    auto* impl = TRY(impl_from(vm));
+    return TRY(Bindings::throw_dom_exception_if_needed(vm, [&] {
+        return impl->structured_clone_impl(vm, vm.argument(0));
+    }));
+}
+
 // https://html.spec.whatwg.org/multipage/webappapis.html#dom-origin
 // https://html.spec.whatwg.org/multipage/webappapis.html#dom-origin
 JS_DEFINE_NATIVE_FUNCTION(Window::origin_getter)
 JS_DEFINE_NATIVE_FUNCTION(Window::origin_getter)
 {
 {

+ 2 - 0
Userland/Libraries/LibWeb/HTML/Window.h

@@ -119,6 +119,7 @@ public:
     WindowProxy* parent();
     WindowProxy* parent();
 
 
     WebIDL::ExceptionOr<void> post_message_impl(JS::Value, String const& target_origin);
     WebIDL::ExceptionOr<void> post_message_impl(JS::Value, String const& target_origin);
+    WebIDL::ExceptionOr<JS::Value> structured_clone_impl(JS::VM& vm, JS::Value);
 
 
     String name() const;
     String name() const;
     void set_name(String const&);
     void set_name(String const&);
@@ -241,6 +242,7 @@ private:
     JS_DECLARE_NATIVE_FUNCTION(outer_height_getter);
     JS_DECLARE_NATIVE_FUNCTION(outer_height_getter);
 
 
     JS_DECLARE_NATIVE_FUNCTION(post_message);
     JS_DECLARE_NATIVE_FUNCTION(post_message);
+    JS_DECLARE_NATIVE_FUNCTION(structured_clone);
 
 
     JS_DECLARE_NATIVE_FUNCTION(local_storage_getter);
     JS_DECLARE_NATIVE_FUNCTION(local_storage_getter);
     JS_DECLARE_NATIVE_FUNCTION(session_storage_getter);
     JS_DECLARE_NATIVE_FUNCTION(session_storage_getter);