diff --git a/Userland/Libraries/LibWeb/CMakeLists.txt b/Userland/Libraries/LibWeb/CMakeLists.txt index 2601a631491..8fc5beadb50 100644 --- a/Userland/Libraries/LibWeb/CMakeLists.txt +++ b/Userland/Libraries/LibWeb/CMakeLists.txt @@ -354,6 +354,7 @@ set(SOURCES HTML/HTMLUListElement.cpp HTML/HTMLUnknownElement.cpp HTML/HTMLVideoElement.cpp + HTML/ImageBitmap.cpp HTML/ImageData.cpp HTML/ImageRequest.cpp HTML/ListOfAvailableImages.cpp diff --git a/Userland/Libraries/LibWeb/Forward.h b/Userland/Libraries/LibWeb/Forward.h index 17fd97c4a72..17ac4c7f005 100644 --- a/Userland/Libraries/LibWeb/Forward.h +++ b/Userland/Libraries/LibWeb/Forward.h @@ -423,6 +423,7 @@ class HTMLTrackElement; class HTMLUListElement; class HTMLUnknownElement; class HTMLVideoElement; +class ImageBitmap; class ImageData; class ImageRequest; class ListOfAvailableImages; diff --git a/Userland/Libraries/LibWeb/HTML/ImageBitmap.cpp b/Userland/Libraries/LibWeb/HTML/ImageBitmap.cpp new file mode 100644 index 00000000000..59a7922bdf2 --- /dev/null +++ b/Userland/Libraries/LibWeb/HTML/ImageBitmap.cpp @@ -0,0 +1,107 @@ +/* + * Copyright (c) 2024, Lucas Chollet + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include + +namespace Web::HTML { + +JS_DEFINE_ALLOCATOR(ImageBitmap); + +JS::NonnullGCPtr ImageBitmap::create(JS::Realm& realm) +{ + return realm.heap().allocate(realm, realm); +} + +ImageBitmap::ImageBitmap(JS::Realm& realm) + : Bindings::PlatformObject(realm) +{ +} + +void ImageBitmap::initialize(JS::Realm& realm) +{ + Base::initialize(realm); + WEB_SET_PROTOTYPE_FOR_INTERFACE(ImageBitmap); +} + +void ImageBitmap::visit_edges(Cell::Visitor& visitor) +{ + Base::visit_edges(visitor); +} + +WebIDL::ExceptionOr ImageBitmap::serialization_steps(HTML::SerializationRecord&, bool, HTML::SerializationMemory&) +{ + // FIXME: Implement this + dbgln("(STUBBED) ImageBitmap::serialization_steps(HTML::SerializationRecord&, bool, HTML::SerializationMemory&)"); + return {}; +} + +WebIDL::ExceptionOr ImageBitmap::deserialization_steps(ReadonlySpan const&, size_t&, HTML::DeserializationMemory&) +{ + // FIXME: Implement this + dbgln("(STUBBED) ImageBitmap::deserialization_steps(ReadonlySpan const&, size_t&, HTML::DeserializationMemory&)"); + return {}; +} + +WebIDL::ExceptionOr ImageBitmap::transfer_steps(HTML::TransferDataHolder&) +{ + // FIXME: Implement this + dbgln("(STUBBED) ImageBitmap::transfer_steps(HTML::TransferDataHolder&)"); + return {}; +} + +WebIDL::ExceptionOr ImageBitmap::transfer_receiving_steps(HTML::TransferDataHolder&) +{ + // FIXME: Implement this + dbgln("(STUBBED) ImageBitmap::transfer_receiving_steps(HTML::TransferDataHolder&)"); + return {}; +} + +HTML::TransferType ImageBitmap::primary_interface() const +{ + // FIXME: Implement this + dbgln("(STUBBED) ImageBitmap::primary_interface()"); + return {}; +} + +// https://html.spec.whatwg.org/multipage/imagebitmap-and-animations.html#dom-imagebitmap-width +WebIDL::UnsignedLong ImageBitmap::width() const +{ + // 1. If this's [[Detached]] internal slot's value is true, then return 0. + if (is_detached()) + return 0; + // 2. Return this's width, in CSS pixels. + return m_width; +} + +// https://html.spec.whatwg.org/multipage/imagebitmap-and-animations.html#dom-imagebitmap-height +WebIDL::UnsignedLong ImageBitmap::height() const +{ + // 1. If this's [[Detached]] internal slot's value is true, then return 0. + if (is_detached()) + return 0; + // 2. Return this's height, in CSS pixels. + return m_height; +} + +// https://html.spec.whatwg.org/multipage/imagebitmap-and-animations.html#dom-imagebitmap-close +void ImageBitmap::close() +{ + // 1. Set this's [[Detached]] internal slot value to true. + set_detached(true); + + // 2. Unset this's bitmap data. + m_bitmap = nullptr; +} + +void ImageBitmap::set_bitmap(RefPtr bitmap) +{ + m_bitmap = move(bitmap); + m_width = m_bitmap->width(); + m_height = m_bitmap->height(); +} + +} diff --git a/Userland/Libraries/LibWeb/HTML/ImageBitmap.h b/Userland/Libraries/LibWeb/HTML/ImageBitmap.h new file mode 100644 index 00000000000..a6d9a3124c6 --- /dev/null +++ b/Userland/Libraries/LibWeb/HTML/ImageBitmap.h @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2024, Lucas Chollet + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +namespace Web::HTML { + +using ImageBitmapSource = Variant, JS::Handle>; + +// https://html.spec.whatwg.org/multipage/imagebitmap-and-animations.html#imagebitmapoptions +struct ImageBitmapOptions { + // FIXME: Implement these fields +}; + +class ImageBitmap final : public Bindings::PlatformObject + , public Web::Bindings::Serializable + , public Web::Bindings::Transferable { + WEB_PLATFORM_OBJECT(ImageBitmap, Bindings::PlatformObject); + JS_DECLARE_ALLOCATOR(ImageBitmap); + +public: + static JS::NonnullGCPtr create(JS::Realm&); + virtual ~ImageBitmap() override = default; + + // ^Web::Bindings::Serializable + virtual StringView interface_name() const override { return "ImageBitmap"sv; } + virtual WebIDL::ExceptionOr serialization_steps(HTML::SerializationRecord&, bool for_storage, HTML::SerializationMemory&) override; + virtual WebIDL::ExceptionOr deserialization_steps(ReadonlySpan const&, size_t& position, HTML::DeserializationMemory&) override; + + // ^Web::Bindings::Transferable + virtual WebIDL::ExceptionOr transfer_steps(HTML::TransferDataHolder&) override; + virtual WebIDL::ExceptionOr transfer_receiving_steps(HTML::TransferDataHolder&) override; + virtual HTML::TransferType primary_interface() const override; + + WebIDL::UnsignedLong width() const; + WebIDL::UnsignedLong height() const; + + void close(); + + // Implementation specific: + void set_bitmap(RefPtr); + +private: + explicit ImageBitmap(JS::Realm&); + + // FIXME: We don't implement this flag yet: + // An ImageBitmap object's bitmap has an origin-clean flag, which indicates whether the bitmap is tainted by content + // from a different origin. The flag is initially set to true and may be changed to false by the steps of + // createImageBitmap(). + + virtual void initialize(JS::Realm&) override; + virtual void visit_edges(Cell::Visitor&) override; + + WebIDL::UnsignedLong m_width = 0; + WebIDL::UnsignedLong m_height = 0; + + RefPtr m_bitmap { nullptr }; +}; + +} diff --git a/Userland/Libraries/LibWeb/HTML/ImageBitmap.idl b/Userland/Libraries/LibWeb/HTML/ImageBitmap.idl new file mode 100644 index 00000000000..d2ec371ab18 --- /dev/null +++ b/Userland/Libraries/LibWeb/HTML/ImageBitmap.idl @@ -0,0 +1,28 @@ +#import +#import + +// https://html.spec.whatwg.org/multipage/imagebitmap-and-animations.html#images-2 +[Exposed=(Window,Worker), Serializable, Transferable] +interface ImageBitmap { + readonly attribute unsigned long width; + readonly attribute unsigned long height; + undefined close(); +}; + +// FIXME: This should also includes CanvasImageSource +typedef (Blob or + ImageData) ImageBitmapSource; + +enum ImageOrientation { "from-image", "flipY" }; +enum PremultiplyAlpha { "none", "premultiply", "default" }; +enum ColorSpaceConversion { "none", "default" }; +enum ResizeQuality { "pixelated", "low", "medium", "high" }; + +dictionary ImageBitmapOptions { + // FIXME: ImageOrientation imageOrientation = "from-image"; + // FIXME: PremultiplyAlpha premultiplyAlpha = "default"; + // FIXME: ColorSpaceConversion colorSpaceConversion = "default"; + // FIXME: [EnforceRange] unsigned long resizeWidth; + // FIXME: [EnforceRange] unsigned long resizeHeight; + // FIXME: ResizeQuality resizeQuality = "low"; +}; diff --git a/Userland/Libraries/LibWeb/HTML/Window.h b/Userland/Libraries/LibWeb/HTML/Window.h index 38213cf7c21..0576d2d8256 100644 --- a/Userland/Libraries/LibWeb/HTML/Window.h +++ b/Userland/Libraries/LibWeb/HTML/Window.h @@ -61,6 +61,7 @@ public: using WindowOrWorkerGlobalScopeMixin::btoa; using WindowOrWorkerGlobalScopeMixin::clear_interval; using WindowOrWorkerGlobalScopeMixin::clear_timeout; + using WindowOrWorkerGlobalScopeMixin::create_image_bitmap; using WindowOrWorkerGlobalScopeMixin::fetch; using WindowOrWorkerGlobalScopeMixin::queue_microtask; using WindowOrWorkerGlobalScopeMixin::set_interval; diff --git a/Userland/Libraries/LibWeb/HTML/WindowOrWorkerGlobalScope.cpp b/Userland/Libraries/LibWeb/HTML/WindowOrWorkerGlobalScope.cpp index 35094f3f9d6..4fe10052565 100644 --- a/Userland/Libraries/LibWeb/HTML/WindowOrWorkerGlobalScope.cpp +++ b/Userland/Libraries/LibWeb/HTML/WindowOrWorkerGlobalScope.cpp @@ -17,7 +17,9 @@ #include #include #include +#include #include +#include #include #include #include @@ -32,6 +34,8 @@ #include #include #include +#include +#include #include #include #include @@ -155,6 +159,92 @@ void WindowOrWorkerGlobalScopeMixin::queue_microtask(WebIDL::CallbackType& callb }); } +// https://html.spec.whatwg.org/multipage/imagebitmap-and-animations.html#dom-createimagebitmap +JS::NonnullGCPtr WindowOrWorkerGlobalScopeMixin::create_image_bitmap(ImageBitmapSource image, Optional options) const +{ + return create_image_bitmap_impl(image, {}, {}, {}, {}, options); +} + +// https://html.spec.whatwg.org/multipage/imagebitmap-and-animations.html#dom-createimagebitmap +JS::NonnullGCPtr WindowOrWorkerGlobalScopeMixin::create_image_bitmap(ImageBitmapSource image, WebIDL::Long sx, WebIDL::Long sy, WebIDL::Long sw, WebIDL::Long sh, Optional options) const +{ + return create_image_bitmap_impl(image, sx, sy, sw, sh, options); +} + +JS::NonnullGCPtr WindowOrWorkerGlobalScopeMixin::create_image_bitmap_impl(ImageBitmapSource& image, Optional sx, Optional sy, Optional sw, Optional sh, Optional& options) const +{ + // 1. If either sw or sh is given and is 0, then return a promise rejected with a RangeError. + if (sw == 0 || sh == 0) { + auto promise = JS::Promise::create(this_impl().realm()); + auto error_message = MUST(String::formatted("{} is an invalid value for {}", sw == 0 ? *sw : *sh, sw == 0 ? "sw"sv : "sh"sv)); + promise->reject(JS::RangeError::create(this_impl().realm(), move(error_message))); + return promise; + } + + // FIXME: + // 2. If either options's resizeWidth or options's resizeHeight is present and is 0, then return a promise rejected with an "InvalidStateError" DOMException. + (void)options; + + // 3. Check the usability of the image argument. If this throws an exception or returns bad, then return a promise rejected with an "InvalidStateError" DOMException. + // FIXME: "Check the usability of the image argument" is only defined for CanvasImageSource, let's skip it for other types + if (image.has()) { + if (auto usability = check_usability_of_image(image.get()); usability.is_error() or usability.value() == CanvasImageSourceUsability::Bad) { + auto promise = JS::Promise::create(this_impl().realm()); + promise->reject(WebIDL::InvalidStateError::create(this_impl().realm(), "image argument is not usable"_string)); + return promise; + } + } + + // 4. Let p be a new promise. + auto p = JS::Promise::create(this_impl().realm()); + + // 5. Let imageBitmap be a new ImageBitmap object. + auto image_bitmap = ImageBitmap::create(this_impl().realm()); + + // 6. Switch on image: + image.visit( + [&](JS::Handle& blob) { + // Run these step in parallel: + Platform::EventLoopPlugin::the().deferred_invoke([=, this]() { + // 1. Let imageData be the result of reading image's data. If an error occurs during reading of the + // object, then reject p with an "InvalidStateError" DOMException and abort these steps. + // FIXME: I guess this is always fine for us as the data is already read. + auto const image_data = blob->bytes(); + + // FIXME: + // 2. Apply the image sniffing rules to determine the file format of imageData, with MIME type of + // image (as given by image's type attribute) giving the official type. + + // 3. If imageData is not in a supported image file format (e.g., it's not an image at all), or if + // imageData is corrupted in some fatal way such that the image dimensions cannot be obtained + // (e.g., a vector graphic with no natural size), then reject p with an "InvalidStateError" DOMException + // and abort these steps. + auto result = Web::Platform::ImageCodecPlugin::the().decode_image(image_data); + if (!result.has_value()) { + p->reject(WebIDL::InvalidStateError::create(this_impl().realm(), "image does not contain a supported image format"_string)); + return; + } + + // 4. Set imageBitmap's bitmap data to imageData, cropped to the source rectangle with formatting. + // If this is an animated image, imageBitmap's bitmap data must only be taken from the default image + // of the animation (the one that the format defines is to be used when animation is not supported + // or is disabled), or, if there is no such image, the first frame of the animation. + image_bitmap->set_bitmap(result.value().frames.take_first().bitmap); + + // 5. Resolve p with imageBitmap. + p->fulfill(image_bitmap); + }); + }, + [&](auto&) { + dbgln("(STUBBED) createImageBitmap() for non-blob types"); + (void)sx; + (void)sy; + }); + + // 7. Return p. + return p; +} + // https://html.spec.whatwg.org/multipage/structured-data.html#dom-structuredclone WebIDL::ExceptionOr WindowOrWorkerGlobalScopeMixin::structured_clone(JS::Value value, StructuredSerializeOptions const& options) const { diff --git a/Userland/Libraries/LibWeb/HTML/WindowOrWorkerGlobalScope.h b/Userland/Libraries/LibWeb/HTML/WindowOrWorkerGlobalScope.h index 14d9efc1ec1..e525abe2a72 100644 --- a/Userland/Libraries/LibWeb/HTML/WindowOrWorkerGlobalScope.h +++ b/Userland/Libraries/LibWeb/HTML/WindowOrWorkerGlobalScope.h @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -39,6 +40,8 @@ public: WebIDL::ExceptionOr btoa(String const& data) const; WebIDL::ExceptionOr atob(String const& data) const; void queue_microtask(WebIDL::CallbackType&); + JS::NonnullGCPtr create_image_bitmap(ImageBitmapSource image, Optional options = {}) const; + JS::NonnullGCPtr create_image_bitmap(ImageBitmapSource image, WebIDL::Long sx, WebIDL::Long sy, WebIDL::Long sw, WebIDL::Long sh, Optional options = {}) const; WebIDL::ExceptionOr structured_clone(JS::Value, StructuredSerializeOptions const&) const; JS::NonnullGCPtr fetch(Fetch::RequestInfo const&, Fetch::RequestInit const&) const; @@ -80,6 +83,8 @@ private: i32 run_timer_initialization_steps(TimerHandler handler, i32 timeout, JS::MarkedVector arguments, Repeat repeat, Optional previous_id = {}); void run_steps_after_a_timeout_impl(i32 timeout, Function completion_step, Optional timer_key = {}); + JS::NonnullGCPtr create_image_bitmap_impl(ImageBitmapSource& image, Optional sx, Optional sy, Optional sw, Optional sh, Optional& options) const; + IDAllocator m_timer_id_allocator; HashMap> m_timers; diff --git a/Userland/Libraries/LibWeb/HTML/WindowOrWorkerGlobalScope.idl b/Userland/Libraries/LibWeb/HTML/WindowOrWorkerGlobalScope.idl index 9dd6938ae8c..8f1e018a101 100644 --- a/Userland/Libraries/LibWeb/HTML/WindowOrWorkerGlobalScope.idl +++ b/Userland/Libraries/LibWeb/HTML/WindowOrWorkerGlobalScope.idl @@ -1,6 +1,7 @@ #import #import #import +#import #import // FIXME: Support VoidFunction in the IDL parser @@ -31,8 +32,8 @@ interface mixin WindowOrWorkerGlobalScope { undefined queueMicrotask(VoidFunction callback); // ImageBitmap - // FIXME: Promise createImageBitmap(ImageBitmapSource image, optional ImageBitmapOptions options = {}); - // FIXME: Promise createImageBitmap(ImageBitmapSource image, long sx, long sy, long sw, long sh, optional ImageBitmapOptions options = {}); + Promise createImageBitmap(ImageBitmapSource image, optional ImageBitmapOptions options = {}); + Promise createImageBitmap(ImageBitmapSource image, long sx, long sy, long sw, long sh, optional ImageBitmapOptions options = {}); // structured cloning any structuredClone(any value, optional StructuredSerializeOptions options = {}); diff --git a/Userland/Libraries/LibWeb/HTML/WorkerGlobalScope.h b/Userland/Libraries/LibWeb/HTML/WorkerGlobalScope.h index e5263e49ee4..cac99a4a35f 100644 --- a/Userland/Libraries/LibWeb/HTML/WorkerGlobalScope.h +++ b/Userland/Libraries/LibWeb/HTML/WorkerGlobalScope.h @@ -50,6 +50,7 @@ public: using WindowOrWorkerGlobalScopeMixin::btoa; using WindowOrWorkerGlobalScopeMixin::clear_interval; using WindowOrWorkerGlobalScopeMixin::clear_timeout; + using WindowOrWorkerGlobalScopeMixin::create_image_bitmap; using WindowOrWorkerGlobalScopeMixin::fetch; using WindowOrWorkerGlobalScopeMixin::performance; using WindowOrWorkerGlobalScopeMixin::queue_microtask; diff --git a/Userland/Libraries/LibWeb/idl_files.cmake b/Userland/Libraries/LibWeb/idl_files.cmake index d7ea4312858..cac025a4366 100644 --- a/Userland/Libraries/LibWeb/idl_files.cmake +++ b/Userland/Libraries/LibWeb/idl_files.cmake @@ -177,6 +177,7 @@ libweb_js_bindings(HTML/HTMLTrackElement) libweb_js_bindings(HTML/HTMLUListElement) libweb_js_bindings(HTML/HTMLUnknownElement) libweb_js_bindings(HTML/HTMLVideoElement) +libweb_js_bindings(HTML/ImageBitmap) libweb_js_bindings(HTML/ImageData) libweb_js_bindings(HTML/Location) libweb_js_bindings(HTML/MediaError)