diff --git a/Libraries/LibWeb/HTML/ImageData.cpp b/Libraries/LibWeb/HTML/ImageData.cpp index 39c07c8493d..01631102708 100644 --- a/Libraries/LibWeb/HTML/ImageData.cpp +++ b/Libraries/LibWeb/HTML/ImageData.cpp @@ -1,6 +1,6 @@ /* * Copyright (c) 2020-2022, Andreas Kling - * Copyright (c) 2024, Kenneth Myhra + * Copyright (c) 2024-2025, Kenneth Myhra * * SPDX-License-Identifier: BSD-2-Clause */ @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -18,6 +19,16 @@ namespace Web::HTML { GC_DEFINE_ALLOCATOR(ImageData); +[[nodiscard]] static auto create_bitmap_backed_by_uint8_clamped_array(u32 const width, u32 const height, JS::Uint8ClampedArray& data) +{ + return Gfx::Bitmap::create_wrapper(Gfx::BitmapFormat::RGBA8888, Gfx::AlphaType::Unpremultiplied, Gfx::IntSize(width, height), width * sizeof(u32), data.data().data()); +} + +GC::Ref ImageData::create(JS::Realm& realm) +{ + return realm.create(realm); +} + // https://html.spec.whatwg.org/multipage/canvas.html#dom-imagedata WebIDL::ExceptionOr> ImageData::create(JS::Realm& realm, u32 sw, u32 sh, Optional const& settings) { @@ -102,7 +113,7 @@ WebIDL::ExceptionOr> ImageData::initialize(JS::Realm& realm, }()); // AD-HOC: Create the bitmap backed by the Uint8ClampedArray. - auto bitmap = TRY_OR_THROW_OOM(realm.vm(), Gfx::Bitmap::create_wrapper(Gfx::BitmapFormat::RGBA8888, Gfx::AlphaType::Unpremultiplied, Gfx::IntSize(pixels_per_row, rows), pixels_per_row * sizeof(u32), data->data().data())); + auto bitmap = TRY_OR_THROW_OOM(realm.vm(), create_bitmap_backed_by_uint8_clamped_array(pixels_per_row, rows, *data)); // 4. Initialize the width attribute of imageData to pixelsPerRow. // 5. Initialize the height attribute of imageData to rows. @@ -121,6 +132,11 @@ WebIDL::ExceptionOr> ImageData::initialize(JS::Realm& realm, return realm.create(realm, move(bitmap), data, color_space); } +ImageData::ImageData(JS::Realm& realm) + : PlatformObject(realm) +{ +} + ImageData::ImageData(JS::Realm& realm, NonnullRefPtr bitmap, GC::Ref data, Bindings::PredefinedColorSpace color_space) : PlatformObject(realm) , m_bitmap(move(bitmap)) @@ -163,4 +179,57 @@ const JS::Uint8ClampedArray* ImageData::data() const return m_data; } +// https://html.spec.whatwg.org/multipage/canvas.html#pixel-manipulation:serialization-steps +WebIDL::ExceptionOr ImageData::serialization_steps(SerializationRecord& serialized, bool for_storage, SerializationMemory& memory) +{ + auto& vm = this->vm(); + + // 1. Set serialized.[[Data]] to the sub-serialization of the value of value's data attribute. + auto serialized_data = TRY(structured_serialize_internal(vm, m_data, for_storage, memory)); + serialized.extend(move(serialized_data)); + + // 2. Set serialized.[[Width]] to the value of value's width attribute. + serialize_primitive_type(serialized, m_bitmap->width()); + + // 3. Set serialized.[[Height]] to the value of value's height attribute. + serialize_primitive_type(serialized, m_bitmap->height()); + + // 4. Set serialized.[[ColorSpace]] to the value of value's colorSpace attribute. + serialize_enum(serialized, m_color_space); + + // FIXME:: 5. Set serialized.[[PixelFormat]] to the value of value's pixelFormat attribute. + + return {}; +} + +// https://html.spec.whatwg.org/multipage/canvas.html#pixel-manipulation:deserialization-steps +WebIDL::ExceptionOr ImageData::deserialization_steps(ReadonlySpan const& serialized, size_t& position, DeserializationMemory& memory) +{ + auto& vm = this->vm(); + auto& realm = this->realm(); + + // 1. Initialize value's data attribute to the sub-deserialization of serialized.[[Data]]. + auto [value, position_after_deserialization] = TRY(structured_deserialize_internal(vm, serialized, realm, memory, position)); + if (value.has_value() && value.value().is_object() && is(value.value().as_object())) { + m_data = as(value.release_value().as_object()); + } + position = position_after_deserialization; + + // 2. Initialize value's width attribute to serialized.[[Width]]. + auto const width = deserialize_primitive_type(serialized, position); + + // 3. Initialize value's height attribute to serialized.[[Height]]. + auto const height = deserialize_primitive_type(serialized, position); + + // 4. Initialize value's colorSpace attribute to serialized.[[ColorSpace]]. + m_color_space = deserialize_primitive_type(serialized, position); + + // FIXME: 5. Initialize value's pixelFormat attribute to serialized.[[PixelFormat]]. + + // AD-HOC: Create the bitmap backed by the Uint8ClampedArray. + m_bitmap = TRY_OR_THROW_OOM(vm, create_bitmap_backed_by_uint8_clamped_array(width, height, *m_data)); + + return {}; +} + } diff --git a/Libraries/LibWeb/HTML/ImageData.h b/Libraries/LibWeb/HTML/ImageData.h index 3fa6036e5bb..c373ec1ab4a 100644 --- a/Libraries/LibWeb/HTML/ImageData.h +++ b/Libraries/LibWeb/HTML/ImageData.h @@ -1,6 +1,6 @@ /* * Copyright (c) 2020-2022, Andreas Kling - * Copyright (c) 2024, Kenneth Myhra + * Copyright (c) 2024-2025, Kenneth Myhra * * SPDX-License-Identifier: BSD-2-Clause */ @@ -10,6 +10,7 @@ #include #include #include +#include #include namespace Web::HTML { @@ -18,11 +19,14 @@ struct ImageDataSettings { Bindings::PredefinedColorSpace color_space; }; -class ImageData final : public Bindings::PlatformObject { +class ImageData final + : public Bindings::PlatformObject + , public Bindings::Serializable { WEB_PLATFORM_OBJECT(ImageData, Bindings::PlatformObject); GC_DECLARE_ALLOCATOR(ImageData); public: + [[nodiscard]] static GC::Ref create(JS::Realm&); [[nodiscard]] static WebIDL::ExceptionOr> create(JS::Realm&, u32 sw, u32 sh, Optional const& settings = {}); [[nodiscard]] static WebIDL::ExceptionOr> create(JS::Realm&, GC::Root const& data, u32 sw, Optional sh = {}, Optional const& settings = {}); @@ -34,25 +38,30 @@ public: WebIDL::UnsignedLong width() const; WebIDL::UnsignedLong height() const; - Gfx::Bitmap& bitmap() { return m_bitmap; } - Gfx::Bitmap const& bitmap() const { return m_bitmap; } + Gfx::Bitmap& bitmap() { return *m_bitmap; } + Gfx::Bitmap const& bitmap() const { return *m_bitmap; } JS::Uint8ClampedArray* data(); const JS::Uint8ClampedArray* data() const; Bindings::PredefinedColorSpace color_space() const { return m_color_space; } + virtual StringView interface_name() const override { return "ImageData"sv; } + virtual WebIDL::ExceptionOr serialization_steps(SerializationRecord& serialized, bool for_storage, SerializationMemory&) override; + virtual WebIDL::ExceptionOr deserialization_steps(ReadonlySpan const& serialized, size_t& position, DeserializationMemory&) override; + private: [[nodiscard]] static WebIDL::ExceptionOr> initialize(JS::Realm&, u32 rows, u32 pixels_per_row, Optional const&, GC::Ptr = {}, Optional = {}); + explicit ImageData(JS::Realm&); ImageData(JS::Realm&, NonnullRefPtr, GC::Ref, Bindings::PredefinedColorSpace); virtual void initialize(JS::Realm&) override; virtual void visit_edges(Cell::Visitor&) override; - NonnullRefPtr m_bitmap; - Bindings::PredefinedColorSpace m_color_space; - GC::Ref m_data; + RefPtr m_bitmap; + Bindings::PredefinedColorSpace m_color_space { Bindings::PredefinedColorSpace::Srgb }; + GC::Ptr m_data; }; } diff --git a/Libraries/LibWeb/HTML/StructuredSerialize.cpp b/Libraries/LibWeb/HTML/StructuredSerialize.cpp index 6f1566f63db..498a2a6c9c3 100644 --- a/Libraries/LibWeb/HTML/StructuredSerialize.cpp +++ b/Libraries/LibWeb/HTML/StructuredSerialize.cpp @@ -46,6 +46,7 @@ #include #include #include +#include #include #include #include @@ -1069,6 +1070,8 @@ private: return Crypto::CryptoKey::create(realm); if (interface_name == "DOMQuad"sv) return Geometry::DOMQuad::create(realm); + if (interface_name == "ImageData"sv) + return ImageData::create(realm); VERIFY_NOT_REACHED(); } diff --git a/Tests/LibWeb/Text/expected/HTML/StructuredClone-serializable-objects.txt b/Tests/LibWeb/Text/expected/HTML/StructuredClone-serializable-objects.txt index e0fa72287c3..062d37e576e 100644 --- a/Tests/LibWeb/Text/expected/HTML/StructuredClone-serializable-objects.txt +++ b/Tests/LibWeb/Text/expected/HTML/StructuredClone-serializable-objects.txt @@ -29,3 +29,10 @@ CryptoKey.algorithm: {"name":"PBKDF2"} CryptoKey.usages: ["deriveKey","deriveBits"] instanceOf DOMException: true DOMException: Index out of bounds - IndexSizeError +instanceOf ImageData: true +ImageData.width: 2 +ImageData.height: 2 +255, 0, 0, 255 +0, 255, 0, 255 +0, 0, 255, 255 +255, 255, 0, 255 diff --git a/Tests/LibWeb/Text/expected/wpt-import/html/infrastructure/safe-passing-of-structured-data/window-postmessage.window.txt b/Tests/LibWeb/Text/expected/wpt-import/html/infrastructure/safe-passing-of-structured-data/window-postmessage.window.txt index cf819fd63e0..2363c8a9128 100644 --- a/Tests/LibWeb/Text/expected/wpt-import/html/infrastructure/safe-passing-of-structured-data/window-postmessage.window.txt +++ b/Tests/LibWeb/Text/expected/wpt-import/html/infrastructure/safe-passing-of-structured-data/window-postmessage.window.txt @@ -2,8 +2,8 @@ Harness status: OK Found 150 tests -134 Pass -15 Fail +140 Pass +9 Fail 1 Optional Feature Unsupported Pass primitive undefined Pass primitive null @@ -109,12 +109,12 @@ Pass File basic Pass FileList empty Pass Array FileList object, FileList empty Pass Object FileList object, FileList empty -Fail ImageData 1x1 transparent black -Fail ImageData 1x1 non-transparent non-black -Fail Array ImageData object, ImageData 1x1 transparent black -Fail Array ImageData object, ImageData 1x1 non-transparent non-black -Fail Object ImageData object, ImageData 1x1 transparent black -Fail Object ImageData object, ImageData 1x1 non-transparent non-black +Pass ImageData 1x1 transparent black +Pass ImageData 1x1 non-transparent non-black +Pass Array ImageData object, ImageData 1x1 transparent black +Pass Array ImageData object, ImageData 1x1 non-transparent non-black +Pass Object ImageData object, ImageData 1x1 transparent black +Pass Object ImageData object, ImageData 1x1 non-transparent non-black Pass Array sparse Pass Array with non-index property Pass Object with index property and length diff --git a/Tests/LibWeb/Text/expected/wpt-import/html/webappapis/structured-clone/structured-clone.any.txt b/Tests/LibWeb/Text/expected/wpt-import/html/webappapis/structured-clone/structured-clone.any.txt index cf819fd63e0..2363c8a9128 100644 --- a/Tests/LibWeb/Text/expected/wpt-import/html/webappapis/structured-clone/structured-clone.any.txt +++ b/Tests/LibWeb/Text/expected/wpt-import/html/webappapis/structured-clone/structured-clone.any.txt @@ -2,8 +2,8 @@ Harness status: OK Found 150 tests -134 Pass -15 Fail +140 Pass +9 Fail 1 Optional Feature Unsupported Pass primitive undefined Pass primitive null @@ -109,12 +109,12 @@ Pass File basic Pass FileList empty Pass Array FileList object, FileList empty Pass Object FileList object, FileList empty -Fail ImageData 1x1 transparent black -Fail ImageData 1x1 non-transparent non-black -Fail Array ImageData object, ImageData 1x1 transparent black -Fail Array ImageData object, ImageData 1x1 non-transparent non-black -Fail Object ImageData object, ImageData 1x1 transparent black -Fail Object ImageData object, ImageData 1x1 non-transparent non-black +Pass ImageData 1x1 transparent black +Pass ImageData 1x1 non-transparent non-black +Pass Array ImageData object, ImageData 1x1 transparent black +Pass Array ImageData object, ImageData 1x1 non-transparent non-black +Pass Object ImageData object, ImageData 1x1 transparent black +Pass Object ImageData object, ImageData 1x1 non-transparent non-black Pass Array sparse Pass Array with non-index property Pass Object with index property and length diff --git a/Tests/LibWeb/Text/input/HTML/StructuredClone-serializable-objects.html b/Tests/LibWeb/Text/input/HTML/StructuredClone-serializable-objects.html index f7aa39ef71d..cf1b8625c82 100644 --- a/Tests/LibWeb/Text/input/HTML/StructuredClone-serializable-objects.html +++ b/Tests/LibWeb/Text/input/HTML/StructuredClone-serializable-objects.html @@ -66,6 +66,24 @@ println(`instanceOf DOMException: ${domException instanceof DOMException}`); println(`DOMException: ${domException.message} - ${domException.name}`); + const data = new Uint8ClampedArray([ + 255, 0, 0, 255, + 0, 255, 0, 255, + 0, 0, 255, 255, + 255, 255, 0, 255 + ]); + const imageData = structuredClone(new ImageData(data, 2, 2)); + println(`instanceOf ImageData: ${imageData instanceof ImageData}`); + println(`ImageData.width: ${imageData.width}`); + println(`ImageData.height: ${imageData.height}`); + for (let i = 0; i < imageData.data.length; i += 4) { + const r = imageData.data[i]; + const g = imageData.data[i + 1]; + const b = imageData.data[i + 2]; + const a = imageData.data[i + 3]; + println(`${r}, ${g}, ${b}, ${a}`); + } + done(); });