LibWeb: Implement HTML::ImageBitmap
And the two methods of `WindowOrWorkerGlobalScope` that are used as constructors for it.
This commit is contained in:
parent
db0519ddc1
commit
676fc5e8c6
Notes:
sideshowbarker
2024-07-17 08:45:34 +09:00
Author: https://github.com/LucasChollet Commit: https://github.com/SerenityOS/serenity/commit/676fc5e8c6 Pull-request: https://github.com/SerenityOS/serenity/pull/23850 Reviewed-by: https://github.com/shannonbooth ✅
11 changed files with 309 additions and 2 deletions
|
@ -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
|
||||
|
|
|
@ -423,6 +423,7 @@ class HTMLTrackElement;
|
|||
class HTMLUListElement;
|
||||
class HTMLUnknownElement;
|
||||
class HTMLVideoElement;
|
||||
class ImageBitmap;
|
||||
class ImageData;
|
||||
class ImageRequest;
|
||||
class ListOfAvailableImages;
|
||||
|
|
107
Userland/Libraries/LibWeb/HTML/ImageBitmap.cpp
Normal file
107
Userland/Libraries/LibWeb/HTML/ImageBitmap.cpp
Normal 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();
|
||||
}
|
||||
|
||||
}
|
71
Userland/Libraries/LibWeb/HTML/ImageBitmap.h
Normal file
71
Userland/Libraries/LibWeb/HTML/ImageBitmap.h
Normal 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 };
|
||||
};
|
||||
|
||||
}
|
28
Userland/Libraries/LibWeb/HTML/ImageBitmap.idl
Normal file
28
Userland/Libraries/LibWeb/HTML/ImageBitmap.idl
Normal 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";
|
||||
};
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
{
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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 = {});
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Add table
Reference in a new issue