LibWeb: Implement HTML::ImageBitmap

And the two methods of `WindowOrWorkerGlobalScope` that are used as
constructors for it.
This commit is contained in:
Lucas CHOLLET 2024-04-04 21:48:58 -04:00 committed by Andreas Kling
parent db0519ddc1
commit 676fc5e8c6
Notes: sideshowbarker 2024-07-17 08:45:34 +09:00
11 changed files with 309 additions and 2 deletions

View file

@ -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

View file

@ -423,6 +423,7 @@ class HTMLTrackElement;
class HTMLUListElement;
class HTMLUnknownElement;
class HTMLVideoElement;
class ImageBitmap;
class ImageData;
class ImageRequest;
class ListOfAvailableImages;

View file

@ -0,0 +1,107 @@
/*
* Copyright (c) 2024, Lucas Chollet <lucas.chollet@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibGfx/Bitmap.h>
#include <LibWeb/HTML/ImageBitmap.h>
namespace Web::HTML {
JS_DEFINE_ALLOCATOR(ImageBitmap);
JS::NonnullGCPtr<ImageBitmap> ImageBitmap::create(JS::Realm& realm)
{
return realm.heap().allocate<ImageBitmap>(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<void> ImageBitmap::serialization_steps(HTML::SerializationRecord&, bool, HTML::SerializationMemory&)
{
// FIXME: Implement this
dbgln("(STUBBED) ImageBitmap::serialization_steps(HTML::SerializationRecord&, bool, HTML::SerializationMemory&)");
return {};
}
WebIDL::ExceptionOr<void> ImageBitmap::deserialization_steps(ReadonlySpan<u32> const&, size_t&, HTML::DeserializationMemory&)
{
// FIXME: Implement this
dbgln("(STUBBED) ImageBitmap::deserialization_steps(ReadonlySpan<u32> const&, size_t&, HTML::DeserializationMemory&)");
return {};
}
WebIDL::ExceptionOr<void> ImageBitmap::transfer_steps(HTML::TransferDataHolder&)
{
// FIXME: Implement this
dbgln("(STUBBED) ImageBitmap::transfer_steps(HTML::TransferDataHolder&)");
return {};
}
WebIDL::ExceptionOr<void> 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<Gfx::Bitmap> bitmap)
{
m_bitmap = move(bitmap);
m_width = m_bitmap->width();
m_height = m_bitmap->height();
}
}

View file

@ -0,0 +1,71 @@
/*
* Copyright (c) 2024, Lucas Chollet <lucas.chollet@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/OwnPtr.h>
#include <LibGfx/Bitmap.h>
#include <LibWeb/Bindings/PlatformObject.h>
#include <LibWeb/Bindings/Serializable.h>
#include <LibWeb/Bindings/Transferable.h>
#include <LibWeb/Forward.h>
#include <LibWeb/HTML/Canvas/CanvasDrawImage.h>
namespace Web::HTML {
using ImageBitmapSource = Variant<CanvasImageSource, JS::Handle<FileAPI::Blob>, JS::Handle<ImageData>>;
// 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<ImageBitmap> create(JS::Realm&);
virtual ~ImageBitmap() override = default;
// ^Web::Bindings::Serializable
virtual StringView interface_name() const override { return "ImageBitmap"sv; }
virtual WebIDL::ExceptionOr<void> serialization_steps(HTML::SerializationRecord&, bool for_storage, HTML::SerializationMemory&) override;
virtual WebIDL::ExceptionOr<void> deserialization_steps(ReadonlySpan<u32> const&, size_t& position, HTML::DeserializationMemory&) override;
// ^Web::Bindings::Transferable
virtual WebIDL::ExceptionOr<void> transfer_steps(HTML::TransferDataHolder&) override;
virtual WebIDL::ExceptionOr<void> 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<Gfx::Bitmap>);
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<Gfx::Bitmap> m_bitmap { nullptr };
};
}

View file

@ -0,0 +1,28 @@
#import <FileAPI/Blob.idl>
#import <HTML/ImageData.idl>
// 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";
};

View file

@ -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;

View file

@ -17,7 +17,9 @@
#include <LibWeb/Bindings/MainThreadVM.h>
#include <LibWeb/Fetch/FetchMethod.h>
#include <LibWeb/Forward.h>
#include <LibWeb/HTML/CanvasRenderingContext2D.h>
#include <LibWeb/HTML/EventLoop/EventLoop.h>
#include <LibWeb/HTML/ImageBitmap.h>
#include <LibWeb/HTML/Scripting/ClassicScript.h>
#include <LibWeb/HTML/Scripting/Environments.h>
#include <LibWeb/HTML/Scripting/ExceptionReporter.h>
@ -32,6 +34,8 @@
#include <LibWeb/PerformanceTimeline/EntryTypes.h>
#include <LibWeb/PerformanceTimeline/PerformanceObserver.h>
#include <LibWeb/PerformanceTimeline/PerformanceObserverEntryList.h>
#include <LibWeb/Platform/EventLoopPlugin.h>
#include <LibWeb/Platform/ImageCodecPlugin.h>
#include <LibWeb/UserTiming/PerformanceMark.h>
#include <LibWeb/UserTiming/PerformanceMeasure.h>
#include <LibWeb/WebIDL/AbstractOperations.h>
@ -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<JS::Promise> WindowOrWorkerGlobalScopeMixin::create_image_bitmap(ImageBitmapSource image, Optional<ImageBitmapOptions> options) const
{
return create_image_bitmap_impl(image, {}, {}, {}, {}, options);
}
// https://html.spec.whatwg.org/multipage/imagebitmap-and-animations.html#dom-createimagebitmap
JS::NonnullGCPtr<JS::Promise> WindowOrWorkerGlobalScopeMixin::create_image_bitmap(ImageBitmapSource image, WebIDL::Long sx, WebIDL::Long sy, WebIDL::Long sw, WebIDL::Long sh, Optional<ImageBitmapOptions> options) const
{
return create_image_bitmap_impl(image, sx, sy, sw, sh, options);
}
JS::NonnullGCPtr<JS::Promise> WindowOrWorkerGlobalScopeMixin::create_image_bitmap_impl(ImageBitmapSource& image, Optional<WebIDL::Long> sx, Optional<WebIDL::Long> sy, Optional<WebIDL::Long> sw, Optional<WebIDL::Long> sh, Optional<ImageBitmapOptions>& 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<CanvasImageSource>()) {
if (auto usability = check_usability_of_image(image.get<CanvasImageSource>()); 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<FileAPI::Blob>& 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<JS::Value> WindowOrWorkerGlobalScopeMixin::structured_clone(JS::Value value, StructuredSerializeOptions const& options) const
{

View file

@ -15,6 +15,7 @@
#include <LibWeb/Bindings/PlatformObject.h>
#include <LibWeb/Fetch/Request.h>
#include <LibWeb/Forward.h>
#include <LibWeb/HTML/ImageBitmap.h>
#include <LibWeb/HTML/MessagePort.h>
#include <LibWeb/PerformanceTimeline/PerformanceEntry.h>
#include <LibWeb/PerformanceTimeline/PerformanceEntryTuple.h>
@ -39,6 +40,8 @@ public:
WebIDL::ExceptionOr<String> btoa(String const& data) const;
WebIDL::ExceptionOr<String> atob(String const& data) const;
void queue_microtask(WebIDL::CallbackType&);
JS::NonnullGCPtr<JS::Promise> create_image_bitmap(ImageBitmapSource image, Optional<ImageBitmapOptions> options = {}) const;
JS::NonnullGCPtr<JS::Promise> create_image_bitmap(ImageBitmapSource image, WebIDL::Long sx, WebIDL::Long sy, WebIDL::Long sw, WebIDL::Long sh, Optional<ImageBitmapOptions> options = {}) const;
WebIDL::ExceptionOr<JS::Value> structured_clone(JS::Value, StructuredSerializeOptions const&) const;
JS::NonnullGCPtr<JS::Promise> fetch(Fetch::RequestInfo const&, Fetch::RequestInit const&) const;
@ -80,6 +83,8 @@ private:
i32 run_timer_initialization_steps(TimerHandler handler, i32 timeout, JS::MarkedVector<JS::Value> arguments, Repeat repeat, Optional<i32> previous_id = {});
void run_steps_after_a_timeout_impl(i32 timeout, Function<void()> completion_step, Optional<i32> timer_key = {});
JS::NonnullGCPtr<JS::Promise> create_image_bitmap_impl(ImageBitmapSource& image, Optional<WebIDL::Long> sx, Optional<WebIDL::Long> sy, Optional<WebIDL::Long> sw, Optional<WebIDL::Long> sh, Optional<ImageBitmapOptions>& options) const;
IDAllocator m_timer_id_allocator;
HashMap<int, JS::NonnullGCPtr<Timer>> m_timers;

View file

@ -1,6 +1,7 @@
#import <Fetch/Request.idl>
#import <Fetch/Response.idl>
#import <HighResolutionTime/Performance.idl>
#import <HTML/ImageBitmap.idl>
#import <HTML/MessagePort.idl>
// FIXME: Support VoidFunction in the IDL parser
@ -31,8 +32,8 @@ interface mixin WindowOrWorkerGlobalScope {
undefined queueMicrotask(VoidFunction callback);
// ImageBitmap
// FIXME: Promise<ImageBitmap> createImageBitmap(ImageBitmapSource image, optional ImageBitmapOptions options = {});
// FIXME: Promise<ImageBitmap> createImageBitmap(ImageBitmapSource image, long sx, long sy, long sw, long sh, optional ImageBitmapOptions options = {});
Promise<ImageBitmap> createImageBitmap(ImageBitmapSource image, optional ImageBitmapOptions options = {});
Promise<ImageBitmap> createImageBitmap(ImageBitmapSource image, long sx, long sy, long sw, long sh, optional ImageBitmapOptions options = {});
// structured cloning
any structuredClone(any value, optional StructuredSerializeOptions options = {});

View file

@ -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;

View file

@ -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)