diff options
author | Daniel Ehrenberg <dan@littledan.dev> | 2022-04-05 02:24:10 +0200 |
---|---|---|
committer | Andreas Kling <kling@serenityos.org> | 2022-11-26 00:47:23 +0100 |
commit | 09841f56ed2f0422b5cfd815d6ff02a4b4f745b2 (patch) | |
tree | bdc3d34969e54d6eb494a0f0a5600184a5c1994e /Userland/Libraries/LibWeb/HTML | |
parent | d94d60219c77dbeb1c27597a702d2b9a9f9967a7 (diff) | |
download | serenity-09841f56ed2f0422b5cfd815d6ff02a4b4f745b2.zip |
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>
Diffstat (limited to 'Userland/Libraries/LibWeb/HTML')
-rw-r--r-- | Userland/Libraries/LibWeb/HTML/StructuredSerialize.cpp | 159 | ||||
-rw-r--r-- | Userland/Libraries/LibWeb/HTML/StructuredSerialize.h | 34 | ||||
-rw-r--r-- | Userland/Libraries/LibWeb/HTML/Window.cpp | 17 | ||||
-rw-r--r-- | Userland/Libraries/LibWeb/HTML/Window.h | 2 |
4 files changed, 212 insertions, 0 deletions
diff --git a/Userland/Libraries/LibWeb/HTML/StructuredSerialize.cpp b/Userland/Libraries/LibWeb/HTML/StructuredSerialize.cpp new file mode 100644 index 0000000000..82d0d6cb79 --- /dev/null +++ b/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(); +} + +} diff --git a/Userland/Libraries/LibWeb/HTML/StructuredSerialize.h b/Userland/Libraries/LibWeb/HTML/StructuredSerialize.h new file mode 100644 index 0000000000..2c27800650 --- /dev/null +++ b/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 + +} diff --git a/Userland/Libraries/LibWeb/HTML/Window.cpp b/Userland/Libraries/LibWeb/HTML/Window.cpp index e30eccb553..36b518e497 100644 --- a/Userland/Libraries/LibWeb/HTML/Window.cpp +++ b/Userland/Libraries/LibWeb/HTML/Window.cpp @@ -44,6 +44,7 @@ #include <LibWeb/HTML/Scripting/Environments.h> #include <LibWeb/HTML/Scripting/ExceptionReporter.h> #include <LibWeb/HTML/Storage.h> +#include <LibWeb/HTML/StructuredSerialize.h> #include <LibWeb/HTML/Timer.h> #include <LibWeb/HTML/Window.h> #include <LibWeb/HTML/WindowProxy.h> @@ -915,6 +916,13 @@ WebIDL::ExceptionOr<void> Window::post_message_impl(JS::Value message, String co 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 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, "postMessage", post_message, 1, attr); + define_native_function(realm, "structuredClone", structured_clone, 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(); } +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 JS_DEFINE_NATIVE_FUNCTION(Window::origin_getter) { diff --git a/Userland/Libraries/LibWeb/HTML/Window.h b/Userland/Libraries/LibWeb/HTML/Window.h index d9e7e6715e..0008dd4484 100644 --- a/Userland/Libraries/LibWeb/HTML/Window.h +++ b/Userland/Libraries/LibWeb/HTML/Window.h @@ -119,6 +119,7 @@ public: WindowProxy* parent(); 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; void set_name(String const&); @@ -241,6 +242,7 @@ private: JS_DECLARE_NATIVE_FUNCTION(outer_height_getter); JS_DECLARE_NATIVE_FUNCTION(post_message); + JS_DECLARE_NATIVE_FUNCTION(structured_clone); JS_DECLARE_NATIVE_FUNCTION(local_storage_getter); JS_DECLARE_NATIVE_FUNCTION(session_storage_getter); |