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
11 changed files with 309 additions and 2 deletions

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
{