diff --git a/components/canvas/canvas_paint_thread.rs b/components/canvas/canvas_paint_thread.rs index 3f96af28179..84e92aada61 100644 --- a/components/canvas/canvas_paint_thread.rs +++ b/components/canvas/canvas_paint_thread.rs @@ -85,8 +85,7 @@ impl CanvasPaintThread { recv(create_receiver) -> msg => { match msg { Ok(ConstellationCanvasMsg::Create { sender: creator, size }) => { - let canvas_data = canvas_paint_thread.create_canvas(size); - creator.send(canvas_data).unwrap(); + creator.send(canvas_paint_thread.create_canvas(size)).unwrap(); }, Ok(ConstellationCanvasMsg::Exit(exit_sender)) => { let _ = exit_sender.send(()); @@ -106,15 +105,15 @@ impl CanvasPaintThread { (create_sender, ipc_sender) } - pub fn create_canvas(&mut self, size: Size2D) -> (CanvasId, ImageKey) { + pub fn create_canvas(&mut self, size: Size2D) -> Option<(CanvasId, ImageKey)> { let canvas_id = self.next_canvas_id; self.next_canvas_id.0 += 1; - let canvas = Canvas::new(size, self.compositor_api.clone(), self.font_context.clone()); + let canvas = Canvas::new(size, self.compositor_api.clone(), self.font_context.clone())?; let image_key = canvas.image_key(); self.canvases.insert(canvas_id, canvas); - (canvas_id, image_key) + Some((canvas_id, image_key)) } fn process_canvas_2d_message(&mut self, message: Canvas2dMsg, canvas_id: CanvasId) { @@ -305,16 +304,33 @@ impl Canvas { size: Size2D, compositor_api: CrossProcessCompositorApi, font_context: Arc, - ) -> Self { - #[cfg(feature = "vello")] - if servo_config::pref!(dom_canvas_vello_enabled) { - return Self::Vello(CanvasData::new(size, compositor_api, font_context)); + ) -> Option { + match servo_config::pref!(dom_canvas_backend) + .to_lowercase() + .as_str() + { + #[cfg(feature = "vello")] + "vello" => Some(Self::Vello(CanvasData::new( + size, + compositor_api, + font_context, + ))), + #[cfg(feature = "vello_cpu")] + "vello_cpu" => Some(Self::VelloCPU(CanvasData::new( + size, + compositor_api, + font_context, + ))), + "" | "auto" | "raqote" => Some(Self::Raqote(CanvasData::new( + size, + compositor_api, + font_context, + ))), + s => { + warn!("Unknown 2D canvas backend: `{s}`"); + None + }, } - #[cfg(feature = "vello_cpu")] - if servo_config::pref!(dom_canvas_vello_cpu_enabled) { - return Self::VelloCPU(CanvasData::new(size, compositor_api, font_context)); - } - Self::Raqote(CanvasData::new(size, compositor_api, font_context)) } fn image_key(&self) -> ImageKey { diff --git a/components/config/prefs.rs b/components/config/prefs.rs index fe375b618db..fe19830ce21 100644 --- a/components/config/prefs.rs +++ b/components/config/prefs.rs @@ -76,10 +76,14 @@ pub struct Preferences { pub dom_allow_scripts_to_close_windows: bool, pub dom_canvas_capture_enabled: bool, pub dom_canvas_text_enabled: bool, - /// Uses vello as canvas backend - pub dom_canvas_vello_enabled: bool, - /// Uses vello_cpu as canvas backend - pub dom_canvas_vello_cpu_enabled: bool, + /// Selects canvas backend + /// + /// Available values: + /// - ` `/`auto` + /// - raqote + /// - vello + /// - vello_cpu + pub dom_canvas_backend: String, pub dom_clipboardevent_enabled: bool, pub dom_composition_event_enabled: bool, pub dom_cookiestore_enabled: bool, @@ -259,8 +263,7 @@ impl Preferences { dom_bluetooth_testing_enabled: false, dom_canvas_capture_enabled: false, dom_canvas_text_enabled: true, - dom_canvas_vello_enabled: false, - dom_canvas_vello_cpu_enabled: false, + dom_canvas_backend: String::new(), dom_clipboardevent_enabled: true, dom_composition_event_enabled: false, dom_cookiestore_enabled: false, diff --git a/components/constellation/constellation.rs b/components/constellation/constellation.rs index a4790e19d0d..75c205111e6 100644 --- a/components/constellation/constellation.rs +++ b/components/constellation/constellation.rs @@ -4368,24 +4368,32 @@ where fn handle_create_canvas_paint_thread_msg( &mut self, size: UntypedSize2D, - response_sender: IpcSender<(IpcSender, CanvasId, ImageKey)>, + response_sender: IpcSender, CanvasId, ImageKey)>>, ) { let (canvas_data_sender, canvas_data_receiver) = unbounded(); let (canvas_sender, canvas_ipc_sender) = self .canvas .get_or_init(|| self.create_canvas_paint_thread()); - if let Err(e) = canvas_sender.send(ConstellationCanvasMsg::Create { + let response = if let Err(e) = canvas_sender.send(ConstellationCanvasMsg::Create { sender: canvas_data_sender, size, }) { - return warn!("Create canvas paint thread failed ({})", e); - } - let (canvas_id, image_key) = match canvas_data_receiver.recv() { - Ok(canvas_data) => canvas_data, - Err(e) => return warn!("Create canvas paint thread id response failed ({})", e), + warn!("Create canvas paint thread failed ({})", e); + None + } else { + match canvas_data_receiver.recv() { + Ok(Some((canvas_id, image_key))) => { + Some((canvas_ipc_sender.clone(), canvas_id, image_key)) + }, + Ok(None) => None, + Err(e) => { + warn!("Create canvas paint thread id response failed ({})", e); + None + }, + } }; - if let Err(e) = response_sender.send((canvas_ipc_sender.clone(), canvas_id, image_key)) { + if let Err(e) = response_sender.send(response) { warn!("Create canvas paint thread response failed ({})", e); } } diff --git a/components/script/canvas_state.rs b/components/script/canvas_state.rs index 218526e15a4..4e77b03fdb6 100644 --- a/components/script/canvas_state.rs +++ b/components/script/canvas_state.rs @@ -221,7 +221,7 @@ pub(crate) struct CanvasState { } impl CanvasState { - pub(crate) fn new(global: &GlobalScope, size: Size2D) -> CanvasState { + pub(crate) fn new(global: &GlobalScope, size: Size2D) -> Option { debug!("Creating new canvas rendering context."); let (sender, receiver) = profiled_ipc::channel(global.time_profiler_chan().clone()).unwrap(); @@ -233,7 +233,7 @@ impl CanvasState { size, sender, )) .unwrap(); - let (ipc_renderer, canvas_id, image_key) = receiver.recv().unwrap(); + let (ipc_renderer, canvas_id, image_key) = receiver.recv().ok()??; debug!("Done."); // Worklets always receive a unique origin. This messes with fetching // cached images in the case of paint worklets, since the image cache @@ -243,7 +243,7 @@ impl CanvasState { } else { global.origin().immutable().clone() }; - CanvasState { + Some(CanvasState { ipc_renderer, canvas_id, size: Cell::new(size), @@ -256,7 +256,7 @@ impl CanvasState { image_key, origin, current_default_path: DomRefCell::new(Path::new()), - } + }) } pub(crate) fn get_ipc_renderer(&self) -> &IpcSender { diff --git a/components/script/dom/canvasrenderingcontext2d.rs b/components/script/dom/canvasrenderingcontext2d.rs index 153bf2e385b..e01566b54ae 100644 --- a/components/script/dom/canvasrenderingcontext2d.rs +++ b/components/script/dom/canvasrenderingcontext2d.rs @@ -68,12 +68,12 @@ impl CanvasRenderingContext2D { global: &GlobalScope, canvas: HTMLCanvasElementOrOffscreenCanvas, size: Size2D, - ) -> CanvasRenderingContext2D { + ) -> Option { let canvas_state = - CanvasState::new(global, Size2D::new(size.width as u64, size.height as u64)); + CanvasState::new(global, Size2D::new(size.width as u64, size.height as u64))?; let ipc_sender = canvas_state.get_ipc_renderer().clone(); let canvas_id = canvas_state.get_canvas_id(); - CanvasRenderingContext2D { + Some(CanvasRenderingContext2D { reflector_: Reflector::new(), canvas, canvas_state, @@ -81,21 +81,22 @@ impl CanvasRenderingContext2D { ipc_sender, canvas_id, }, - } + }) } + #[cfg_attr(crown, allow(crown::unrooted_must_root))] pub(crate) fn new( global: &GlobalScope, canvas: &HTMLCanvasElement, size: Size2D, can_gc: CanGc, - ) -> DomRoot { + ) -> Option> { let boxed = Box::new(CanvasRenderingContext2D::new_inherited( global, HTMLCanvasElementOrOffscreenCanvas::HTMLCanvasElement(DomRoot::from_ref(canvas)), size, - )); - reflect_dom_object(boxed, global, can_gc) + )?); + Some(reflect_dom_object(boxed, global, can_gc)) } // https://html.spec.whatwg.org/multipage/#reset-the-rendering-context-to-its-default-state diff --git a/components/script/dom/htmlcanvaselement.rs b/components/script/dom/htmlcanvaselement.rs index 136e787b083..046eacf73dc 100644 --- a/components/script/dom/htmlcanvaselement.rs +++ b/components/script/dom/htmlcanvaselement.rs @@ -205,7 +205,7 @@ impl HTMLCanvasElement { let window = self.owner_window(); let size = self.get_size(); - let context = CanvasRenderingContext2D::new(window.as_global_scope(), self, size, can_gc); + let context = CanvasRenderingContext2D::new(window.as_global_scope(), self, size, can_gc)?; *self.context_mode.borrow_mut() = Some(RenderingContext::Context2d(Dom::from_ref(&*context))); Some(context) diff --git a/components/script/dom/offscreencanvas.rs b/components/script/dom/offscreencanvas.rs index ece5d17ec1b..f4bd085e0e4 100644 --- a/components/script/dom/offscreencanvas.rs +++ b/components/script/dom/offscreencanvas.rs @@ -135,7 +135,7 @@ impl OffscreenCanvas { _ => None, }; } - let context = OffscreenCanvasRenderingContext2D::new(&self.global(), self, can_gc); + let context = OffscreenCanvasRenderingContext2D::new(&self.global(), self, can_gc)?; *self.context.borrow_mut() = Some(OffscreenRenderingContext::Context2d(Dom::from_ref( &*context, ))); diff --git a/components/script/dom/offscreencanvasrenderingcontext2d.rs b/components/script/dom/offscreencanvasrenderingcontext2d.rs index a4768f68779..242e8312871 100644 --- a/components/script/dom/offscreencanvasrenderingcontext2d.rs +++ b/components/script/dom/offscreencanvasrenderingcontext2d.rs @@ -39,29 +39,31 @@ pub(crate) struct OffscreenCanvasRenderingContext2D { } impl OffscreenCanvasRenderingContext2D { + #[cfg_attr(crown, allow(crown::unrooted_must_root))] fn new_inherited( global: &GlobalScope, canvas: &OffscreenCanvas, - ) -> OffscreenCanvasRenderingContext2D { + ) -> Option { let size = canvas.get_size().cast(); - OffscreenCanvasRenderingContext2D { + Some(OffscreenCanvasRenderingContext2D { context: CanvasRenderingContext2D::new_inherited( global, HTMLCanvasElementOrOffscreenCanvas::OffscreenCanvas(DomRoot::from_ref(canvas)), size, - ), - } + )?, + }) } + #[cfg_attr(crown, allow(crown::unrooted_must_root))] pub(crate) fn new( global: &GlobalScope, canvas: &OffscreenCanvas, can_gc: CanGc, - ) -> DomRoot { + ) -> Option> { let boxed = Box::new(OffscreenCanvasRenderingContext2D::new_inherited( global, canvas, - )); - reflect_dom_object(boxed, global, can_gc) + )?); + Some(reflect_dom_object(boxed, global, can_gc)) } pub(crate) fn send_canvas_2d_msg(&self, msg: Canvas2dMsg) { diff --git a/components/script/dom/paintrenderingcontext2d.rs b/components/script/dom/paintrenderingcontext2d.rs index b835507b807..3d5cc3b1f77 100644 --- a/components/script/dom/paintrenderingcontext2d.rs +++ b/components/script/dom/paintrenderingcontext2d.rs @@ -42,23 +42,25 @@ pub(crate) struct PaintRenderingContext2D { } impl PaintRenderingContext2D { - fn new_inherited(global: &PaintWorkletGlobalScope) -> PaintRenderingContext2D { - PaintRenderingContext2D { + #[cfg_attr(crown, allow(crown::unrooted_must_root))] + fn new_inherited(global: &PaintWorkletGlobalScope) -> Option { + Some(PaintRenderingContext2D { reflector_: Reflector::new(), - canvas_state: CanvasState::new(global.upcast(), Size2D::zero()), + canvas_state: CanvasState::new(global.upcast(), Size2D::zero())?, device_pixel_ratio: Cell::new(Scale::new(1.0)), - } + }) } + #[cfg_attr(crown, allow(crown::unrooted_must_root))] pub(crate) fn new( global: &PaintWorkletGlobalScope, can_gc: CanGc, - ) -> DomRoot { - reflect_dom_object( - Box::new(PaintRenderingContext2D::new_inherited(global)), + ) -> Option> { + Some(reflect_dom_object( + Box::new(PaintRenderingContext2D::new_inherited(global)?), global, can_gc, - ) + )) } /// Send update to canvas paint thread and returns [`ImageKey`] diff --git a/components/script/dom/paintworkletglobalscope.rs b/components/script/dom/paintworkletglobalscope.rs index 0a8046d9508..7beffb5c6bd 100644 --- a/components/script/dom/paintworkletglobalscope.rs +++ b/components/script/dom/paintworkletglobalscope.rs @@ -563,7 +563,9 @@ impl PaintWorkletGlobalScopeMethods for PaintWorkletGlobal } // Step 19. - let context = PaintRenderingContext2D::new(self, CanGc::note()); + let Some(context) = PaintRenderingContext2D::new(self, CanGc::note()) else { + return Err(Error::Operation); + }; let definition = PaintDefinition::new( paint_val.handle(), paint_function.handle(), diff --git a/components/shared/canvas/lib.rs b/components/shared/canvas/lib.rs index df6b0854b9e..6ef19f34d04 100644 --- a/components/shared/canvas/lib.rs +++ b/components/shared/canvas/lib.rs @@ -18,7 +18,7 @@ pub mod webgl; pub enum ConstellationCanvasMsg { Create { - sender: Sender<(CanvasId, ImageKey)>, + sender: Sender>, size: Size2D, }, Exit(Sender<()>), diff --git a/components/shared/constellation/from_script_message.rs b/components/shared/constellation/from_script_message.rs index b35ccf12540..1ca6aa8f9b6 100644 --- a/components/shared/constellation/from_script_message.rs +++ b/components/shared/constellation/from_script_message.rs @@ -535,7 +535,7 @@ pub enum ScriptToConstellationMessage { /// 2D canvases may use the GPU and we don't want to give untrusted content access to the GPU.) CreateCanvasPaintThread( UntypedSize2D, - IpcSender<(IpcSender, CanvasId, ImageKey)>, + IpcSender, CanvasId, ImageKey)>>, ), /// Notifies the constellation that this pipeline is requesting focus. /// diff --git a/tests/wpt/mozilla/meta/MANIFEST.json b/tests/wpt/mozilla/meta/MANIFEST.json index 3c5179ac4fc..98b01860c55 100644 --- a/tests/wpt/mozilla/meta/MANIFEST.json +++ b/tests/wpt/mozilla/meta/MANIFEST.json @@ -12970,6 +12970,13 @@ null, {} ] + ], + "invalid.html": [ + "d50ff1030c48794dfc92083b6fecde3f97524b7e", + [ + null, + {} + ] ] }, "canvas-oversize-serialization.html": [ diff --git a/tests/wpt/mozilla/meta/mozilla/canvas/invalid.html.ini b/tests/wpt/mozilla/meta/mozilla/canvas/invalid.html.ini new file mode 100644 index 00000000000..6b65f3ac79f --- /dev/null +++ b/tests/wpt/mozilla/meta/mozilla/canvas/invalid.html.ini @@ -0,0 +1,3 @@ +[invalid.html] + prefs: ["dom_canvas_backend:none"] + diff --git a/tests/wpt/mozilla/tests/mozilla/canvas/invalid.html b/tests/wpt/mozilla/tests/mozilla/canvas/invalid.html new file mode 100644 index 00000000000..d50ff1030c4 --- /dev/null +++ b/tests/wpt/mozilla/tests/mozilla/canvas/invalid.html @@ -0,0 +1,11 @@ + + +empty canvas context with invalid backend pref + + + + diff --git a/tests/wpt/vello_canvas_subsuite.json b/tests/wpt/vello_canvas_subsuite.json index 799a0de2583..1992978ba5d 100644 --- a/tests/wpt/vello_canvas_subsuite.json +++ b/tests/wpt/vello_canvas_subsuite.json @@ -1,7 +1,7 @@ { "vello_canvas": { "config": { - "binary_args": ["--pref", "dom_canvas_vello_enabled"] + "binary_args": ["--pref", "dom_canvas_backend=vello"] }, "include": ["/html/canvas/element"] } diff --git a/tests/wpt/vello_cpu_canvas_subsuite.json b/tests/wpt/vello_cpu_canvas_subsuite.json index a01f8c2b766..6784039d57f 100644 --- a/tests/wpt/vello_cpu_canvas_subsuite.json +++ b/tests/wpt/vello_cpu_canvas_subsuite.json @@ -1,7 +1,7 @@ { "vello_cpu_canvas": { "config": { - "binary_args": ["--pref", "dom_canvas_vello_cpu_enabled"] + "binary_args": ["--pref", "dom_canvas_backend=vello_cpu"] }, "include": ["/html/canvas/element"] }