canvas: pop many clips on restore (#38496)

When restoring context/state we need to pop all clips from current
state, before we just poped one (even if there was none).

Testing: Added new WPT tests

---------

Signed-off-by: sagudev <16504129+sagudev@users.noreply.github.com>
This commit is contained in:
sagudev 2025-08-07 10:23:09 +02:00 committed by GitHub
parent c055e8b456
commit c0cc8484f8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 161 additions and 11 deletions

View file

@ -749,8 +749,10 @@ impl<DrawTarget: GenericDrawTarget> CanvasData<DrawTarget> {
}
}
pub(crate) fn pop_clip(&mut self) {
self.drawtarget.pop_clip();
pub(crate) fn pop_clips(&mut self, clips: usize) {
for _ in 0..clips {
self.drawtarget.pop_clip();
}
}
}

View file

@ -282,7 +282,7 @@ impl CanvasPaintThread {
self.canvas(canvas_id).update_image_rendering();
sender.send(()).unwrap();
},
Canvas2dMsg::PopClip => self.canvas(canvas_id).pop_clip(),
Canvas2dMsg::PopClips(clips) => self.canvas(canvas_id).pop_clips(clips),
}
}
@ -348,14 +348,14 @@ impl Canvas {
}
}
fn pop_clip(&mut self) {
fn pop_clips(&mut self, clips: usize) {
match self {
#[cfg(feature = "raqote")]
Canvas::Raqote(canvas_data) => canvas_data.pop_clip(),
Canvas::Raqote(canvas_data) => canvas_data.pop_clips(clips),
#[cfg(feature = "vello")]
Canvas::Vello(canvas_data) => canvas_data.pop_clip(),
Canvas::Vello(canvas_data) => canvas_data.pop_clips(clips),
#[cfg(feature = "vello_cpu")]
Canvas::VelloCPU(canvas_data) => canvas_data.pop_clip(),
Canvas::VelloCPU(canvas_data) => canvas_data.pop_clips(clips),
_ => unreachable!(),
}
}

View file

@ -119,6 +119,9 @@ pub(crate) struct CanvasContextState {
text_baseline: TextBaseline,
#[no_trace]
direction: Direction,
/// The number of clips pushed onto the context while in this state.
/// When restoring old state, same number of clips will be popped to restore state.
clips_pushed: usize,
}
impl CanvasContextState {
@ -146,6 +149,7 @@ impl CanvasContextState {
direction: Default::default(),
line_dash: Vec::new(),
line_dash_offset: 0.0,
clips_pushed: 0,
}
}
@ -1320,12 +1324,15 @@ impl CanvasState {
}
#[cfg_attr(crown, allow(crown::unrooted_must_root))]
// https://html.spec.whatwg.org/multipage/#dom-context-2d-restore
/// <https://html.spec.whatwg.org/multipage/#dom-context-2d-restore>
pub(crate) fn restore(&self) {
let mut saved_states = self.saved_states.borrow_mut();
if let Some(state) = saved_states.pop() {
let clips_to_pop = self.state.borrow().clips_pushed;
if clips_to_pop != 0 {
self.send_canvas_2d_msg(Canvas2dMsg::PopClips(clips_to_pop));
}
self.state.borrow_mut().clone_from(&state);
self.send_canvas_2d_msg(Canvas2dMsg::PopClip);
}
}
@ -1940,6 +1947,7 @@ impl CanvasState {
// https://html.spec.whatwg.org/multipage/#dom-context-2d-clip
pub(crate) fn clip_(&self, path: Path, fill_rule: CanvasFillRule) {
self.state.borrow_mut().clips_pushed += 1;
self.send_canvas_2d_msg(Canvas2dMsg::ClipPath(
path,
fill_rule.convert(),

View file

@ -483,7 +483,7 @@ pub enum Canvas2dMsg {
),
ClearRect(Rect<f32>, Transform2D<f32>),
ClipPath(Path, FillRule, Transform2D<f32>),
PopClip,
PopClips(usize),
FillPath(
FillOrStrokeStyle,
Path,