diff --git a/components/script/canvas_state.rs b/components/script/canvas_state.rs index 6a7a6283bf4..6f9aa99b567 100644 --- a/components/script/canvas_state.rs +++ b/components/script/canvas_state.rs @@ -529,6 +529,15 @@ impl CanvasState { smoothing_enabled, )); }, + CanvasContext::Placeholder(ref context) => { + context.send_canvas_2d_msg(Canvas2dMsg::DrawImageInOther( + self.get_canvas_id(), + image_size, + dest_rect, + source_rect, + smoothing_enabled, + )); + }, _ => return Err(Error::InvalidState), } } else { diff --git a/components/script/dom/htmlcanvaselement.rs b/components/script/dom/htmlcanvaselement.rs index 4fb7a12e1f2..ad153affc00 100644 --- a/components/script/dom/htmlcanvaselement.rs +++ b/components/script/dom/htmlcanvaselement.rs @@ -58,6 +58,9 @@ use crate::dom::htmlelement::HTMLElement; use crate::dom::mediastream::MediaStream; use crate::dom::mediastreamtrack::MediaStreamTrack; use crate::dom::node::{Node, NodeTraits}; +use crate::dom::offscreencanvas::OffscreenCanvas; +use crate::dom::offscreencanvasrenderingcontext2d::OffscreenCanvasRenderingContext2D; +use crate::dom::values::UNSIGNED_LONG_MAX; use crate::dom::virtualmethods::VirtualMethods; use crate::dom::webgl2renderingcontext::WebGL2RenderingContext; use crate::dom::webglrenderingcontext::WebGLRenderingContext; @@ -105,6 +108,7 @@ impl EncodedImageType { #[crown::unrooted_must_root_lint::must_root] #[derive(Clone, JSTraceable, MallocSizeOf)] pub(crate) enum CanvasContext { + Placeholder(Dom), Context2d(Dom), WebGL(Dom), WebGL2(Dom), @@ -165,6 +169,9 @@ impl HTMLCanvasElement { CanvasContext::WebGL2(ref context) => context.recreate(size), #[cfg(feature = "webgpu")] CanvasContext::WebGPU(ref context) => context.resize(), + CanvasContext::Placeholder(ref context) => { + context.set_canvas_bitmap_dimensions(size.to_u64()) + }, } } } @@ -179,6 +186,26 @@ impl HTMLCanvasElement { _ => true, } } + + pub(crate) fn set_natural_width(&self, value: u32) { + let value = if value > UNSIGNED_LONG_MAX { + DEFAULT_WIDTH + } else { + value + }; + let element = self.upcast::(); + element.set_uint_attribute(&html5ever::local_name!("width"), value, CanGc::note()); + } + + pub(crate) fn set_natural_height(&self, value: u32) { + let value = if value > UNSIGNED_LONG_MAX { + DEFAULT_HEIGHT + } else { + value + }; + let element = self.upcast::(); + element.set_uint_attribute(&html5ever::local_name!("height"), value, CanGc::note()); + } } pub(crate) trait LayoutCanvasRenderingContextHelpers { @@ -202,7 +229,7 @@ impl LayoutHTMLCanvasElementHelpers for LayoutDom<'_, HTMLCanvasElement> { Some(CanvasContext::WebGL2(context)) => context.to_layout().canvas_data_source(), #[cfg(feature = "webgpu")] Some(CanvasContext::WebGPU(context)) => context.to_layout().canvas_data_source(), - None => HTMLCanvasDataSource::Empty, + Some(CanvasContext::Placeholder(_)) | None => HTMLCanvasDataSource::Empty, } }; @@ -397,6 +424,17 @@ impl HTMLCanvasElement { // TODO: add a method in GPUCanvasContext to get the pixels. return None; }, + Some(CanvasContext::Placeholder(context)) => { + let (sender, receiver) = + ipc::channel(self.global().time_profiler_chan().clone()).unwrap(); + let msg = CanvasMsg::FromScript( + FromScriptMsg::SendPixels(sender), + context.get_canvas_id(), + ); + context.get_ipc_renderer().send(msg).unwrap(); + + Some(receiver.recv().unwrap()) + }, None => None, }; @@ -415,7 +453,7 @@ impl HTMLCanvasElement { //TODO: Add method get_image_data to GPUCanvasContext #[cfg(feature = "webgpu")] Some(CanvasContext::WebGPU(_)) => None, - None => { + Some(CanvasContext::Placeholder(_)) | None => { // Each pixel is fully-transparent black. Some(vec![0; (self.Width() * self.Height() * 4) as usize]) }, @@ -481,23 +519,57 @@ impl HTMLCanvasElementMethods for HTMLCanvasElement { make_uint_getter!(Width, "width", DEFAULT_WIDTH); // https://html.spec.whatwg.org/multipage/#dom-canvas-width - make_uint_setter!(SetWidth, "width", DEFAULT_WIDTH); + // When setting the value of the width or height attribute, if the context mode of the canvas element + // is set to placeholder, the user agent must throw an "InvalidStateError" DOMException and leave the + // attribute's value unchanged. + fn SetWidth(&self, value: u32) -> Fallible<()> { + if let Some(CanvasContext::Placeholder(_)) = *self.context.borrow() { + return Err(Error::InvalidState); + } + + let value = if value > UNSIGNED_LONG_MAX { + DEFAULT_WIDTH + } else { + value + }; + let element = self.upcast::(); + element.set_uint_attribute(&html5ever::local_name!("width"), value, CanGc::note()); + Ok(()) + } // https://html.spec.whatwg.org/multipage/#dom-canvas-height make_uint_getter!(Height, "height", DEFAULT_HEIGHT); // https://html.spec.whatwg.org/multipage/#dom-canvas-height - make_uint_setter!(SetHeight, "height", DEFAULT_HEIGHT); + fn SetHeight(&self, value: u32) -> Fallible<()> { + if let Some(CanvasContext::Placeholder(_)) = *self.context.borrow() { + return Err(Error::InvalidState); + } - // https://html.spec.whatwg.org/multipage/#dom-canvas-getcontext + let value = if value > UNSIGNED_LONG_MAX { + DEFAULT_HEIGHT + } else { + value + }; + let element = self.upcast::(); + element.set_uint_attribute(&html5ever::local_name!("height"), value, CanGc::note()); + Ok(()) + } + + /// fn GetContext( &self, cx: JSContext, id: DOMString, options: HandleValue, can_gc: CanGc, - ) -> Option { - match &*id { + ) -> Fallible> { + // Always throw an InvalidState exception when the canvas is in Placeholder mode (See table in the spec). + if let Some(CanvasContext::Placeholder(_)) = *self.context.borrow() { + return Err(Error::InvalidState); + } + + Ok(match &*id { "2d" => self .get_or_init_2d_context() .map(RenderingContext::CanvasRenderingContext2D), @@ -512,7 +584,7 @@ impl HTMLCanvasElementMethods for HTMLCanvasElement { .get_or_init_webgpu_context() .map(RenderingContext::GPUCanvasContext), _ => None, - } + }) } /// @@ -619,6 +691,38 @@ impl HTMLCanvasElementMethods for HTMLCanvasElement { Ok(()) } + /// + fn TransferControlToOffscreen(&self) -> Fallible> { + if self.context.borrow().is_some() { + // Step 1. + // If this canvas element's context mode is not set to none, throw an "InvalidStateError" DOMException. + return Err(Error::InvalidState); + }; + + // Step 2. + // Let offscreenCanvas be a new OffscreenCanvas object with its width and height equal to the values of + // the width and height content attributes of this canvas element. + // Step 3. + // Set the placeholder canvas element of offscreenCanvas to a weak reference to this canvas element. + let offscreen_canvas = OffscreenCanvas::new( + &self.global(), + None, + self.Width().into(), + self.Height().into(), + Some(&Dom::from_ref(self)), + CanGc::note(), + ); + // Step 4. Set this canvas element's context mode to placeholder. + if let Some(ctx) = offscreen_canvas.get_or_init_2d_context() { + *self.context.borrow_mut() = Some(CanvasContext::Placeholder(ctx.as_traced())); + } else { + return Err(Error::InvalidState); + } + + // Step 5. Return offscreenCanvas. + Ok(offscreen_canvas) + } + /// fn CaptureStream( &self, diff --git a/components/script/dom/offscreencanvas.rs b/components/script/dom/offscreencanvas.rs index d57599217ca..41b862ce118 100644 --- a/components/script/dom/offscreencanvas.rs +++ b/components/script/dom/offscreencanvas.rs @@ -57,7 +57,7 @@ impl OffscreenCanvas { } } - fn new( + pub(crate) fn new( global: &GlobalScope, proto: Option, width: u64, @@ -115,8 +115,9 @@ impl OffscreenCanvas { Some((data, size.to_u32())) } - #[allow(unsafe_code)] - fn get_or_init_2d_context(&self) -> Option> { + pub(crate) fn get_or_init_2d_context( + &self, + ) -> Option> { if let Some(ctx) = self.context() { return match *ctx { OffscreenCanvasContext::OffscreenContext2d(ref ctx) => Some(DomRoot::from_ref(ctx)), @@ -190,6 +191,10 @@ impl OffscreenCanvasMethods for OffscreenCanvas { }, } } + + if let Some(canvas) = &self.placeholder { + canvas.set_natural_width(value as _); + } } // https://html.spec.whatwg.org/multipage/#dom-offscreencanvas-height @@ -208,5 +213,9 @@ impl OffscreenCanvasMethods for OffscreenCanvas { }, } } + + if let Some(canvas) = &self.placeholder { + canvas.set_natural_height(value as _); + } } } diff --git a/components/script/dom/webidls/HTMLCanvasElement.webidl b/components/script/dom/webidls/HTMLCanvasElement.webidl index 84bfab7d587..cbcb6b4d030 100644 --- a/components/script/dom/webidls/HTMLCanvasElement.webidl +++ b/components/script/dom/webidls/HTMLCanvasElement.webidl @@ -12,9 +12,10 @@ typedef (CanvasRenderingContext2D interface HTMLCanvasElement : HTMLElement { [HTMLConstructor] constructor(); - [CEReactions, Pure] attribute unsigned long width; - [CEReactions, Pure] attribute unsigned long height; + [CEReactions, Pure, SetterThrows] attribute unsigned long width; + [CEReactions, Pure, SetterThrows] attribute unsigned long height; + [Throws] RenderingContext? getContext(DOMString contextId, optional any options = null); [Throws] @@ -22,7 +23,9 @@ interface HTMLCanvasElement : HTMLElement { [Throws] undefined toBlob(BlobCallback callback, optional DOMString type = "image/png", optional any quality); - //OffscreenCanvas transferControlToOffscreen(); + + [Throws] + OffscreenCanvas transferControlToOffscreen(); }; partial interface HTMLCanvasElement { diff --git a/tests/wpt/meta/html/canvas/offscreen/manual/the-offscreen-canvas/offscreencanvas.resize.html.ini b/tests/wpt/meta/html/canvas/offscreen/manual/the-offscreen-canvas/offscreencanvas.resize.html.ini index 0f0ce8f08e8..56771e2c3f4 100644 --- a/tests/wpt/meta/html/canvas/offscreen/manual/the-offscreen-canvas/offscreencanvas.resize.html.ini +++ b/tests/wpt/meta/html/canvas/offscreen/manual/the-offscreen-canvas/offscreencanvas.resize.html.ini @@ -8,14 +8,5 @@ [Verify that resizing an OffscreenCanvas with a 2d context propagates the new size to its placeholder canvas asynchronously.] expected: FAIL - [Verify that drawImage uses the size of the frame as the intinsic size of a placeholder canvas.] - expected: FAIL - [Verify that writing to the width and height attributes of an OffscreenCanvas works when there is a webgl context attached.] expected: FAIL - - [Verify that writing to the width or height attribute of a placeholder canvas throws an exception] - expected: FAIL - - [Verify that writing to the width or height attribute of a placeholder canvas throws an exception even when not changing the value of the attribute.] - expected: FAIL diff --git a/tests/wpt/meta/html/canvas/offscreen/manual/the-offscreen-canvas/offscreencanvas.transfer.lowlatency.nocrash.html.ini b/tests/wpt/meta/html/canvas/offscreen/manual/the-offscreen-canvas/offscreencanvas.transfer.lowlatency.nocrash.html.ini deleted file mode 100644 index 2592d8b2a72..00000000000 --- a/tests/wpt/meta/html/canvas/offscreen/manual/the-offscreen-canvas/offscreencanvas.transfer.lowlatency.nocrash.html.ini +++ /dev/null @@ -1,3 +0,0 @@ -[offscreencanvas.transfer.lowlatency.nocrash.html] - [Tests that transferring a low latency canvas does not cause a crash. See crbug.com/1255153] - expected: FAIL diff --git a/tests/wpt/meta/html/canvas/offscreen/manual/the-offscreen-canvas/offscreencanvas.transfercontrol.to.offscreen.html.ini b/tests/wpt/meta/html/canvas/offscreen/manual/the-offscreen-canvas/offscreencanvas.transfercontrol.to.offscreen.html.ini deleted file mode 100644 index c103d8d158b..00000000000 --- a/tests/wpt/meta/html/canvas/offscreen/manual/the-offscreen-canvas/offscreencanvas.transfercontrol.to.offscreen.html.ini +++ /dev/null @@ -1,10 +0,0 @@ -[offscreencanvas.transfercontrol.to.offscreen.html] - [Test that calling transferControlToOffscreen twice throws an exception] - expected: FAIL - - [Test that an OffscreenCanvas generated by transferControlToOffscreen gets correct width and height] - expected: FAIL - - [Test that calling getContext on a placeholder canvas that has already transferred its control throws an exception] - expected: FAIL - diff --git a/tests/wpt/meta/html/dom/idlharness.https.html.ini b/tests/wpt/meta/html/dom/idlharness.https.html.ini index 07deed81ae0..cfe491b292d 100644 --- a/tests/wpt/meta/html/dom/idlharness.https.html.ini +++ b/tests/wpt/meta/html/dom/idlharness.https.html.ini @@ -7915,12 +7915,6 @@ [HTMLSlotElement interface: calling assign((Element or Text)...) on document.createElement("slot") with too few arguments must throw TypeError] expected: FAIL - [HTMLCanvasElement interface: operation transferControlToOffscreen()] - expected: FAIL - - [HTMLCanvasElement interface: document.createElement("canvas") must inherit property "transferControlToOffscreen()" with the proper type] - expected: FAIL - [HTMLMarqueeElement interface: existence and properties of interface object] expected: FAIL