canvas: Make 2D context state creation failable and use dom_canvas_backend pref for backend selection (#38310)

Before script just crashed in those cases because IPCSender was dropped,
now we send `None` to tell script about the failure and fail getContext
or registerPainter accordingly.
This PR also unifies `dom_canvas_{backends}_enabled` prefs into
`dom_canvas_backend` which is more flexible in multi-backends scenarios.

Reviewable per commit.

Testing: Added servo specific WPT test.

---------

Signed-off-by: sagudev <16504129+sagudev@users.noreply.github.com>
This commit is contained in:
sagudev 2025-07-28 11:13:07 +02:00 committed by GitHub
parent 93d234d270
commit ae69646371
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
17 changed files with 116 additions and 61 deletions

View file

@ -221,7 +221,7 @@ pub(crate) struct CanvasState {
}
impl CanvasState {
pub(crate) fn new(global: &GlobalScope, size: Size2D<u64>) -> CanvasState {
pub(crate) fn new(global: &GlobalScope, size: Size2D<u64>) -> Option<CanvasState> {
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<CanvasMsg> {

View file

@ -68,12 +68,12 @@ impl CanvasRenderingContext2D {
global: &GlobalScope,
canvas: HTMLCanvasElementOrOffscreenCanvas,
size: Size2D<u32>,
) -> CanvasRenderingContext2D {
) -> Option<CanvasRenderingContext2D> {
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<u32>,
can_gc: CanGc,
) -> DomRoot<CanvasRenderingContext2D> {
) -> Option<DomRoot<CanvasRenderingContext2D>> {
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

View file

@ -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)

View file

@ -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,
)));

View file

@ -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<OffscreenCanvasRenderingContext2D> {
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<OffscreenCanvasRenderingContext2D> {
) -> Option<DomRoot<OffscreenCanvasRenderingContext2D>> {
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) {

View file

@ -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<PaintRenderingContext2D> {
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<PaintRenderingContext2D> {
reflect_dom_object(
Box::new(PaintRenderingContext2D::new_inherited(global)),
) -> Option<DomRoot<PaintRenderingContext2D>> {
Some(reflect_dom_object(
Box::new(PaintRenderingContext2D::new_inherited(global)?),
global,
can_gc,
)
))
}
/// Send update to canvas paint thread and returns [`ImageKey`]

View file

@ -563,7 +563,9 @@ impl PaintWorkletGlobalScopeMethods<crate::DomTypeHolder> 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(),