refact: move create_image_bitmap to ImageBitmap Impl (#37348) (#37602)

This PR moves the `create_image_bitmap` method from `GlobalScope` to a
helper function within the `ImageBitmap` implementation in
`imagebitmap.rs`.

Moving this method improves code organization and maintainability. Given
that `globalscope.rs` is already quite large, relocating
`create_image_bitmap` to `imagebitmap.rs` places it closer to the
`ImageBitmap` struct it primarily operates on.

As mentioned in the issue description (No dedicated tests are required
for this change, as long as the project builds), which it does using
`./mach build`. Also ran `./mach fmt` and `./mach test-tidy` which both
didn't report any issue.

Closes #37348

Signed-off-by: Bhuwan Pandit <bhuwanpandit109@gmail.com>
This commit is contained in:
Bhuwan Pandit 2025-06-21 05:12:11 +01:00 committed by GitHub
parent 0832ec5d96
commit d7269c0f3b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 409 additions and 390 deletions

View file

@ -29,7 +29,6 @@ use crossbeam_channel::Sender;
use devtools_traits::{PageError, ScriptToDevtoolsControlMsg}; use devtools_traits::{PageError, ScriptToDevtoolsControlMsg};
use dom_struct::dom_struct; use dom_struct::dom_struct;
use embedder_traits::EmbedderMsg; use embedder_traits::EmbedderMsg;
use euclid::default::Size2D;
use http::HeaderMap; use http::HeaderMap;
use hyper_serde::Serde; use hyper_serde::Serde;
use ipc_channel::ipc::{self, IpcSender}; use ipc_channel::ipc::{self, IpcSender};
@ -60,7 +59,6 @@ use net_traits::{
CoreResourceMsg, CoreResourceThread, FetchResponseListener, IpcSend, ReferrerPolicy, CoreResourceMsg, CoreResourceThread, FetchResponseListener, IpcSend, ReferrerPolicy,
ResourceThreads, fetch_async, ResourceThreads, fetch_async,
}; };
use pixels::{CorsStatus, PixelFormat, Snapshot, SnapshotAlphaMode, SnapshotPixelFormat};
use profile_traits::{ipc as profile_ipc, mem as profile_mem, time as profile_time}; use profile_traits::{ipc as profile_ipc, mem as profile_mem, time as profile_time};
use script_bindings::interfaces::GlobalScopeHelpers; use script_bindings::interfaces::GlobalScopeHelpers;
use servo_url::{ImmutableOrigin, MutableOrigin, ServoUrl}; use servo_url::{ImmutableOrigin, MutableOrigin, ServoUrl};
@ -81,9 +79,6 @@ use crate::dom::bindings::cell::{DomRefCell, RefMut};
use crate::dom::bindings::codegen::Bindings::BroadcastChannelBinding::BroadcastChannelMethods; use crate::dom::bindings::codegen::Bindings::BroadcastChannelBinding::BroadcastChannelMethods;
use crate::dom::bindings::codegen::Bindings::EventSourceBinding::EventSource_Binding::EventSourceMethods; use crate::dom::bindings::codegen::Bindings::EventSourceBinding::EventSource_Binding::EventSourceMethods;
use crate::dom::bindings::codegen::Bindings::FunctionBinding::Function; use crate::dom::bindings::codegen::Bindings::FunctionBinding::Function;
use crate::dom::bindings::codegen::Bindings::ImageBitmapBinding::{
ImageBitmapOptions, ImageBitmapSource,
};
use crate::dom::bindings::codegen::Bindings::NotificationBinding::NotificationPermissionCallback; use crate::dom::bindings::codegen::Bindings::NotificationBinding::NotificationPermissionCallback;
use crate::dom::bindings::codegen::Bindings::PermissionStatusBinding::{ use crate::dom::bindings::codegen::Bindings::PermissionStatusBinding::{
PermissionName, PermissionState, PermissionName, PermissionState,
@ -117,8 +112,6 @@ use crate::dom::eventsource::EventSource;
use crate::dom::eventtarget::EventTarget; use crate::dom::eventtarget::EventTarget;
use crate::dom::file::File; use crate::dom::file::File;
use crate::dom::htmlscriptelement::{ScriptId, SourceCode}; use crate::dom::htmlscriptelement::{ScriptId, SourceCode};
use crate::dom::imagebitmap::ImageBitmap;
use crate::dom::messageevent::MessageEvent;
use crate::dom::messageport::MessagePort; use crate::dom::messageport::MessagePort;
use crate::dom::node::{Node, NodeTraits}; use crate::dom::node::{Node, NodeTraits};
use crate::dom::paintworkletglobalscope::PaintWorkletGlobalScope; use crate::dom::paintworkletglobalscope::PaintWorkletGlobalScope;
@ -129,6 +122,7 @@ use crate::dom::readablestream::{CrossRealmTransformReadable, ReadableStream};
use crate::dom::serviceworker::ServiceWorker; use crate::dom::serviceworker::ServiceWorker;
use crate::dom::serviceworkerregistration::ServiceWorkerRegistration; use crate::dom::serviceworkerregistration::ServiceWorkerRegistration;
use crate::dom::trustedtypepolicyfactory::TrustedTypePolicyFactory; use crate::dom::trustedtypepolicyfactory::TrustedTypePolicyFactory;
use crate::dom::types::MessageEvent;
use crate::dom::underlyingsourcecontainer::UnderlyingSourceType; use crate::dom::underlyingsourcecontainer::UnderlyingSourceType;
#[cfg(feature = "webgpu")] #[cfg(feature = "webgpu")]
use crate::dom::webgpu::gpudevice::GPUDevice; use crate::dom::webgpu::gpudevice::GPUDevice;
@ -141,7 +135,7 @@ use crate::dom::writablestream::CrossRealmTransformWritable;
use crate::messaging::{CommonScriptMsg, ScriptEventLoopReceiver, ScriptEventLoopSender}; use crate::messaging::{CommonScriptMsg, ScriptEventLoopReceiver, ScriptEventLoopSender};
use crate::microtask::{Microtask, MicrotaskQueue, UserMicrotask}; use crate::microtask::{Microtask, MicrotaskQueue, UserMicrotask};
use crate::network_listener::{NetworkListener, PreInvoke}; use crate::network_listener::{NetworkListener, PreInvoke};
use crate::realms::{AlreadyInRealm, InRealm, enter_realm}; use crate::realms::{InRealm, enter_realm};
use crate::script_module::{ use crate::script_module::{
DynamicModuleList, ImportMap, ModuleScript, ModuleTree, ScriptFetchOptions, DynamicModuleList, ImportMap, ModuleScript, ModuleTree, ScriptFetchOptions,
}; };
@ -2984,377 +2978,6 @@ impl GlobalScope {
result == CheckResult::Blocked 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(|h| h == 0) {
p.reject_error(Error::InvalidState, can_gc);
return p;
}
// The promise with image bitmap should be fulfilled on the the bitmap task source.
let fullfill_promise_on_bitmap_task_source =
|promise: &Rc<Promise>, image_bitmap: &ImageBitmap| {
let trusted_promise = TrustedPromise::new(promise.clone());
let trusted_image_bitmap = Trusted::new(image_bitmap);
self.task_manager()
.bitmap_task_source()
.queue(task!(resolve_promise: move || {
let promise = trusted_promise.root();
let image_bitmap = trusted_image_bitmap.root();
promise.resolve_native(&image_bitmap, CanGc::note());
}));
};
// The promise with "InvalidStateError" DOMException should be rejected
// on the the bitmap task source.
let reject_promise_on_bitmap_task_source = |promise: &Rc<Promise>| {
let trusted_promise = TrustedPromise::new(promise.clone());
self.task_manager()
.bitmap_task_source()
.queue(task!(reject_promise: move || {
let promise = trusted_promise.root();
promise.reject_error(Error::InvalidState, CanGc::note());
}));
};
// 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::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;
}
// 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;
};
// TODO: Support vector HTMLImageElement.
let Some(img) = img.as_raster_image() else {
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 => SnapshotPixelFormat::BGRA,
PixelFormat::RGBA8 => SnapshotPixelFormat::RGBA,
pixel_format => {
unimplemented!("unsupported pixel format ({:?})", pixel_format)
},
};
let alpha_mode = SnapshotAlphaMode::Transparent {
premultiplied: false,
};
let snapshot = Snapshot::from_vec(
size.cast(),
format,
alpha_mode,
img.first_frame().bytes.to_vec(),
);
// Step 6.3. Set imageBitmap's bitmap data to a copy of image's media data,
// cropped to the source rectangle with formatting.
let Some(bitmap_data) =
ImageBitmap::crop_and_transform_bitmap_data(snapshot, sx, sy, sw, sh, options)
else {
p.reject_error(Error::InvalidState, can_gc);
return p;
};
let image_bitmap = ImageBitmap::new(self, bitmap_data, can_gc);
// Step 6.4. If image is not origin-clean, then set the origin-clean flag
// of imageBitmap's bitmap to false.
image_bitmap.set_origin_clean(image.same_origin(GlobalScope::entry().origin()));
// Step 6.5. Queue a global task, using the bitmap task source,
// to resolve promise with imageBitmap.
fullfill_promise_on_bitmap_task_source(&p, &image_bitmap);
},
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;
}
// Step 6.1. If image's networkState attribute is NETWORK_EMPTY, then return
// a promise rejected with an "InvalidStateError" DOMException.
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;
};
// Step 6.2. Set imageBitmap's bitmap data to a copy of the frame at the current
// playback position, at the media resource's natural width and natural height
// (i.e., after any aspect-ratio correction has been applied),
// cropped to the source rectangle with formatting.
let Some(bitmap_data) =
ImageBitmap::crop_and_transform_bitmap_data(snapshot, sx, sy, sw, sh, options)
else {
p.reject_error(Error::InvalidState, can_gc);
return p;
};
let image_bitmap = ImageBitmap::new(self, bitmap_data, can_gc);
// Step 6.3. If image is not origin-clean, then set the origin-clean flag
// of imageBitmap's bitmap to false.
image_bitmap.set_origin_clean(video.origin_is_clean());
// Step 6.4. Queue a global task, using the bitmap task source,
// to resolve promise with imageBitmap.
fullfill_promise_on_bitmap_task_source(&p, &image_bitmap);
},
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;
};
// Step 6.1. Set imageBitmap's bitmap data to a copy of image's bitmap data,
// cropped to the source rectangle with formatting.
let Some(bitmap_data) =
ImageBitmap::crop_and_transform_bitmap_data(snapshot, sx, sy, sw, sh, options)
else {
p.reject_error(Error::InvalidState, can_gc);
return p;
};
let image_bitmap = ImageBitmap::new(self, bitmap_data, can_gc);
// Step 6.2. Set the origin-clean flag of the imageBitmap's bitmap to the same value
// as the origin-clean flag of image's bitmap.
image_bitmap.set_origin_clean(canvas.origin_is_clean());
// Step 6.3. Queue a global task, using the bitmap task source,
// to resolve promise with imageBitmap.
fullfill_promise_on_bitmap_task_source(&p, &image_bitmap);
},
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;
};
// Step 6.1. Set imageBitmap's bitmap data to a copy of image's bitmap data,
// cropped to the source rectangle with formatting.
let Some(bitmap_data) =
ImageBitmap::crop_and_transform_bitmap_data(snapshot, sx, sy, sw, sh, options)
else {
p.reject_error(Error::InvalidState, can_gc);
return p;
};
let image_bitmap = ImageBitmap::new(self, bitmap_data, can_gc);
// Step 6.2. Set the origin-clean flag of imageBitmap's bitmap to the same value
// as the origin-clean flag of image's bitmap.
image_bitmap.set_origin_clean(bitmap.origin_is_clean());
// Step 6.3. Queue a global task, using the bitmap task source,
// to resolve promise with imageBitmap.
fullfill_promise_on_bitmap_task_source(&p, &image_bitmap);
},
ImageBitmapSource::OffscreenCanvas(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;
};
// Step 6.1. Set imageBitmap's bitmap data to a copy of image's bitmap data,
// cropped to the source rectangle with formatting.
let Some(bitmap_data) =
ImageBitmap::crop_and_transform_bitmap_data(snapshot, sx, sy, sw, sh, options)
else {
p.reject_error(Error::InvalidState, can_gc);
return p;
};
let image_bitmap = ImageBitmap::new(self, bitmap_data, can_gc);
// Step 6.2. Set the origin-clean flag of the imageBitmap's bitmap to the same value
// as the origin-clean flag of image's bitmap.
image_bitmap.set_origin_clean(canvas.origin_is_clean());
// Step 6.3. Queue a global task, using the bitmap task source,
// to resolve promise with imageBitmap.
fullfill_promise_on_bitmap_task_source(&p, &image_bitmap);
},
ImageBitmapSource::Blob(ref blob) => {
// Step 6.1. Let imageData be the result of reading image's data.
// If an error occurs during reading of the object, then queue
// a global task, using the bitmap task source, to reject promise
// with an "InvalidStateError" DOMException and abort these steps.
let Ok(bytes) = blob.get_bytes() else {
reject_promise_on_bitmap_task_source(&p);
return p;
};
// Step 6.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.
// Step 6.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 queue
// a global task, using the bitmap task source, to reject promise
// with an "InvalidStateError" DOMException and abort these steps.
let Some(img) = pixels::load_from_memory(&bytes, CorsStatus::Safe) else {
reject_promise_on_bitmap_task_source(&p);
return p;
};
let size = Size2D::new(img.metadata.width, img.metadata.height);
let format = match img.format {
PixelFormat::BGRA8 => SnapshotPixelFormat::BGRA,
PixelFormat::RGBA8 => SnapshotPixelFormat::RGBA,
pixel_format => {
unimplemented!("unsupported pixel format ({:?})", pixel_format)
},
};
let alpha_mode = SnapshotAlphaMode::Transparent {
premultiplied: false,
};
let snapshot = Snapshot::from_vec(
size.cast(),
format,
alpha_mode,
img.first_frame().bytes.to_vec(),
);
// Step 6.4. Set imageBitmap's bitmap data to imageData, cropped
// to the source rectangle with formatting.
let Some(bitmap_data) =
ImageBitmap::crop_and_transform_bitmap_data(snapshot, sx, sy, sw, sh, options)
else {
reject_promise_on_bitmap_task_source(&p);
return p;
};
let image_bitmap = ImageBitmap::new(self, bitmap_data, can_gc);
// Step 6.5. Queue a global task, using the bitmap task source,
// to resolve promise with imageBitmap.
fullfill_promise_on_bitmap_task_source(&p, &image_bitmap);
},
ImageBitmapSource::ImageData(ref image_data) => {
// Step 6.1. Let buffer be image's data attribute value's [[ViewedArrayBuffer]] internal slot.
// Step 6.2. If IsDetachedBuffer(buffer) is true, then return a promise rejected
// with an "InvalidStateError" DOMException.
if image_data.is_detached() {
p.reject_error(Error::InvalidState, can_gc);
return p;
}
let alpha_mode = SnapshotAlphaMode::Transparent {
premultiplied: false,
};
let snapshot = Snapshot::from_vec(
image_data.get_size().cast(),
SnapshotPixelFormat::RGBA,
alpha_mode,
image_data.to_vec(),
);
// Step 6.3. Set imageBitmap's bitmap data to image's image data,
// cropped to the source rectangle with formatting.
let Some(bitmap_data) =
ImageBitmap::crop_and_transform_bitmap_data(snapshot, sx, sy, sw, sh, options)
else {
p.reject_error(Error::InvalidState, can_gc);
return p;
};
let image_bitmap = ImageBitmap::new(self, bitmap_data, can_gc);
// Step 6.4. Queue a global task, using the bitmap task source,
// to resolve promise with imageBitmap.
fullfill_promise_on_bitmap_task_source(&p, &image_bitmap);
},
ImageBitmapSource::CSSStyleValue(_) => {
// TODO: CSSStyleValue is not part of ImageBitmapSource
// <https://html.spec.whatwg.org/multipage/#imagebitmapsource>
p.reject_error(Error::NotSupported, can_gc);
},
}
// Step 7. Return promise.
p
}
pub(crate) fn fire_timer(&self, handle: TimerEventId, can_gc: CanGc) { pub(crate) fn fire_timer(&self, handle: TimerEventId, can_gc: CanGc) {
self.timers().fire_timer(handle, self, can_gc); self.timers().fire_timer(handle, self, can_gc);
} }

View file

@ -4,24 +4,31 @@
use std::cell::{Cell, Ref}; use std::cell::{Cell, Ref};
use std::collections::HashMap; use std::collections::HashMap;
use std::rc::Rc;
use base::id::{ImageBitmapId, ImageBitmapIndex}; use base::id::{ImageBitmapId, ImageBitmapIndex};
use constellation_traits::SerializableImageBitmap; use constellation_traits::SerializableImageBitmap;
use dom_struct::dom_struct; use dom_struct::dom_struct;
use euclid::default::{Point2D, Rect, Size2D}; use euclid::default::{Point2D, Rect, Size2D};
use pixels::{Snapshot, SnapshotAlphaMode, SnapshotPixelFormat}; use pixels::{CorsStatus, PixelFormat, Snapshot, SnapshotAlphaMode, SnapshotPixelFormat};
use script_bindings::error::Error;
use script_bindings::realms::{AlreadyInRealm, InRealm};
use crate::dom::bindings::cell::DomRefCell; use crate::dom::bindings::cell::DomRefCell;
use crate::dom::bindings::codegen::Bindings::ImageBitmapBinding::{ use crate::dom::bindings::codegen::Bindings::ImageBitmapBinding::{
ImageBitmapMethods, ImageBitmapOptions, ImageOrientation, PremultiplyAlpha, ResizeQuality, ImageBitmapMethods, ImageBitmapOptions, ImageBitmapSource, ImageOrientation, PremultiplyAlpha,
ResizeQuality,
}; };
use crate::dom::bindings::refcounted::Trusted;
use crate::dom::bindings::reflector::{Reflector, reflect_dom_object}; use crate::dom::bindings::reflector::{Reflector, reflect_dom_object};
use crate::dom::bindings::root::DomRoot; use crate::dom::bindings::root::DomRoot;
use crate::dom::bindings::serializable::Serializable; use crate::dom::bindings::serializable::Serializable;
use crate::dom::bindings::structuredclone::StructuredData; use crate::dom::bindings::structuredclone::StructuredData;
use crate::dom::bindings::transferable::Transferable; use crate::dom::bindings::transferable::Transferable;
use crate::dom::globalscope::GlobalScope; use crate::dom::globalscope::GlobalScope;
use crate::dom::types::Promise;
use crate::script_runtime::CanGc; use crate::script_runtime::CanGc;
use crate::test::TrustedPromise;
#[dom_struct] #[dom_struct]
pub(crate) struct ImageBitmap { pub(crate) struct ImageBitmap {
@ -265,6 +272,378 @@ impl ImageBitmap {
// Step 11. Return output. // Step 11. Return output.
Some(output) Some(output)
} }
/// <https://html.spec.whatwg.org/multipage/#dom-createimagebitmap>
#[allow(clippy::too_many_arguments)]
pub(crate) fn create_image_bitmap(
global_scope: &GlobalScope,
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(|h| h == 0) {
p.reject_error(Error::InvalidState, can_gc);
return p;
}
// The promise with image bitmap should be fulfilled on the the bitmap task source.
let fullfill_promise_on_bitmap_task_source =
|promise: &Rc<Promise>, image_bitmap: &ImageBitmap| {
let trusted_promise = TrustedPromise::new(promise.clone());
let trusted_image_bitmap = Trusted::new(image_bitmap);
global_scope.task_manager().bitmap_task_source().queue(
task!(resolve_promise: move || {
let promise = trusted_promise.root();
let image_bitmap = trusted_image_bitmap.root();
promise.resolve_native(&image_bitmap, CanGc::note());
}),
);
};
// The promise with "InvalidStateError" DOMException should be rejected
// on the the bitmap task source.
let reject_promise_on_bitmap_task_source = |promise: &Rc<Promise>| {
let trusted_promise = TrustedPromise::new(promise.clone());
global_scope
.task_manager()
.bitmap_task_source()
.queue(task!(reject_promise: move || {
let promise = trusted_promise.root();
promise.reject_error(Error::InvalidState, CanGc::note());
}));
};
// 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::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;
}
// 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;
};
// TODO: Support vector HTMLImageElement.
let Some(img) = img.as_raster_image() else {
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 => SnapshotPixelFormat::BGRA,
PixelFormat::RGBA8 => SnapshotPixelFormat::RGBA,
pixel_format => {
unimplemented!("unsupported pixel format ({:?})", pixel_format)
},
};
let alpha_mode = SnapshotAlphaMode::Transparent {
premultiplied: false,
};
let snapshot = Snapshot::from_vec(
size.cast(),
format,
alpha_mode,
img.first_frame().bytes.to_vec(),
);
// Step 6.3. Set imageBitmap's bitmap data to a copy of image's media data,
// cropped to the source rectangle with formatting.
let Some(bitmap_data) =
ImageBitmap::crop_and_transform_bitmap_data(snapshot, sx, sy, sw, sh, options)
else {
p.reject_error(Error::InvalidState, can_gc);
return p;
};
let image_bitmap = Self::new(global_scope, bitmap_data, can_gc);
// Step 6.4. If image is not origin-clean, then set the origin-clean flag
// of imageBitmap's bitmap to false.
image_bitmap.set_origin_clean(image.same_origin(GlobalScope::entry().origin()));
// Step 6.5. Queue a global task, using the bitmap task source,
// to resolve promise with imageBitmap.
fullfill_promise_on_bitmap_task_source(&p, &image_bitmap);
},
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;
}
// Step 6.1. If image's networkState attribute is NETWORK_EMPTY, then return
// a promise rejected with an "InvalidStateError" DOMException.
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;
};
// Step 6.2. Set imageBitmap's bitmap data to a copy of the frame at the current
// playback position, at the media resource's natural width and natural height
// (i.e., after any aspect-ratio correction has been applied),
// cropped to the source rectangle with formatting.
let Some(bitmap_data) =
ImageBitmap::crop_and_transform_bitmap_data(snapshot, sx, sy, sw, sh, options)
else {
p.reject_error(Error::InvalidState, can_gc);
return p;
};
let image_bitmap = Self::new(global_scope, bitmap_data, can_gc);
// Step 6.3. If image is not origin-clean, then set the origin-clean flag
// of imageBitmap's bitmap to false.
image_bitmap.set_origin_clean(video.origin_is_clean());
// Step 6.4. Queue a global task, using the bitmap task source,
// to resolve promise with imageBitmap.
fullfill_promise_on_bitmap_task_source(&p, &image_bitmap);
},
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;
};
// Step 6.1. Set imageBitmap's bitmap data to a copy of image's bitmap data,
// cropped to the source rectangle with formatting.
let Some(bitmap_data) =
ImageBitmap::crop_and_transform_bitmap_data(snapshot, sx, sy, sw, sh, options)
else {
p.reject_error(Error::InvalidState, can_gc);
return p;
};
let image_bitmap = Self::new(global_scope, bitmap_data, can_gc);
// Step 6.2. Set the origin-clean flag of the imageBitmap's bitmap to the same value
// as the origin-clean flag of image's bitmap.
image_bitmap.set_origin_clean(canvas.origin_is_clean());
// Step 6.3. Queue a global task, using the bitmap task source,
// to resolve promise with imageBitmap.
fullfill_promise_on_bitmap_task_source(&p, &image_bitmap);
},
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;
};
// Step 6.1. Set imageBitmap's bitmap data to a copy of image's bitmap data,
// cropped to the source rectangle with formatting.
let Some(bitmap_data) =
ImageBitmap::crop_and_transform_bitmap_data(snapshot, sx, sy, sw, sh, options)
else {
p.reject_error(Error::InvalidState, can_gc);
return p;
};
let image_bitmap = Self::new(global_scope, bitmap_data, can_gc);
// Step 6.2. Set the origin-clean flag of imageBitmap's bitmap to the same value
// as the origin-clean flag of image's bitmap.
image_bitmap.set_origin_clean(bitmap.origin_is_clean());
// Step 6.3. Queue a global task, using the bitmap task source,
// to resolve promise with imageBitmap.
fullfill_promise_on_bitmap_task_source(&p, &image_bitmap);
},
ImageBitmapSource::OffscreenCanvas(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;
};
// Step 6.1. Set imageBitmap's bitmap data to a copy of image's bitmap data,
// cropped to the source rectangle with formatting.
let Some(bitmap_data) =
ImageBitmap::crop_and_transform_bitmap_data(snapshot, sx, sy, sw, sh, options)
else {
p.reject_error(Error::InvalidState, can_gc);
return p;
};
let image_bitmap = Self::new(global_scope, bitmap_data, can_gc);
// Step 6.2. Set the origin-clean flag of the imageBitmap's bitmap to the same value
// as the origin-clean flag of image's bitmap.
image_bitmap.set_origin_clean(canvas.origin_is_clean());
// Step 6.3. Queue a global task, using the bitmap task source,
// to resolve promise with imageBitmap.
fullfill_promise_on_bitmap_task_source(&p, &image_bitmap);
},
ImageBitmapSource::Blob(ref blob) => {
// Step 6.1. Let imageData be the result of reading image's data.
// If an error occurs during reading of the object, then queue
// a global task, using the bitmap task source, to reject promise
// with an "InvalidStateError" DOMException and abort these steps.
let Ok(bytes) = blob.get_bytes() else {
reject_promise_on_bitmap_task_source(&p);
return p;
};
// Step 6.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.
// Step 6.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 queue
// a global task, using the bitmap task source, to reject promise
// with an "InvalidStateError" DOMException and abort these steps.
let Some(img) = pixels::load_from_memory(&bytes, CorsStatus::Safe) else {
reject_promise_on_bitmap_task_source(&p);
return p;
};
let size = Size2D::new(img.metadata.width, img.metadata.height);
let format = match img.format {
PixelFormat::BGRA8 => SnapshotPixelFormat::BGRA,
PixelFormat::RGBA8 => SnapshotPixelFormat::RGBA,
pixel_format => {
unimplemented!("unsupported pixel format ({:?})", pixel_format)
},
};
let alpha_mode = SnapshotAlphaMode::Transparent {
premultiplied: false,
};
let snapshot = Snapshot::from_vec(
size.cast(),
format,
alpha_mode,
img.first_frame().bytes.to_vec(),
);
// Step 6.4. Set imageBitmap's bitmap data to imageData, cropped
// to the source rectangle with formatting.
let Some(bitmap_data) =
ImageBitmap::crop_and_transform_bitmap_data(snapshot, sx, sy, sw, sh, options)
else {
reject_promise_on_bitmap_task_source(&p);
return p;
};
let image_bitmap = Self::new(global_scope, bitmap_data, can_gc);
// Step 6.5. Queue a global task, using the bitmap task source,
// to resolve promise with imageBitmap.
fullfill_promise_on_bitmap_task_source(&p, &image_bitmap);
},
ImageBitmapSource::ImageData(ref image_data) => {
// Step 6.1. Let buffer be image's data attribute value's [[ViewedArrayBuffer]] internal slot.
// Step 6.2. If IsDetachedBuffer(buffer) is true, then return a promise rejected
// with an "InvalidStateError" DOMException.
if image_data.is_detached() {
p.reject_error(Error::InvalidState, can_gc);
return p;
}
let alpha_mode = SnapshotAlphaMode::Transparent {
premultiplied: false,
};
let snapshot = Snapshot::from_vec(
image_data.get_size().cast(),
SnapshotPixelFormat::RGBA,
alpha_mode,
image_data.to_vec(),
);
// Step 6.3. Set imageBitmap's bitmap data to image's image data,
// cropped to the source rectangle with formatting.
let Some(bitmap_data) =
ImageBitmap::crop_and_transform_bitmap_data(snapshot, sx, sy, sw, sh, options)
else {
p.reject_error(Error::InvalidState, can_gc);
return p;
};
let image_bitmap = Self::new(global_scope, bitmap_data, can_gc);
// Step 6.4. Queue a global task, using the bitmap task source,
// to resolve promise with imageBitmap.
fullfill_promise_on_bitmap_task_source(&p, &image_bitmap);
},
ImageBitmapSource::CSSStyleValue(_) => {
// TODO: CSSStyleValue is not part of ImageBitmapSource
// <https://html.spec.whatwg.org/multipage/#imagebitmapsource>
p.reject_error(Error::NotSupported, can_gc);
},
}
// Step 7. Return promise.
p
}
} }
impl Serializable for ImageBitmap { impl Serializable for ImageBitmap {

View file

@ -152,7 +152,7 @@ use crate::dom::storage::Storage;
#[cfg(feature = "bluetooth")] #[cfg(feature = "bluetooth")]
use crate::dom::testrunner::TestRunner; use crate::dom::testrunner::TestRunner;
use crate::dom::trustedtypepolicyfactory::TrustedTypePolicyFactory; use crate::dom::trustedtypepolicyfactory::TrustedTypePolicyFactory;
use crate::dom::types::UIEvent; use crate::dom::types::{ImageBitmap, UIEvent};
use crate::dom::webglrenderingcontext::WebGLCommandSender; use crate::dom::webglrenderingcontext::WebGLCommandSender;
#[cfg(feature = "webgpu")] #[cfg(feature = "webgpu")]
use crate::dom::webgpu::identityhub::IdentityHub; use crate::dom::webgpu::identityhub::IdentityHub;
@ -1209,9 +1209,16 @@ impl WindowMethods<crate::DomTypeHolder> for Window {
options: &ImageBitmapOptions, options: &ImageBitmapOptions,
can_gc: CanGc, can_gc: CanGc,
) -> Rc<Promise> { ) -> Rc<Promise> {
let p = self let p = ImageBitmap::create_image_bitmap(
.as_global_scope() self.as_global_scope(),
.create_image_bitmap(image, 0, 0, None, None, options, can_gc); image,
0,
0,
None,
None,
options,
can_gc,
);
p p
} }
@ -1226,7 +1233,8 @@ impl WindowMethods<crate::DomTypeHolder> for Window {
options: &ImageBitmapOptions, options: &ImageBitmapOptions,
can_gc: CanGc, can_gc: CanGc,
) -> Rc<Promise> { ) -> Rc<Promise> {
let p = self.as_global_scope().create_image_bitmap( let p = ImageBitmap::create_image_bitmap(
self.as_global_scope(),
image, image,
sx, sx,
sy, sy,

View file

@ -58,6 +58,7 @@ use crate::dom::performance::Performance;
use crate::dom::promise::Promise; use crate::dom::promise::Promise;
use crate::dom::trustedscripturl::TrustedScriptURL; use crate::dom::trustedscripturl::TrustedScriptURL;
use crate::dom::trustedtypepolicyfactory::TrustedTypePolicyFactory; use crate::dom::trustedtypepolicyfactory::TrustedTypePolicyFactory;
use crate::dom::types::ImageBitmap;
#[cfg(feature = "webgpu")] #[cfg(feature = "webgpu")]
use crate::dom::webgpu::identityhub::IdentityHub; use crate::dom::webgpu::identityhub::IdentityHub;
use crate::dom::window::{base64_atob, base64_btoa}; use crate::dom::window::{base64_atob, base64_btoa};
@ -465,9 +466,16 @@ impl WorkerGlobalScopeMethods<crate::DomTypeHolder> for WorkerGlobalScope {
options: &ImageBitmapOptions, options: &ImageBitmapOptions,
can_gc: CanGc, can_gc: CanGc,
) -> Rc<Promise> { ) -> Rc<Promise> {
let p = self let p = ImageBitmap::create_image_bitmap(
.upcast::<GlobalScope>() self.upcast(),
.create_image_bitmap(image, 0, 0, None, None, options, can_gc); image,
0,
0,
None,
None,
options,
can_gc,
);
p p
} }
@ -482,7 +490,8 @@ impl WorkerGlobalScopeMethods<crate::DomTypeHolder> for WorkerGlobalScope {
options: &ImageBitmapOptions, options: &ImageBitmapOptions,
can_gc: CanGc, can_gc: CanGc,
) -> Rc<Promise> { ) -> Rc<Promise> {
let p = self.upcast::<GlobalScope>().create_image_bitmap( let p = ImageBitmap::create_image_bitmap(
self.upcast(),
image, image,
sx, sx,
sy, sy,