diff --git a/components/canvas/canvas_data.rs b/components/canvas/canvas_data.rs index 99d6273813e..2667b7f6b44 100644 --- a/components/canvas/canvas_data.rs +++ b/components/canvas/canvas_data.rs @@ -28,6 +28,10 @@ use webrender_api::{ImageDescriptor, ImageDescriptorFlags, ImageFormat, ImageKey use crate::raqote_backend::Repetition; +// Asserts on WR texture cache update for zero sized image with raw data. +// https://github.com/servo/webrender/blob/main/webrender/src/texture_cache.rs#L1475 +const MIN_WR_IMAGE_SIZE: Size2D = Size2D::new(1, 1); + fn to_path(path: &[PathSegment], mut builder: Box) -> Path { let mut build_ref = PathBuilderRef { builder: &mut builder, @@ -595,6 +599,7 @@ impl<'a> CanvasData<'a> { compositor_api: CrossProcessCompositorApi, font_context: Arc, ) -> CanvasData<'a> { + let size = size.max(MIN_WR_IMAGE_SIZE); let backend = create_backend(); let draw_target = backend.create_drawtarget(size); let image_key = compositor_api.generate_image_key().unwrap(); @@ -1402,7 +1407,9 @@ impl<'a> CanvasData<'a> { } pub fn recreate(&mut self, size: Option>) { - let size = size.unwrap_or_else(|| self.drawtarget.get_size().to_u64()); + let size = size + .unwrap_or_else(|| self.drawtarget.get_size().to_u64()) + .max(MIN_WR_IMAGE_SIZE); self.drawtarget = self .backend .create_drawtarget(Size2D::new(size.width, size.height)); diff --git a/components/script/canvas_state.rs b/components/script/canvas_state.rs index 408c94c124a..e9892818e92 100644 --- a/components/script/canvas_state.rs +++ b/components/script/canvas_state.rs @@ -152,6 +152,8 @@ pub(crate) struct CanvasState { canvas_id: CanvasId, #[no_trace] image_key: ImageKey, + #[no_trace] + size: Cell>, state: DomRefCell, origin_clean: Cell, #[ignore_malloc_size_of = "Arc"] @@ -176,6 +178,7 @@ impl CanvasState { profiled_ipc::channel(global.time_profiler_chan().clone()).unwrap(); let script_to_constellation_chan = global.script_to_constellation_chan(); debug!("Asking constellation to create new canvas thread."); + let size = adjust_canvas_size(size); script_to_constellation_chan .send(ScriptToConstellationMessage::CreateCanvasPaintThread( size, sender, @@ -194,6 +197,7 @@ impl CanvasState { CanvasState { ipc_renderer, canvas_id, + size: Cell::new(size), state: DomRefCell::new(CanvasContextState::new()), origin_clean: Cell::new(true), image_cache: global.image_cache(), @@ -221,7 +225,15 @@ impl CanvasState { self.canvas_id } + pub(crate) fn is_paintable(&self) -> bool { + !self.size.get().is_empty() + } + pub(crate) fn send_canvas_2d_msg(&self, msg: Canvas2dMsg) { + if !self.is_paintable() { + return; + } + self.ipc_renderer .send(CanvasMsg::Canvas2d(msg, self.get_canvas_id())) .unwrap() @@ -229,6 +241,10 @@ impl CanvasState { /// Updates WR image and blocks on completion pub(crate) fn update_rendering(&self) { + if !self.is_paintable() { + return; + } + let (sender, receiver) = ipc::channel().unwrap(); self.ipc_renderer .send(CanvasMsg::Canvas2d( @@ -239,16 +255,27 @@ impl CanvasState { receiver.recv().unwrap(); } - // https://html.spec.whatwg.org/multipage/#concept-canvas-set-bitmap-dimensions + /// pub(crate) fn set_bitmap_dimensions(&self, size: Size2D) { self.reset_to_initial_state(); + + self.size.replace(adjust_canvas_size(size)); + self.ipc_renderer - .send(CanvasMsg::Recreate(Some(size), self.get_canvas_id())) + .send(CanvasMsg::Recreate( + Some(self.size.get()), + self.get_canvas_id(), + )) .unwrap(); } pub(crate) fn reset(&self) { self.reset_to_initial_state(); + + if !self.is_paintable() { + return; + } + self.ipc_renderer .send(CanvasMsg::Recreate(None, self.get_canvas_id())) .unwrap(); @@ -347,7 +374,6 @@ impl CanvasState { pub(crate) fn get_rect(&self, canvas_size: Size2D, rect: Rect) -> Vec { assert!(self.origin_is_clean()); - assert!(Rect::from_size(canvas_size).contains_rect(&rect)); let (sender, receiver) = ipc::channel().unwrap(); @@ -398,18 +424,22 @@ impl CanvasState { dw: Option, dh: Option, ) -> ErrorResult { + if !self.is_paintable() { + return Ok(()); + } + let result = match image { CanvasImageSource::HTMLCanvasElement(ref canvas) => { - // https://html.spec.whatwg.org/multipage/#check-the-usability-of-the-image-argument - if !canvas.is_valid() { + // + if canvas.get_size().is_empty() { return Err(Error::InvalidState); } self.draw_html_canvas_element(canvas, htmlcanvas, sx, sy, sw, sh, dx, dy, dw, dh) }, CanvasImageSource::OffscreenCanvas(ref canvas) => { - // https://html.spec.whatwg.org/multipage/#check-the-usability-of-the-image-argument - if !canvas.is_valid() { + // + if canvas.get_size().is_empty() { return Err(Error::InvalidState); } @@ -528,11 +558,6 @@ impl CanvasState { dw: Option, dh: Option, ) -> ErrorResult { - // 1. Check the usability of the image argument - if !canvas.is_valid() { - return Err(Error::InvalidState); - } - let canvas_size = canvas.get_size(); let dw = dw.unwrap_or(canvas_size.width as f64); let dh = dh.unwrap_or(canvas_size.height as f64); @@ -1403,13 +1428,13 @@ impl CanvasState { }, }; - ImageData::new( - global, - size.width, - size.height, - Some(self.get_rect(canvas_size, read_rect)), - can_gc, - ) + let data = if self.is_paintable() { + Some(self.get_rect(canvas_size, read_rect)) + } else { + None + }; + + ImageData::new(global, size.width, size.height, data, can_gc) } // https://html.spec.whatwg.org/multipage/#dom-context-2d-putimagedata @@ -1445,6 +1470,10 @@ impl CanvasState { dirty_width: i32, dirty_height: i32, ) { + if !self.is_paintable() { + return; + } + // FIXME(nox): There are many arithmetic operations here that can // overflow or underflow, this should probably be audited. @@ -2013,3 +2042,23 @@ where style.font_family.to_css_string() ) } + +fn adjust_canvas_size(size: Size2D) -> Size2D { + // Firefox limits width/height to 32767 pixels and Chromium to 65535 pixels, + // but slows down dramatically before it reaches that limit. + // We limit by area instead, giving us larger maximum dimensions, + // in exchange for a smaller maximum canvas size. + const MAX_CANVAS_AREA: u64 = 32768 * 8192; + // Max width/height to 65535 in CSS pixels. + const MAX_CANVAS_SIZE: u64 = 65535; + + if !size.is_empty() && + size.greater_than(Size2D::new(MAX_CANVAS_SIZE, MAX_CANVAS_SIZE)) + .none() && + size.area() < MAX_CANVAS_AREA + { + size + } else { + Size2D::zero() + } +} diff --git a/components/script/dom/canvasrenderingcontext2d.rs b/components/script/dom/canvasrenderingcontext2d.rs index 73052e6906e..38bd38ad511 100644 --- a/components/script/dom/canvasrenderingcontext2d.rs +++ b/components/script/dom/canvasrenderingcontext2d.rs @@ -4,7 +4,7 @@ use canvas_traits::canvas::{Canvas2dMsg, CanvasId, CanvasMsg, FromScriptMsg}; use dom_struct::dom_struct; -use euclid::default::{Point2D, Rect, Size2D}; +use euclid::default::Size2D; use profile_traits::ipc; use script_bindings::inheritance::Castable; use script_layout_interface::HTMLCanvasDataSource; @@ -74,23 +74,12 @@ impl CanvasRenderingContext2D { reflect_dom_object(boxed, global, can_gc) } - // https://html.spec.whatwg.org/multipage/#concept-canvas-set-bitmap-dimensions - pub(crate) fn set_bitmap_dimensions(&self, size: Size2D) { - self.reset_to_initial_state(); - self.canvas_state - .get_ipc_renderer() - .send(CanvasMsg::Recreate( - Some(size.to_u64()), - self.canvas_state.get_canvas_id(), - )) - .unwrap(); - } - // https://html.spec.whatwg.org/multipage/#reset-the-rendering-context-to-its-default-state fn reset_to_initial_state(&self) { self.canvas_state.reset_to_initial_state(); } + /// pub(crate) fn set_canvas_bitmap_dimensions(&self, size: Size2D) { self.canvas_state.set_bitmap_dimensions(size); } @@ -106,20 +95,17 @@ impl CanvasRenderingContext2D { pub(crate) fn send_canvas_2d_msg(&self, msg: Canvas2dMsg) { self.canvas_state.send_canvas_2d_msg(msg) } - - pub(crate) fn get_rect(&self, rect: Rect) -> Vec { - let rect = Rect::new( - Point2D::new(rect.origin.x as u64, rect.origin.y as u64), - Size2D::new(rect.size.width as u64, rect.size.height as u64), - ); - self.canvas_state.get_rect(self.canvas.size(), rect) - } } impl LayoutCanvasRenderingContextHelpers for LayoutDom<'_, CanvasRenderingContext2D> { fn canvas_data_source(self) -> HTMLCanvasDataSource { let canvas_state = &self.unsafe_get().canvas_state; - HTMLCanvasDataSource::Image(canvas_state.image_key()) + + if canvas_state.is_paintable() { + HTMLCanvasDataSource::Image(canvas_state.image_key()) + } else { + HTMLCanvasDataSource::Empty + } } } @@ -139,13 +125,11 @@ impl CanvasContext for CanvasRenderingContext2D { } fn resize(&self) { - self.set_bitmap_dimensions(self.size().cast()) + self.set_canvas_bitmap_dimensions(self.size().cast()) } fn get_image_data(&self) -> Option { - let size = self.size(); - - if size.is_empty() { + if !self.canvas_state.is_paintable() { return None; } diff --git a/components/script/dom/offscreencanvasrenderingcontext2d.rs b/components/script/dom/offscreencanvasrenderingcontext2d.rs index 2f9b52640e6..b2d0f3201ca 100644 --- a/components/script/dom/offscreencanvasrenderingcontext2d.rs +++ b/components/script/dom/offscreencanvasrenderingcontext2d.rs @@ -65,7 +65,7 @@ impl OffscreenCanvasRenderingContext2D { } pub(crate) fn set_canvas_bitmap_dimensions(&self, size: Size2D) { - self.context.set_bitmap_dimensions(size.cast()); + self.context.set_canvas_bitmap_dimensions(size.cast()); } pub(crate) fn send_canvas_2d_msg(&self, msg: Canvas2dMsg) { diff --git a/tests/wpt/meta/html/canvas/element/canvas-host/2d.canvas.host.size.large.html.ini b/tests/wpt/meta/html/canvas/element/canvas-host/2d.canvas.host.size.large.html.ini deleted file mode 100644 index f6455f9bd76..00000000000 --- a/tests/wpt/meta/html/canvas/element/canvas-host/2d.canvas.host.size.large.html.ini +++ /dev/null @@ -1,2 +0,0 @@ -[2d.canvas.host.size.large.html] - expected: CRASH diff --git a/tests/wpt/meta/html/canvas/offscreen/canvas-host/2d.canvas.host.size.large.html.ini b/tests/wpt/meta/html/canvas/offscreen/canvas-host/2d.canvas.host.size.large.html.ini deleted file mode 100644 index f6455f9bd76..00000000000 --- a/tests/wpt/meta/html/canvas/offscreen/canvas-host/2d.canvas.host.size.large.html.ini +++ /dev/null @@ -1,2 +0,0 @@ -[2d.canvas.host.size.large.html] - expected: CRASH diff --git a/tests/wpt/meta/html/canvas/offscreen/canvas-host/2d.canvas.host.size.large.worker.js.ini b/tests/wpt/meta/html/canvas/offscreen/canvas-host/2d.canvas.host.size.large.worker.js.ini deleted file mode 100644 index d571dfa4cf9..00000000000 --- a/tests/wpt/meta/html/canvas/offscreen/canvas-host/2d.canvas.host.size.large.worker.js.ini +++ /dev/null @@ -1,2 +0,0 @@ -[2d.canvas.host.size.large.worker.html] - expected: CRASH