diff --git a/components/script/dom/globalscope.rs b/components/script/dom/globalscope.rs index ceb4064c905..bf9e8ae923c 100644 --- a/components/script/dom/globalscope.rs +++ b/components/script/dom/globalscope.rs @@ -29,7 +29,6 @@ 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}; @@ -60,7 +59,6 @@ use net_traits::{ CoreResourceMsg, CoreResourceThread, FetchResponseListener, IpcSend, ReferrerPolicy, 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 script_bindings::interfaces::GlobalScopeHelpers; 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::EventSourceBinding::EventSource_Binding::EventSourceMethods; 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::PermissionStatusBinding::{ PermissionName, PermissionState, @@ -117,8 +112,6 @@ use crate::dom::eventsource::EventSource; use crate::dom::eventtarget::EventTarget; use crate::dom::file::File; use crate::dom::htmlscriptelement::{ScriptId, SourceCode}; -use crate::dom::imagebitmap::ImageBitmap; -use crate::dom::messageevent::MessageEvent; use crate::dom::messageport::MessagePort; use crate::dom::node::{Node, NodeTraits}; use crate::dom::paintworkletglobalscope::PaintWorkletGlobalScope; @@ -129,6 +122,7 @@ use crate::dom::readablestream::{CrossRealmTransformReadable, ReadableStream}; use crate::dom::serviceworker::ServiceWorker; use crate::dom::serviceworkerregistration::ServiceWorkerRegistration; use crate::dom::trustedtypepolicyfactory::TrustedTypePolicyFactory; +use crate::dom::types::MessageEvent; use crate::dom::underlyingsourcecontainer::UnderlyingSourceType; #[cfg(feature = "webgpu")] use crate::dom::webgpu::gpudevice::GPUDevice; @@ -141,7 +135,7 @@ use crate::dom::writablestream::CrossRealmTransformWritable; use crate::messaging::{CommonScriptMsg, ScriptEventLoopReceiver, ScriptEventLoopSender}; use crate::microtask::{Microtask, MicrotaskQueue, UserMicrotask}; use crate::network_listener::{NetworkListener, PreInvoke}; -use crate::realms::{AlreadyInRealm, InRealm, enter_realm}; +use crate::realms::{InRealm, enter_realm}; use crate::script_module::{ DynamicModuleList, ImportMap, ModuleScript, ModuleTree, ScriptFetchOptions, }; @@ -2984,377 +2978,6 @@ impl GlobalScope { result == CheckResult::Blocked } - /// - #[allow(clippy::too_many_arguments)] - pub(crate) fn create_image_bitmap( - &self, - image: ImageBitmapSource, - sx: i32, - sy: i32, - sw: Option, - sh: Option, - options: &ImageBitmapOptions, - can_gc: CanGc, - ) -> Rc { - let in_realm_proof = AlreadyInRealm::assert::(); - 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, 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| { - 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) => { - // - 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) => { - // - 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) => { - // - 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) => { - // - 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) => { - // - 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 - // - p.reject_error(Error::NotSupported, can_gc); - }, - } - - // Step 7. Return promise. - p - } - pub(crate) fn fire_timer(&self, handle: TimerEventId, can_gc: CanGc) { self.timers().fire_timer(handle, self, can_gc); } diff --git a/components/script/dom/imagebitmap.rs b/components/script/dom/imagebitmap.rs index 76e111cb99f..27a38264eed 100644 --- a/components/script/dom/imagebitmap.rs +++ b/components/script/dom/imagebitmap.rs @@ -4,24 +4,31 @@ use std::cell::{Cell, Ref}; use std::collections::HashMap; +use std::rc::Rc; use base::id::{ImageBitmapId, ImageBitmapIndex}; use constellation_traits::SerializableImageBitmap; use dom_struct::dom_struct; 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::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::root::DomRoot; use crate::dom::bindings::serializable::Serializable; use crate::dom::bindings::structuredclone::StructuredData; use crate::dom::bindings::transferable::Transferable; use crate::dom::globalscope::GlobalScope; +use crate::dom::types::Promise; use crate::script_runtime::CanGc; +use crate::test::TrustedPromise; #[dom_struct] pub(crate) struct ImageBitmap { @@ -265,6 +272,378 @@ impl ImageBitmap { // Step 11. Return output. Some(output) } + + /// + #[allow(clippy::too_many_arguments)] + pub(crate) fn create_image_bitmap( + global_scope: &GlobalScope, + image: ImageBitmapSource, + sx: i32, + sy: i32, + sw: Option, + sh: Option, + options: &ImageBitmapOptions, + can_gc: CanGc, + ) -> Rc { + let in_realm_proof = AlreadyInRealm::assert::(); + 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, 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| { + 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) => { + // + 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) => { + // + 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) => { + // + 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) => { + // + 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) => { + // + 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 + // + p.reject_error(Error::NotSupported, can_gc); + }, + } + + // Step 7. Return promise. + p + } } impl Serializable for ImageBitmap { diff --git a/components/script/dom/window.rs b/components/script/dom/window.rs index 46709366478..9ce257244f2 100644 --- a/components/script/dom/window.rs +++ b/components/script/dom/window.rs @@ -152,7 +152,7 @@ use crate::dom::storage::Storage; #[cfg(feature = "bluetooth")] use crate::dom::testrunner::TestRunner; use crate::dom::trustedtypepolicyfactory::TrustedTypePolicyFactory; -use crate::dom::types::UIEvent; +use crate::dom::types::{ImageBitmap, UIEvent}; use crate::dom::webglrenderingcontext::WebGLCommandSender; #[cfg(feature = "webgpu")] use crate::dom::webgpu::identityhub::IdentityHub; @@ -1209,9 +1209,16 @@ impl WindowMethods for Window { options: &ImageBitmapOptions, can_gc: CanGc, ) -> Rc { - let p = self - .as_global_scope() - .create_image_bitmap(image, 0, 0, None, None, options, can_gc); + let p = ImageBitmap::create_image_bitmap( + self.as_global_scope(), + image, + 0, + 0, + None, + None, + options, + can_gc, + ); p } @@ -1226,7 +1233,8 @@ impl WindowMethods for Window { options: &ImageBitmapOptions, can_gc: CanGc, ) -> Rc { - let p = self.as_global_scope().create_image_bitmap( + let p = ImageBitmap::create_image_bitmap( + self.as_global_scope(), image, sx, sy, diff --git a/components/script/dom/workerglobalscope.rs b/components/script/dom/workerglobalscope.rs index 061e6a2777b..4cc4f95e76f 100644 --- a/components/script/dom/workerglobalscope.rs +++ b/components/script/dom/workerglobalscope.rs @@ -58,6 +58,7 @@ use crate::dom::performance::Performance; use crate::dom::promise::Promise; use crate::dom::trustedscripturl::TrustedScriptURL; use crate::dom::trustedtypepolicyfactory::TrustedTypePolicyFactory; +use crate::dom::types::ImageBitmap; #[cfg(feature = "webgpu")] use crate::dom::webgpu::identityhub::IdentityHub; use crate::dom::window::{base64_atob, base64_btoa}; @@ -465,9 +466,16 @@ impl WorkerGlobalScopeMethods for WorkerGlobalScope { options: &ImageBitmapOptions, can_gc: CanGc, ) -> Rc { - let p = self - .upcast::() - .create_image_bitmap(image, 0, 0, None, None, options, can_gc); + let p = ImageBitmap::create_image_bitmap( + self.upcast(), + image, + 0, + 0, + None, + None, + options, + can_gc, + ); p } @@ -482,7 +490,8 @@ impl WorkerGlobalScopeMethods for WorkerGlobalScope { options: &ImageBitmapOptions, can_gc: CanGc, ) -> Rc { - let p = self.upcast::().create_image_bitmap( + let p = ImageBitmap::create_image_bitmap( + self.upcast(), image, sx, sy,