LibWeb: Implement {,de}serialization steps for ImageBitmap

To make {,de}serialization of ImageBitmap work we also had to add
support for creating an ImageBitmap from a HTMLCanvasElement in
WindowOrWorkerGlobalScopeMixin::create_image_bitmap_impl().
This commit is contained in:
Kenneth Myhra 2025-07-14 21:32:04 +02:00 committed by Shannon Booth
commit c0976b18e0
Notes: github-actions[bot] 2025-07-20 00:32:03 +00:00
5 changed files with 72 additions and 29 deletions

View file

@ -7,11 +7,40 @@
#include <LibGfx/Bitmap.h> #include <LibGfx/Bitmap.h>
#include <LibWeb/Bindings/ImageBitmapPrototype.h> #include <LibWeb/Bindings/ImageBitmapPrototype.h>
#include <LibWeb/HTML/ImageBitmap.h> #include <LibWeb/HTML/ImageBitmap.h>
#include <LibWeb/HTML/StructuredSerialize.h>
#include <LibWeb/WebIDL/DOMException.h>
#include <LibWeb/WebIDL/ExceptionOr.h>
namespace Web::HTML { namespace Web::HTML {
GC_DEFINE_ALLOCATOR(ImageBitmap); GC_DEFINE_ALLOCATOR(ImageBitmap);
[[nodiscard]] static auto create_bitmap_from_bitmap_data(Gfx::BitmapFormat const format, Gfx::AlphaType const alpha_type, u32 const width, u32 const height, u32 const pitch, ByteBuffer data)
{
return Gfx::Bitmap::create_wrapper(format, alpha_type, Gfx::IntSize(width, height), pitch, data.data());
}
static void serialize_bitmap(HTML::TransferDataEncoder& encoder, Gfx::Bitmap& bitmap)
{
encoder.encode(bitmap.width());
encoder.encode(bitmap.height());
encoder.encode(bitmap.pitch());
encoder.encode(bitmap.format());
encoder.encode(bitmap.alpha_type());
encoder.encode(ReadonlyBytes { bitmap.scanline_u8(0), bitmap.data_size() });
}
[[nodiscard]] static WebIDL::ExceptionOr<NonnullRefPtr<Gfx::Bitmap>> deserialize_bitmap(JS::Realm& realm, HTML::TransferDataDecoder& decoder)
{
auto const width = decoder.decode<int>();
auto const height = decoder.decode<int>();
auto const pitch = decoder.decode<size_t>();
auto const format = decoder.decode<Gfx::BitmapFormat>();
auto const alpha_type = decoder.decode<Gfx::AlphaType>();
auto const data = TRY(decoder.decode_buffer(realm));
return TRY_OR_THROW_OOM(realm.vm(), create_bitmap_from_bitmap_data(format, alpha_type, width, height, pitch, data));
}
GC::Ref<ImageBitmap> ImageBitmap::create(JS::Realm& realm) GC::Ref<ImageBitmap> ImageBitmap::create(JS::Realm& realm)
{ {
return realm.create<ImageBitmap>(realm); return realm.create<ImageBitmap>(realm);
@ -33,17 +62,23 @@ void ImageBitmap::visit_edges(Cell::Visitor& visitor)
Base::visit_edges(visitor); Base::visit_edges(visitor);
} }
WebIDL::ExceptionOr<void> ImageBitmap::serialization_steps(HTML::TransferDataEncoder&, bool, HTML::SerializationMemory&) // https://html.spec.whatwg.org/multipage/imagebitmap-and-animations.html#the-imagebitmap-interface:serialization-steps
WebIDL::ExceptionOr<void> ImageBitmap::serialization_steps(HTML::TransferDataEncoder& serialized, bool, HTML::SerializationMemory&)
{ {
// FIXME: Implement this // FIXME: 1. If value's origin-clean flag is not set, then throw a "DataCloneError" DOMException.
dbgln("(STUBBED) ImageBitmap::serialization_steps(HTML::TransferDataEncoder&, bool, HTML::SerializationMemory&)");
// 2. Set serialized.[[BitmapData]] to a copy of value's bitmap data.
serialize_bitmap(serialized, *m_bitmap);
return {}; return {};
} }
WebIDL::ExceptionOr<void> ImageBitmap::deserialization_steps(HTML::TransferDataDecoder&, HTML::DeserializationMemory&) // https://html.spec.whatwg.org/multipage/imagebitmap-and-animations.html#the-imagebitmap-interface:deserialization-steps
WebIDL::ExceptionOr<void> ImageBitmap::deserialization_steps(HTML::TransferDataDecoder& serialized, HTML::DeserializationMemory&)
{ {
// FIXME: Implement this // 1. Set value's bitmap data to serialized.[[BitmapData]].
dbgln("(STUBBED) ImageBitmap::deserialization_steps(ReadonlySpan<u32> const&, size_t&, HTML::DeserializationMemory&)"); m_bitmap = TRY(deserialize_bitmap(this->realm(), serialized));
return {}; return {};
} }

View file

@ -254,11 +254,19 @@ GC::Ref<WebIDL::Promise> WindowOrWorkerGlobalScopeMixin::create_image_bitmap_imp
WebIDL::reject_promise(realm, *p, error); WebIDL::reject_promise(realm, *p, error);
}, },
// -> canvas // -> canvas
[&](GC::Root<HTMLCanvasElement> const&) { [&](GC::Root<HTMLCanvasElement> const& canvas_element) {
dbgln("(STUBBED) createImageBitmap() for HTMLCanvasElement"); // 1. Set imageBitmap's bitmap data to a copy of image's bitmap data, cropped to the source rectangle with formatting.
auto const error = JS::Error::create(realm, "Not Implemented: createImageBitmap() for HTMLCanvasElement"sv); // FIXME: Actually crop the image to the source rectangle with formatting: https://html.spec.whatwg.org/multipage/imagebitmap-and-animations.html#cropped-to-the-source-rectangle-with-formatting
TemporaryExecutionContext const context { relevant_realm(p->promise()), TemporaryExecutionContext::CallbacksEnabled::Yes }; image_bitmap->set_bitmap(canvas_element->get_bitmap_from_surface());
WebIDL::reject_promise(realm, *p, error);
// FIXME: 2. Set the origin-clean flag of the imageBitmap's bitmap to the same value as the origin-clean flag of image's bitmap.
// 3. Queue a global task, using the bitmap task source, to resolve promise with imageBitmap.
queue_global_task(Task::Source::BitmapTask, image_bitmap, GC::create_function(realm.heap(), [p, image_bitmap] {
auto& realm = relevant_realm(image_bitmap);
TemporaryExecutionContext const context { realm, TemporaryExecutionContext::CallbacksEnabled::Yes };
WebIDL::resolve_promise(realm, *p, image_bitmap);
}));
}, },
// -> ImageBitmap // -> ImageBitmap
[&](GC::Root<ImageBitmap> const&) { [&](GC::Root<ImageBitmap> const&) {

View file

@ -2,7 +2,7 @@ Blob [Success]: [object ImageBitmap]
ImageData [Success]: [object ImageBitmap] ImageData [Success]: [object ImageBitmap]
HTMLImageElement [ Error ]: InvalidStateError: image argument is not usable HTMLImageElement [ Error ]: InvalidStateError: image argument is not usable
SVGImageElement [ Error ]: InvalidStateError: image argument is not usable SVGImageElement [ Error ]: InvalidStateError: image argument is not usable
HTMLCanvasElement [ Error ]: Error: Not Implemented: createImageBitmap() for HTMLCanvasElement HTMLCanvasElement [Success]: [object ImageBitmap]
ImageBitmap [ Error ]: TypeError: No union types matched ImageBitmap [ Error ]: TypeError: No union types matched
OffscreenCanvas [ Error ]: Error: Not Implemented: createImageBitmap() for OffscreenCanvas OffscreenCanvas [ Error ]: Error: Not Implemented: createImageBitmap() for OffscreenCanvas
HTMLVideoElement [ Error ]: InvalidStateError: image argument is not usable HTMLVideoElement [ Error ]: InvalidStateError: image argument is not usable

View file

@ -2,8 +2,8 @@ Harness status: OK
Found 150 tests Found 150 tests
141 Pass 147 Pass
9 Fail 3 Fail
Pass primitive undefined Pass primitive undefined
Pass primitive null Pass primitive null
Pass primitive true Pass primitive true
@ -126,12 +126,12 @@ Pass Object with non-enumerable property
Pass Object with non-writable property Pass Object with non-writable property
Pass Object with non-configurable property Pass Object with non-configurable property
Pass Object with a getter that throws Pass Object with a getter that throws
Fail ImageBitmap 1x1 transparent black Pass ImageBitmap 1x1 transparent black
Fail ImageBitmap 1x1 non-transparent non-black Pass ImageBitmap 1x1 non-transparent non-black
Fail Array ImageBitmap object, ImageBitmap 1x1 transparent black Pass Array ImageBitmap object, ImageBitmap 1x1 transparent black
Fail Array ImageBitmap object, ImageBitmap 1x1 transparent non-black Pass Array ImageBitmap object, ImageBitmap 1x1 transparent non-black
Fail Object ImageBitmap object, ImageBitmap 1x1 transparent black Pass Object ImageBitmap object, ImageBitmap 1x1 transparent black
Fail Object ImageBitmap object, ImageBitmap 1x1 transparent non-black Pass Object ImageBitmap object, ImageBitmap 1x1 transparent non-black
Pass ObjectPrototype must lose its exotic-ness when cloned Pass ObjectPrototype must lose its exotic-ness when cloned
Pass Serializing a non-serializable platform object fails Pass Serializing a non-serializable platform object fails
Pass An object whose interface is deleted from the global must still deserialize Pass An object whose interface is deleted from the global must still deserialize

View file

@ -2,8 +2,8 @@ Harness status: OK
Found 150 tests Found 150 tests
141 Pass 147 Pass
9 Fail 3 Fail
Pass primitive undefined Pass primitive undefined
Pass primitive null Pass primitive null
Pass primitive true Pass primitive true
@ -126,12 +126,12 @@ Pass Object with non-enumerable property
Pass Object with non-writable property Pass Object with non-writable property
Pass Object with non-configurable property Pass Object with non-configurable property
Pass Object with a getter that throws Pass Object with a getter that throws
Fail ImageBitmap 1x1 transparent black Pass ImageBitmap 1x1 transparent black
Fail ImageBitmap 1x1 non-transparent non-black Pass ImageBitmap 1x1 non-transparent non-black
Fail Array ImageBitmap object, ImageBitmap 1x1 transparent black Pass Array ImageBitmap object, ImageBitmap 1x1 transparent black
Fail Array ImageBitmap object, ImageBitmap 1x1 transparent non-black Pass Array ImageBitmap object, ImageBitmap 1x1 transparent non-black
Fail Object ImageBitmap object, ImageBitmap 1x1 transparent black Pass Object ImageBitmap object, ImageBitmap 1x1 transparent black
Fail Object ImageBitmap object, ImageBitmap 1x1 transparent non-black Pass Object ImageBitmap object, ImageBitmap 1x1 transparent non-black
Pass ObjectPrototype must lose its exotic-ness when cloned Pass ObjectPrototype must lose its exotic-ness when cloned
Pass Serializing a non-serializable platform object fails Pass Serializing a non-serializable platform object fails
Pass An object whose interface is deleted from the global must still deserialize Pass An object whose interface is deleted from the global must still deserialize