imagebitmap: Add missing basic functionality (#37025)

Add missing basic functionality for ImageBitmap
https://html.spec.whatwg.org/multipage/#imagebitmap
including new variant of creation bitmap with source rectangle
https://html.spec.whatwg.org/multipage/#dom-createimagebitmap
but without support of cropping bitmap data with formatting.

Add ImageBitmap to CanvasImageSource union type
https://html.spec.whatwg.org/multipage/#canvasimagesource

Add ImageBitmap to TexImageSource for WebGL
https://registry.khronos.org/webgl/specs/latest/1.0/index.html

Testing: Improvements in the following WPT tests
 - html/canvas/element/manual/imagebitmap/*
 - html/canvas/element/manual/wide-gamut-canvas/*
 - html/semantics/embedded-content/the-canvas-element/*
 - webgl/tests/conformance/textures/image_bitmap_from*
 - webmessaging/postMessage_cross_domain_image_transfer_2d.sub.htm

Fixes: https://github.com/servo/servo/issues/34112

Signed-off-by: Andrei Volykhin <andrei.volykhin@gmail.com>
This commit is contained in:
Andrei Volykhin 2025-06-09 17:28:30 +03:00 committed by GitHub
parent a3c792e5aa
commit 7f536e8092
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
79 changed files with 653 additions and 1232 deletions

View file

@ -29,9 +29,10 @@ use crossbeam_channel::Sender;
use devtools_traits::{PageError, ScriptToDevtoolsControlMsg};
use dom_struct::dom_struct;
use embedder_traits::EmbedderMsg;
use euclid::default::Size2D;
use http::HeaderMap;
use hyper_serde::Serde;
use ipc_channel::ipc::{self, IpcSender};
use ipc_channel::ipc::{self, IpcSender, IpcSharedMemory};
use ipc_channel::router::ROUTER;
use js::glue::{IsWrapper, UnwrapObjectDynamic};
use js::jsapi::{
@ -59,9 +60,11 @@ use net_traits::{
CoreResourceMsg, CoreResourceThread, FetchResponseListener, IpcSend, ReferrerPolicy,
ResourceThreads, fetch_async,
};
use pixels::PixelFormat;
use profile_traits::{ipc as profile_ipc, mem as profile_mem, time as profile_time};
use script_bindings::interfaces::GlobalScopeHelpers;
use servo_url::{ImmutableOrigin, MutableOrigin, ServoUrl};
use snapshot::Snapshot;
use timers::{TimerEventId, TimerEventRequest, TimerSource};
use url::Origin;
use uuid::Uuid;
@ -2956,64 +2959,209 @@ impl GlobalScope {
result == CheckResult::Blocked
}
/// <https://html.spec.whatwg.org/multipage/#dom-createimagebitmap>
#[allow(clippy::too_many_arguments)]
pub(crate) fn create_image_bitmap(
&self,
image: ImageBitmapSource,
_sx: i32,
_sy: i32,
sw: Option<i32>,
sh: Option<i32>,
options: &ImageBitmapOptions,
can_gc: CanGc,
) -> Rc<Promise> {
let in_realm_proof = AlreadyInRealm::assert::<crate::DomTypeHolder>();
let p = Promise::new_in_current_realm(InRealm::Already(&in_realm_proof), can_gc);
// Step 1. If either sw or sh is given and is 0, then return a promise rejected with a RangeError.
if sw.is_some_and(|w| w == 0) {
p.reject_error(
Error::Range("'sw' must be a non-zero value".to_owned()),
can_gc,
);
return p;
}
if sh.is_some_and(|h| h == 0) {
p.reject_error(
Error::Range("'sh' must be a non-zero value".to_owned()),
can_gc,
);
return p;
}
// Step 2. If either options's resizeWidth or options's resizeHeight is present and is 0,
// then return a promise rejected with an "InvalidStateError" DOMException.
if options.resizeWidth.is_some_and(|w| w == 0) {
p.reject_error(Error::InvalidState, can_gc);
return p;
}
if options.resizeHeight.is_some_and(|w| w == 0) {
if options.resizeHeight.is_some_and(|h| h == 0) {
p.reject_error(Error::InvalidState, can_gc);
return p;
}
// Step 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.
// Step 6. Switch on image:
match image {
ImageBitmapSource::HTMLCanvasElement(ref canvas) => {
// https://html.spec.whatwg.org/multipage/#check-the-usability-of-the-image-argument
if !canvas.is_valid() {
ImageBitmapSource::HTMLImageElement(ref image) => {
// <https://html.spec.whatwg.org/multipage/#check-the-usability-of-the-image-argument>
if !image.is_usable().is_ok_and(|u| u) {
p.reject_error(Error::InvalidState, can_gc);
return p;
}
match canvas.get_image_data() {
Some(snapshot) => {
let image_bitmap = ImageBitmap::new(self, snapshot, can_gc);
image_bitmap.set_origin_clean(canvas.origin_is_clean());
p.resolve_native(&(image_bitmap), can_gc);
// If no ImageBitmap object can be constructed, then the promise is rejected instead.
let Some(img) = image.image_data() else {
p.reject_error(Error::InvalidState, can_gc);
return p;
};
let Some(img) = img.as_raster_image() else {
// Vector HTMLImageElement are not yet supported.
p.reject_error(Error::InvalidState, can_gc);
return p;
};
let size = Size2D::new(img.metadata.width, img.metadata.height);
let format = match img.format {
PixelFormat::BGRA8 => snapshot::PixelFormat::BGRA,
PixelFormat::RGBA8 => snapshot::PixelFormat::RGBA,
pixel_format => {
unimplemented!("unsupported pixel format ({:?})", pixel_format)
},
None => p.reject_error(Error::InvalidState, can_gc),
};
let alpha_mode = snapshot::AlphaMode::Transparent {
premultiplied: false,
};
let snapshot = Snapshot::from_shared_memory(
size.cast(),
format,
alpha_mode,
IpcSharedMemory::from_bytes(img.first_frame().bytes),
);
let image_bitmap = ImageBitmap::new(self, snapshot, can_gc);
image_bitmap.set_origin_clean(image.same_origin(GlobalScope::entry().origin()));
p.resolve_native(&image_bitmap, can_gc);
},
ImageBitmapSource::HTMLVideoElement(ref video) => {
// <https://html.spec.whatwg.org/multipage/#check-the-usability-of-the-image-argument>
if !video.is_usable() {
p.reject_error(Error::InvalidState, can_gc);
return p;
}
p
if video.is_network_state_empty() {
p.reject_error(Error::InvalidState, can_gc);
return p;
}
// If no ImageBitmap object can be constructed, then the promise is rejected instead.
let Some(snapshot) = video.get_current_frame_data() else {
p.reject_error(Error::InvalidState, can_gc);
return p;
};
let image_bitmap = ImageBitmap::new(self, snapshot, can_gc);
image_bitmap.set_origin_clean(video.origin_is_clean());
p.resolve_native(&image_bitmap, can_gc);
},
ImageBitmapSource::HTMLCanvasElement(ref canvas) => {
// <https://html.spec.whatwg.org/multipage/#check-the-usability-of-the-image-argument>
if canvas.get_size().is_empty() {
p.reject_error(Error::InvalidState, can_gc);
return p;
}
// If no ImageBitmap object can be constructed, then the promise is rejected instead.
let Some(snapshot) = canvas.get_image_data() else {
p.reject_error(Error::InvalidState, can_gc);
return p;
};
let image_bitmap = ImageBitmap::new(self, snapshot, can_gc);
image_bitmap.set_origin_clean(canvas.origin_is_clean());
p.resolve_native(&image_bitmap, can_gc);
},
ImageBitmapSource::ImageBitmap(ref bitmap) => {
// <https://html.spec.whatwg.org/multipage/#check-the-usability-of-the-image-argument>
if bitmap.is_detached() {
p.reject_error(Error::InvalidState, can_gc);
return p;
}
// If no ImageBitmap object can be constructed, then the promise is rejected instead.
let Some(snapshot) = bitmap.bitmap_data().clone() else {
p.reject_error(Error::InvalidState, can_gc);
return p;
};
let image_bitmap = ImageBitmap::new(self, snapshot, can_gc);
image_bitmap.set_origin_clean(bitmap.origin_is_clean());
p.resolve_native(&image_bitmap, can_gc);
},
ImageBitmapSource::OffscreenCanvas(ref canvas) => {
// https://html.spec.whatwg.org/multipage/#check-the-usability-of-the-image-argument
if !canvas.is_valid() {
// <https://html.spec.whatwg.org/multipage/#check-the-usability-of-the-image-argument>
if canvas.get_size().is_empty() {
p.reject_error(Error::InvalidState, can_gc);
return p;
}
match canvas.get_image_data() {
Some(snapshot) => {
let image_bitmap = ImageBitmap::new(self, snapshot, can_gc);
image_bitmap.set_origin_clean(canvas.origin_is_clean());
p.resolve_native(&(image_bitmap), can_gc);
},
None => p.reject_error(Error::InvalidState, can_gc),
}
p
// If no ImageBitmap object can be constructed, then the promise is rejected instead.
let Some(snapshot) = canvas.get_image_data() else {
p.reject_error(Error::InvalidState, can_gc);
return p;
};
let image_bitmap = ImageBitmap::new(self, snapshot, can_gc);
image_bitmap.set_origin_clean(canvas.origin_is_clean());
p.resolve_native(&image_bitmap, can_gc);
},
_ => {
ImageBitmapSource::Blob(_) => {
// TODO: implement support of Blob object as ImageBitmapSource
p.reject_error(Error::InvalidState, can_gc);
},
ImageBitmapSource::ImageData(ref image_data) => {
// <https://html.spec.whatwg.org/multipage/#the-imagebitmap-interface:imagedata-4>
if image_data.is_detached() {
p.reject_error(Error::InvalidState, can_gc);
return p;
}
let alpha_mode = snapshot::AlphaMode::Transparent {
premultiplied: false,
};
let snapshot = Snapshot::from_shared_memory(
image_data.get_size().cast(),
snapshot::PixelFormat::RGBA,
alpha_mode,
image_data.to_shared_memory(),
);
let image_bitmap = ImageBitmap::new(self, snapshot, can_gc);
p.resolve_native(&image_bitmap, can_gc);
},
ImageBitmapSource::CSSStyleValue(_) => {
// TODO: CSSStyleValue is not part of ImageBitmapSource
// <https://html.spec.whatwg.org/multipage/#imagebitmapsource>
p.reject_error(Error::NotSupported, can_gc);
p
},
}
// Step 7. Return promise.
p
}
pub(crate) fn fire_timer(&self, handle: TimerEventId, can_gc: CanGc) {