From 251eeb2c2dee8aa91b69d7f451b1a1a7607a3bd1 Mon Sep 17 00:00:00 2001 From: Lukas Lihotzki Date: Wed, 26 Mar 2025 13:12:44 +0100 Subject: [PATCH] Add `Path2D` (#35783) Signed-off-by: Lukas Lihotzki --- components/canvas/canvas_data.rs | 284 ++++++++++--- components/canvas/canvas_paint_thread.rs | 14 +- components/canvas/raqote_backend.rs | 26 ++ components/script/canvas_state.rs | 45 +- .../script/dom/canvasrenderingcontext2d.rs | 24 ++ components/script/dom/mod.rs | 1 + .../dom/offscreencanvasrenderingcontext2d.rs | 21 + .../script/dom/paintrenderingcontext2d.rs | 22 + components/script/dom/path2d.rs | 313 ++++++++++++++ components/script/lib.rs | 2 + components/script/svgpath/mod.rs | 13 + components/script/svgpath/number.rs | 198 +++++++++ components/script/svgpath/path.rs | 393 ++++++++++++++++++ components/script/svgpath/stream.rs | 162 ++++++++ .../webidls/CanvasRenderingContext2D.webidl | 19 +- components/shared/canvas/canvas.rs | 59 ++- python/tidy/tidy.py | 3 + .../element/layers/2d.layer.nested.html.ini | 2 + ...distance-with-clipping.ctx-filter.html.ini | 2 + ...h-clipping.layer-filter.tentative.html.ini | 2 + ...distance-with-clipping.ctx-filter.html.ini | 2 + ...h-clipping.layer-filter.tentative.html.ini | 2 + .../offscreen/layers/2d.layer.nested.html.ini | 2 + ...distance-with-clipping.ctx-filter.html.ini | 2 + ...h-clipping.layer-filter.tentative.html.ini | 2 + ...distance-with-clipping.ctx-filter.html.ini | 2 + ...h-clipping.layer-filter.tentative.html.ini | 2 + .../element/layers/2d.layer.nested.html.ini | 2 + ...distance-with-clipping.ctx-filter.html.ini | 2 + ...h-clipping.layer-filter.tentative.html.ini | 2 + ...distance-with-clipping.ctx-filter.html.ini | 2 + ...h-clipping.layer-filter.tentative.html.ini | 2 + .../2d.path.arc.negative.html.ini | 4 - .../2d.path.arcTo.negative.html.ini | 4 - .../2d.path.isPointInpath.invalid.html.ini | 4 - .../offscreen/layers/2d.layer.nested.html.ini | 2 + ...distance-with-clipping.ctx-filter.html.ini | 2 + ...h-clipping.layer-filter.tentative.html.ini | 2 + ...distance-with-clipping.ctx-filter.html.ini | 2 + ...h-clipping.layer-filter.tentative.html.ini | 2 + .../2d.path.arc.negative.html.ini | 4 - .../2d.path.arc.negative.worker.js.ini | 4 - .../2d.path.arcTo.negative.html.ini | 4 - .../2d.path.arcTo.negative.worker.js.ini | 4 - .../2d.path.isPointInpath.invalid.html.ini | 3 - ...d.path.isPointInpath.invalid.worker.js.ini | 3 - tests/wpt/meta/html/dom/idlharness.any.js.ini | 48 --- .../meta/html/dom/idlharness.https.html.ini | 96 ----- .../the-canvas-element/historical.html.ini | 2 - .../interface-objects/001.worker.js.ini | 3 - tests/wpt/mozilla/meta/MANIFEST.json | 4 +- .../tests/mozilla/interfaces.https.html | 1 + .../tests/mozilla/interfaces.worker.js | 1 + 53 files changed, 1566 insertions(+), 262 deletions(-) create mode 100644 components/script/dom/path2d.rs create mode 100644 components/script/svgpath/mod.rs create mode 100644 components/script/svgpath/number.rs create mode 100644 components/script/svgpath/path.rs create mode 100644 components/script/svgpath/stream.rs create mode 100644 tests/wpt/meta-legacy-layout/html/canvas/element/layers/2d.layer.nested.html.ini create mode 100644 tests/wpt/meta-legacy-layout/html/canvas/element/layers/2d.layer.shadow-from-outside-canvas.long-distance-with-clipping.ctx-filter.html.ini create mode 100644 tests/wpt/meta-legacy-layout/html/canvas/element/layers/2d.layer.shadow-from-outside-canvas.long-distance-with-clipping.layer-filter.tentative.html.ini create mode 100644 tests/wpt/meta-legacy-layout/html/canvas/element/layers/2d.layer.shadow-from-outside-canvas.short-distance-with-clipping.ctx-filter.html.ini create mode 100644 tests/wpt/meta-legacy-layout/html/canvas/element/layers/2d.layer.shadow-from-outside-canvas.short-distance-with-clipping.layer-filter.tentative.html.ini create mode 100644 tests/wpt/meta-legacy-layout/html/canvas/offscreen/layers/2d.layer.nested.html.ini create mode 100644 tests/wpt/meta-legacy-layout/html/canvas/offscreen/layers/2d.layer.shadow-from-outside-canvas.long-distance-with-clipping.ctx-filter.html.ini create mode 100644 tests/wpt/meta-legacy-layout/html/canvas/offscreen/layers/2d.layer.shadow-from-outside-canvas.long-distance-with-clipping.layer-filter.tentative.html.ini create mode 100644 tests/wpt/meta-legacy-layout/html/canvas/offscreen/layers/2d.layer.shadow-from-outside-canvas.short-distance-with-clipping.ctx-filter.html.ini create mode 100644 tests/wpt/meta-legacy-layout/html/canvas/offscreen/layers/2d.layer.shadow-from-outside-canvas.short-distance-with-clipping.layer-filter.tentative.html.ini create mode 100644 tests/wpt/meta/html/canvas/element/layers/2d.layer.nested.html.ini create mode 100644 tests/wpt/meta/html/canvas/element/layers/2d.layer.shadow-from-outside-canvas.long-distance-with-clipping.ctx-filter.html.ini create mode 100644 tests/wpt/meta/html/canvas/element/layers/2d.layer.shadow-from-outside-canvas.long-distance-with-clipping.layer-filter.tentative.html.ini create mode 100644 tests/wpt/meta/html/canvas/element/layers/2d.layer.shadow-from-outside-canvas.short-distance-with-clipping.ctx-filter.html.ini create mode 100644 tests/wpt/meta/html/canvas/element/layers/2d.layer.shadow-from-outside-canvas.short-distance-with-clipping.layer-filter.tentative.html.ini delete mode 100644 tests/wpt/meta/html/canvas/element/path-objects/2d.path.arc.negative.html.ini delete mode 100644 tests/wpt/meta/html/canvas/element/path-objects/2d.path.arcTo.negative.html.ini delete mode 100644 tests/wpt/meta/html/canvas/element/path-objects/2d.path.isPointInpath.invalid.html.ini create mode 100644 tests/wpt/meta/html/canvas/offscreen/layers/2d.layer.nested.html.ini create mode 100644 tests/wpt/meta/html/canvas/offscreen/layers/2d.layer.shadow-from-outside-canvas.long-distance-with-clipping.ctx-filter.html.ini create mode 100644 tests/wpt/meta/html/canvas/offscreen/layers/2d.layer.shadow-from-outside-canvas.long-distance-with-clipping.layer-filter.tentative.html.ini create mode 100644 tests/wpt/meta/html/canvas/offscreen/layers/2d.layer.shadow-from-outside-canvas.short-distance-with-clipping.ctx-filter.html.ini create mode 100644 tests/wpt/meta/html/canvas/offscreen/layers/2d.layer.shadow-from-outside-canvas.short-distance-with-clipping.layer-filter.tentative.html.ini delete mode 100644 tests/wpt/meta/html/canvas/offscreen/path-objects/2d.path.arc.negative.html.ini delete mode 100644 tests/wpt/meta/html/canvas/offscreen/path-objects/2d.path.arc.negative.worker.js.ini delete mode 100644 tests/wpt/meta/html/canvas/offscreen/path-objects/2d.path.arcTo.negative.html.ini delete mode 100644 tests/wpt/meta/html/canvas/offscreen/path-objects/2d.path.arcTo.negative.worker.js.ini delete mode 100644 tests/wpt/meta/html/canvas/offscreen/path-objects/2d.path.isPointInpath.invalid.html.ini delete mode 100644 tests/wpt/meta/html/canvas/offscreen/path-objects/2d.path.isPointInpath.invalid.worker.js.ini delete mode 100644 tests/wpt/meta/html/semantics/embedded-content/the-canvas-element/historical.html.ini diff --git a/components/canvas/canvas_data.rs b/components/canvas/canvas_data.rs index 22f1514d049..5462098abf5 100644 --- a/components/canvas/canvas_data.rs +++ b/components/canvas/canvas_data.rs @@ -27,6 +27,77 @@ use webrender_traits::{CrossProcessCompositorApi, ImageUpdate, SerializableImage use crate::raqote_backend::Repetition; +fn to_path(path: &[PathSegment], mut builder: Box) -> Path { + let mut build_ref = PathBuilderRef { + builder: &mut builder, + transform: Transform2D::identity(), + }; + for &seg in path { + match seg { + PathSegment::ClosePath => build_ref.close(), + PathSegment::MoveTo { x, y } => build_ref.move_to(&Point2D::new(x, y)), + PathSegment::LineTo { x, y } => build_ref.line_to(&Point2D::new(x, y)), + PathSegment::Quadratic { cpx, cpy, x, y } => { + build_ref.quadratic_curve_to(&Point2D::new(cpx, cpy), &Point2D::new(x, y)) + }, + PathSegment::Bezier { + cp1x, + cp1y, + cp2x, + cp2y, + x, + y, + } => build_ref.bezier_curve_to( + &Point2D::new(cp1x, cp1y), + &Point2D::new(cp2x, cp2y), + &Point2D::new(x, y), + ), + PathSegment::ArcTo { + cp1x, + cp1y, + cp2x, + cp2y, + radius, + } => build_ref.arc_to(&Point2D::new(cp1x, cp1y), &Point2D::new(cp2x, cp2y), radius), + PathSegment::Ellipse { + x, + y, + radius_x, + radius_y, + rotation, + start_angle, + end_angle, + anticlockwise, + } => build_ref.ellipse( + &Point2D::new(x, y), + radius_x, + radius_y, + rotation, + start_angle, + end_angle, + anticlockwise, + ), + PathSegment::SvgArc { + radius_x, + radius_y, + rotation, + large_arc, + sweep, + x, + y, + } => build_ref.svg_arc( + radius_x, + radius_y, + rotation, + large_arc, + sweep, + &Point2D::new(x, y), + ), + } + } + builder.finish() +} + /// The canvas data stores a state machine for the current status of /// the path data and any relevant transformations that are /// applied to it. The Azure drawing API expects the path to be in @@ -125,6 +196,15 @@ pub trait GenericPathBuilder { fn line_to(&mut self, point: Point2D); fn move_to(&mut self, point: Point2D); fn quadratic_curve_to(&mut self, control_point: &Point2D, end_point: &Point2D); + fn svg_arc( + &mut self, + radius_x: f32, + radius_y: f32, + rotation_angle: f32, + large_arc: bool, + sweep: bool, + end_point: Point2D, + ); fn finish(&mut self) -> Path; } @@ -192,6 +272,69 @@ impl PathBuilderRef<'_> { .arc(center, radius, start_angle, end_angle, ccw); } + fn arc_to(&mut self, cp1: &Point2D, cp2: &Point2D, radius: f32) { + let cp0 = if let (Some(inverse), Some(point)) = + (self.transform.inverse(), self.builder.get_current_point()) + { + inverse.transform_point(Point2D::new(point.x, point.y)) + } else { + *cp1 + }; + if (cp0.x == cp1.x && cp0.y == cp1.y) || cp1 == cp2 || radius == 0.0 { + self.line_to(cp1); + return; + } + + // if all three control points lie on a single straight line, + // connect the first two by a straight line + let direction = (cp2.x - cp1.x) * (cp0.y - cp1.y) + (cp2.y - cp1.y) * (cp1.x - cp0.x); + if direction == 0.0 { + self.line_to(cp1); + return; + } + + // otherwise, draw the Arc + let a2 = (cp0.x - cp1.x).powi(2) + (cp0.y - cp1.y).powi(2); + let b2 = (cp1.x - cp2.x).powi(2) + (cp1.y - cp2.y).powi(2); + let d = { + let c2 = (cp0.x - cp2.x).powi(2) + (cp0.y - cp2.y).powi(2); + let cosx = (a2 + b2 - c2) / (2.0 * (a2 * b2).sqrt()); + let sinx = (1.0 - cosx.powi(2)).sqrt(); + radius / ((1.0 - cosx) / sinx) + }; + + // first tangent point + let anx = (cp1.x - cp0.x) / a2.sqrt(); + let any = (cp1.y - cp0.y) / a2.sqrt(); + let tp1 = Point2D::new(cp1.x - anx * d, cp1.y - any * d); + + // second tangent point + let bnx = (cp1.x - cp2.x) / b2.sqrt(); + let bny = (cp1.y - cp2.y) / b2.sqrt(); + let tp2 = Point2D::new(cp1.x - bnx * d, cp1.y - bny * d); + + // arc center and angles + let anticlockwise = direction < 0.0; + let cx = tp1.x + any * radius * if anticlockwise { 1.0 } else { -1.0 }; + let cy = tp1.y - anx * radius * if anticlockwise { 1.0 } else { -1.0 }; + let angle_start = (tp1.y - cy).atan2(tp1.x - cx); + let angle_end = (tp2.y - cy).atan2(tp2.x - cx); + + self.line_to(&self.transform.transform_point(tp1)); + if [cx, cy, angle_start, angle_end] + .iter() + .all(|x| x.is_finite()) + { + self.arc( + &self.transform.transform_point(Point2D::new(cx, cy)), + radius, + angle_start, + angle_end, + anticlockwise, + ); + } + } + #[allow(clippy::too_many_arguments)] pub fn ellipse( &mut self, @@ -215,11 +358,24 @@ impl PathBuilderRef<'_> { ); } - fn current_point(&mut self) -> Option> { - let inverse = self.transform.inverse()?; - self.builder - .get_current_point() - .map(|point| inverse.transform_point(Point2D::new(point.x, point.y))) + fn svg_arc( + &mut self, + radius_x: f32, + radius_y: f32, + rotation_angle: f32, + large_arc: bool, + sweep: bool, + end_point: &Point2D, + ) { + let end_point = self.transform.transform_point(*end_point); + self.builder.svg_arc( + radius_x, + radius_y, + rotation_angle, + large_arc, + sweep, + end_point, + ); } fn close(&mut self) { @@ -957,6 +1113,20 @@ impl<'a> CanvasData<'a> { ); } + pub fn fill_path(&mut self, path: &[PathSegment]) { + if self.state.fill_style.is_zero_size_gradient() { + return; // Paint nothing if gradient size is zero. + } + + let path = to_path(path, self.drawtarget.create_path_builder()); + + self.drawtarget.fill( + &path, + self.state.fill_style.clone(), + &self.state.draw_options, + ); + } + pub fn stroke(&mut self) { if self.state.stroke_style.is_zero_size_gradient() { return; // Paint nothing if gradient size is zero. @@ -971,12 +1141,32 @@ impl<'a> CanvasData<'a> { ); } + pub fn stroke_path(&mut self, path: &[PathSegment]) { + if self.state.stroke_style.is_zero_size_gradient() { + return; // Paint nothing if gradient size is zero. + } + + let path = to_path(path, self.drawtarget.create_path_builder()); + + self.drawtarget.stroke( + &path, + self.state.stroke_style.clone(), + &self.state.stroke_opts, + &self.state.draw_options, + ); + } + pub fn clip(&mut self) { self.ensure_path(); let path = self.path().clone(); self.drawtarget.push_clip(&path); } + pub fn clip_path(&mut self, path: &[PathSegment]) { + let path = to_path(path, self.drawtarget.create_path_builder()); + self.drawtarget.push_clip(&path); + } + pub fn is_point_in_path( &mut self, x: f64, @@ -996,6 +1186,26 @@ impl<'a> CanvasData<'a> { chan.send(result).unwrap(); } + pub fn is_point_in_path_( + &mut self, + path: &[PathSegment], + x: f64, + y: f64, + _fill_rule: FillRule, + chan: IpcSender, + ) { + let path_transform = match self.path_state.as_ref() { + Some(PathState::UserSpacePath(_, Some(transform))) => transform, + Some(_) | None => &self.drawtarget.get_transform(), + }; + let result = to_path(path, self.drawtarget.create_path_builder()).contains_point( + x, + y, + path_transform, + ); + chan.send(result).unwrap(); + } + pub fn move_to(&mut self, point: &Point2D) { self.path_builder().move_to(point); } @@ -1105,69 +1315,7 @@ impl<'a> CanvasData<'a> { } pub fn arc_to(&mut self, cp1: &Point2D, cp2: &Point2D, radius: f32) { - let cp0 = match self.path_builder().current_point() { - Some(p) => p, - None => { - self.path_builder().move_to(cp1); - *cp1 - }, - }; - let cp1 = *cp1; - let cp2 = *cp2; - - if (cp0.x == cp1.x && cp0.y == cp1.y) || cp1 == cp2 || radius == 0.0 { - self.line_to(&cp1); - return; - } - - // if all three control points lie on a single straight line, - // connect the first two by a straight line - let direction = (cp2.x - cp1.x) * (cp0.y - cp1.y) + (cp2.y - cp1.y) * (cp1.x - cp0.x); - if direction == 0.0 { - self.line_to(&cp1); - return; - } - - // otherwise, draw the Arc - let a2 = (cp0.x - cp1.x).powi(2) + (cp0.y - cp1.y).powi(2); - let b2 = (cp1.x - cp2.x).powi(2) + (cp1.y - cp2.y).powi(2); - let d = { - let c2 = (cp0.x - cp2.x).powi(2) + (cp0.y - cp2.y).powi(2); - let cosx = (a2 + b2 - c2) / (2.0 * (a2 * b2).sqrt()); - let sinx = (1.0 - cosx.powi(2)).sqrt(); - radius / ((1.0 - cosx) / sinx) - }; - - // first tangent point - let anx = (cp1.x - cp0.x) / a2.sqrt(); - let any = (cp1.y - cp0.y) / a2.sqrt(); - let tp1 = Point2D::new(cp1.x - anx * d, cp1.y - any * d); - - // second tangent point - let bnx = (cp1.x - cp2.x) / b2.sqrt(); - let bny = (cp1.y - cp2.y) / b2.sqrt(); - let tp2 = Point2D::new(cp1.x - bnx * d, cp1.y - bny * d); - - // arc center and angles - let anticlockwise = direction < 0.0; - let cx = tp1.x + any * radius * if anticlockwise { 1.0 } else { -1.0 }; - let cy = tp1.y - anx * radius * if anticlockwise { 1.0 } else { -1.0 }; - let angle_start = (tp1.y - cy).atan2(tp1.x - cx); - let angle_end = (tp2.y - cy).atan2(tp2.x - cx); - - self.line_to(&tp1); - if [cx, cy, angle_start, angle_end] - .iter() - .all(|x| x.is_finite()) - { - self.arc( - &Point2D::new(cx, cy), - radius, - angle_start, - angle_end, - anticlockwise, - ); - } + self.path_builder().arc_to(cp1, cp2, radius); } #[allow(clippy::too_many_arguments)] diff --git a/components/canvas/canvas_paint_thread.rs b/components/canvas/canvas_paint_thread.rs index 608c97ad496..09d670d5274 100644 --- a/components/canvas/canvas_paint_thread.rs +++ b/components/canvas/canvas_paint_thread.rs @@ -139,14 +139,26 @@ impl<'a> CanvasPaintThread<'a> { self.canvas(canvas_id).set_fill_style(style); self.canvas(canvas_id).fill(); }, + Canvas2dMsg::FillPath(style, path) => { + self.canvas(canvas_id).set_fill_style(style); + self.canvas(canvas_id).fill_path(&path[..]); + }, Canvas2dMsg::Stroke(style) => { self.canvas(canvas_id).set_stroke_style(style); self.canvas(canvas_id).stroke(); }, + Canvas2dMsg::StrokePath(style, path) => { + self.canvas(canvas_id).set_stroke_style(style); + self.canvas(canvas_id).stroke_path(&path[..]); + }, Canvas2dMsg::Clip => self.canvas(canvas_id).clip(), - Canvas2dMsg::IsPointInPath(x, y, fill_rule, chan) => self + Canvas2dMsg::ClipPath(path) => self.canvas(canvas_id).clip_path(&path[..]), + Canvas2dMsg::IsPointInCurrentPath(x, y, fill_rule, chan) => self .canvas(canvas_id) .is_point_in_path(x, y, fill_rule, chan), + Canvas2dMsg::IsPointInPath(path, x, y, fill_rule, chan) => self + .canvas(canvas_id) + .is_point_in_path_(&path[..], x, y, fill_rule, chan), Canvas2dMsg::DrawImage( ref image_data, image_size, diff --git a/components/canvas/raqote_backend.rs b/components/canvas/raqote_backend.rs index 4ee9a15dc55..bc8d20e9dcf 100644 --- a/components/canvas/raqote_backend.rs +++ b/components/canvas/raqote_backend.rs @@ -813,6 +813,32 @@ impl GenericPathBuilder for PathBuilder { }); } + fn svg_arc( + &mut self, + radius_x: f32, + radius_y: f32, + rotation_angle: f32, + large_arc: bool, + sweep: bool, + end_point: Point2D, + ) { + let Some(start) = self.get_current_point() else { + return; + }; + + let arc = lyon_geom::SvgArc { + from: start, + to: end_point, + radii: lyon_geom::vector(radius_x, radius_y), + x_rotation: lyon_geom::Angle::degrees(rotation_angle), + flags: lyon_geom::ArcFlags { large_arc, sweep }, + }; + + arc.for_each_quadratic_bezier(&mut |q| { + self.quadratic_curve_to(&q.ctrl, &q.to); + }); + } + fn get_current_point(&mut self) -> Option> { let path = self.finish(); self.0 = Some(path.as_raqote().clone().into()); diff --git a/components/script/canvas_state.rs b/components/script/canvas_state.rs index c9e94307dd6..b82f682b29f 100644 --- a/components/script/canvas_state.rs +++ b/components/script/canvas_state.rs @@ -9,7 +9,7 @@ use std::sync::Arc; use canvas_traits::canvas::{ Canvas2dMsg, CanvasId, CanvasMsg, CompositionOrBlending, Direction, FillOrStrokeStyle, - FillRule, LineCapStyle, LineJoinStyle, LinearGradientStyle, RadialGradientStyle, + FillRule, LineCapStyle, LineJoinStyle, LinearGradientStyle, PathSegment, RadialGradientStyle, RepetitionStyle, TextAlign, TextBaseline, TextMetrics as CanvasTextMetrics, }; use cssparser::color::clamp_unit_f32; @@ -1521,18 +1521,36 @@ impl CanvasState { self.send_canvas_2d_msg(Canvas2dMsg::Fill(style)); } + // https://html.spec.whatwg.org/multipage/#dom-context-2d-fill + pub(crate) fn fill_(&self, path: Vec, _fill_rule: CanvasFillRule) { + // TODO: Process fill rule + let style = self.state.borrow().fill_style.to_fill_or_stroke_style(); + self.send_canvas_2d_msg(Canvas2dMsg::FillPath(style, path)); + } + // https://html.spec.whatwg.org/multipage/#dom-context-2d-stroke pub(crate) fn stroke(&self) { let style = self.state.borrow().stroke_style.to_fill_or_stroke_style(); self.send_canvas_2d_msg(Canvas2dMsg::Stroke(style)); } + pub(crate) fn stroke_(&self, path: Vec) { + let style = self.state.borrow().stroke_style.to_fill_or_stroke_style(); + self.send_canvas_2d_msg(Canvas2dMsg::StrokePath(style, path)); + } + // https://html.spec.whatwg.org/multipage/#dom-context-2d-clip pub(crate) fn clip(&self, _fill_rule: CanvasFillRule) { // TODO: Process fill rule self.send_canvas_2d_msg(Canvas2dMsg::Clip); } + // https://html.spec.whatwg.org/multipage/#dom-context-2d-clip + pub(crate) fn clip_(&self, path: Vec, _fill_rule: CanvasFillRule) { + // TODO: Process fill rule + self.send_canvas_2d_msg(Canvas2dMsg::ClipPath(path)); + } + // https://html.spec.whatwg.org/multipage/#dom-context-2d-ispointinpath pub(crate) fn is_point_in_path( &self, @@ -1551,7 +1569,30 @@ impl CanvasState { }; let (sender, receiver) = profiled_ipc::channel::(global.time_profiler_chan().clone()).unwrap(); - self.send_canvas_2d_msg(Canvas2dMsg::IsPointInPath(x, y, fill_rule, sender)); + self.send_canvas_2d_msg(Canvas2dMsg::IsPointInCurrentPath(x, y, fill_rule, sender)); + receiver.recv().unwrap() + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-ispointinpath + pub(crate) fn is_point_in_path_( + &self, + global: &GlobalScope, + path: Vec, + x: f64, + y: f64, + fill_rule: CanvasFillRule, + ) -> bool { + if !(x.is_finite() && y.is_finite()) { + return false; + } + + let fill_rule = match fill_rule { + CanvasFillRule::Nonzero => FillRule::Nonzero, + CanvasFillRule::Evenodd => FillRule::Evenodd, + }; + let (sender, receiver) = + profiled_ipc::channel::(global.time_profiler_chan().clone()).unwrap(); + self.send_canvas_2d_msg(Canvas2dMsg::IsPointInPath(path, x, y, fill_rule, sender)); receiver.recv().unwrap() } diff --git a/components/script/dom/canvasrenderingcontext2d.rs b/components/script/dom/canvasrenderingcontext2d.rs index b55246500ad..cc8215fd335 100644 --- a/components/script/dom/canvasrenderingcontext2d.rs +++ b/components/script/dom/canvasrenderingcontext2d.rs @@ -32,6 +32,7 @@ use crate::dom::globalscope::GlobalScope; use crate::dom::htmlcanvaselement::HTMLCanvasElement; use crate::dom::imagedata::ImageData; use crate::dom::node::{Node, NodeDamage, NodeTraits}; +use crate::dom::path2d::Path2D; use crate::dom::textmetrics::TextMetrics; use crate::script_runtime::CanGc; @@ -288,23 +289,46 @@ impl CanvasRenderingContext2DMethods for CanvasRenderingCo self.mark_as_dirty(); } + // https://html.spec.whatwg.org/multipage/#dom-context-2d-fill + fn Fill_(&self, path: &Path2D, fill_rule: CanvasFillRule) { + self.canvas_state.fill_(path.segments(), fill_rule); + self.mark_as_dirty(); + } + // https://html.spec.whatwg.org/multipage/#dom-context-2d-stroke fn Stroke(&self) { self.canvas_state.stroke(); self.mark_as_dirty(); } + // https://html.spec.whatwg.org/multipage/#dom-context-2d-stroke + fn Stroke_(&self, path: &Path2D) { + self.canvas_state.stroke_(path.segments()); + self.mark_as_dirty(); + } + // https://html.spec.whatwg.org/multipage/#dom-context-2d-clip fn Clip(&self, fill_rule: CanvasFillRule) { self.canvas_state.clip(fill_rule) } + // https://html.spec.whatwg.org/multipage/#dom-context-2d-clip + fn Clip_(&self, path: &Path2D, fill_rule: CanvasFillRule) { + self.canvas_state.clip_(path.segments(), fill_rule) + } + // https://html.spec.whatwg.org/multipage/#dom-context-2d-ispointinpath fn IsPointInPath(&self, x: f64, y: f64, fill_rule: CanvasFillRule) -> bool { self.canvas_state .is_point_in_path(&self.global(), x, y, fill_rule) } + // https://html.spec.whatwg.org/multipage/#dom-context-2d-ispointinpath + fn IsPointInPath_(&self, path: &Path2D, x: f64, y: f64, fill_rule: CanvasFillRule) -> bool { + self.canvas_state + .is_point_in_path_(&self.global(), path.segments(), x, y, fill_rule) + } + // https://html.spec.whatwg.org/multipage/#dom-context-2d-filltext fn FillText(&self, text: DOMString, x: f64, y: f64, max_width: Option, can_gc: CanGc) { self.canvas_state diff --git a/components/script/dom/mod.rs b/components/script/dom/mod.rs index 341b0bdd572..4581ea56135 100644 --- a/components/script/dom/mod.rs +++ b/components/script/dom/mod.rs @@ -466,6 +466,7 @@ pub(crate) mod paintrenderingcontext2d; pub(crate) mod paintsize; pub(crate) mod paintworkletglobalscope; pub(crate) mod pannernode; +pub(crate) mod path2d; pub(crate) mod performance; #[allow(dead_code)] pub(crate) mod performanceentry; diff --git a/components/script/dom/offscreencanvasrenderingcontext2d.rs b/components/script/dom/offscreencanvasrenderingcontext2d.rs index e4282b43d90..731ef6d008a 100644 --- a/components/script/dom/offscreencanvasrenderingcontext2d.rs +++ b/components/script/dom/offscreencanvasrenderingcontext2d.rs @@ -27,6 +27,7 @@ use crate::dom::dommatrix::DOMMatrix; use crate::dom::globalscope::GlobalScope; use crate::dom::imagedata::ImageData; use crate::dom::offscreencanvas::OffscreenCanvas; +use crate::dom::path2d::Path2D; use crate::dom::textmetrics::TextMetrics; use crate::script_runtime::CanGc; @@ -437,21 +438,41 @@ impl OffscreenCanvasRenderingContext2DMethods self.context.Fill(fill_rule) } + // https://html.spec.whatwg.org/multipage/#dom-context-2d-fill + fn Fill_(&self, path: &Path2D, fill_rule: CanvasFillRule) { + self.context.Fill_(path, fill_rule) + } + // https://html.spec.whatwg.org/multipage/#dom-context-2d-stroke fn Stroke(&self) { self.context.Stroke() } + // https://html.spec.whatwg.org/multipage/#dom-context-2d-stroke + fn Stroke_(&self, path: &Path2D) { + self.context.Stroke_(path) + } + // https://html.spec.whatwg.org/multipage/#dom-context-2d-clip fn Clip(&self, fill_rule: CanvasFillRule) { self.context.Clip(fill_rule) } + // https://html.spec.whatwg.org/multipage/#dom-context-2d-clip + fn Clip_(&self, path: &Path2D, fill_rule: CanvasFillRule) { + self.context.Clip_(path, fill_rule) + } + // https://html.spec.whatwg.org/multipage/#dom-context-2d-ispointinpath fn IsPointInPath(&self, x: f64, y: f64, fill_rule: CanvasFillRule) -> bool { self.context.IsPointInPath(x, y, fill_rule) } + // https://html.spec.whatwg.org/multipage/#dom-context-2d-ispointinpath + fn IsPointInPath_(&self, path: &Path2D, x: f64, y: f64, fill_rule: CanvasFillRule) -> bool { + self.context.IsPointInPath_(path, x, y, fill_rule) + } + // https://html.spec.whatwg.org/multipage/#dom-context-2d-scale fn Scale(&self, x: f64, y: f64) { self.context.Scale(x, y) diff --git a/components/script/dom/paintrenderingcontext2d.rs b/components/script/dom/paintrenderingcontext2d.rs index 4e0672a42a2..0babe77da97 100644 --- a/components/script/dom/paintrenderingcontext2d.rs +++ b/components/script/dom/paintrenderingcontext2d.rs @@ -29,6 +29,7 @@ use crate::dom::canvasgradient::CanvasGradient; use crate::dom::canvaspattern::CanvasPattern; use crate::dom::dommatrix::DOMMatrix; use crate::dom::paintworkletglobalscope::PaintWorkletGlobalScope; +use crate::dom::path2d::Path2D; use crate::script_runtime::CanGc; #[dom_struct] @@ -192,22 +193,43 @@ impl PaintRenderingContext2DMethods for PaintRenderingCont self.canvas_state.fill(fill_rule) } + // https://html.spec.whatwg.org/multipage/#dom-context-2d-fill + fn Fill_(&self, path: &Path2D, fill_rule: CanvasFillRule) { + self.canvas_state.fill_(path.segments(), fill_rule) + } + // https://html.spec.whatwg.org/multipage/#dom-context-2d-stroke fn Stroke(&self) { self.canvas_state.stroke() } + // https://html.spec.whatwg.org/multipage/#dom-context-2d-stroke + fn Stroke_(&self, path: &Path2D) { + self.canvas_state.stroke_(path.segments()) + } + // https://html.spec.whatwg.org/multipage/#dom-context-2d-clip fn Clip(&self, fill_rule: CanvasFillRule) { self.canvas_state.clip(fill_rule) } + // https://html.spec.whatwg.org/multipage/#dom-context-2d-clip + fn Clip_(&self, path: &Path2D, fill_rule: CanvasFillRule) { + self.canvas_state.clip_(path.segments(), fill_rule) + } + // https://html.spec.whatwg.org/multipage/#dom-context-2d-ispointinpath fn IsPointInPath(&self, x: f64, y: f64, fill_rule: CanvasFillRule) -> bool { self.canvas_state .is_point_in_path(&self.global(), x, y, fill_rule) } + // https://html.spec.whatwg.org/multipage/#dom-context-2d-ispointinpath + fn IsPointInPath_(&self, path: &Path2D, x: f64, y: f64, fill_rule: CanvasFillRule) -> bool { + self.canvas_state + .is_point_in_path_(&self.global(), path.segments(), x, y, fill_rule) + } + // https://html.spec.whatwg.org/multipage/#dom-context-2d-drawimage fn DrawImage(&self, image: CanvasImageSource, dx: f64, dy: f64) -> ErrorResult { self.canvas_state.draw_image(None, image, dx, dy) diff --git a/components/script/dom/path2d.rs b/components/script/dom/path2d.rs new file mode 100644 index 00000000000..c5b6c34a79d --- /dev/null +++ b/components/script/dom/path2d.rs @@ -0,0 +1,313 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use std::cell::RefCell; + +use canvas_traits::canvas::PathSegment; +use dom_struct::dom_struct; +use js::rust::HandleObject; +use script_bindings::str::DOMString; + +use crate::dom::bindings::codegen::Bindings::CanvasRenderingContext2DBinding::Path2DMethods; +use crate::dom::bindings::error::{Error, Fallible}; +use crate::dom::bindings::reflector::{Reflector, reflect_dom_object_with_proto}; +use crate::dom::bindings::root::DomRoot; +use crate::dom::globalscope::GlobalScope; +use crate::script_runtime::CanGc; +use crate::svgpath::PathParser; + +#[dom_struct] +pub(crate) struct Path2D { + reflector_: Reflector, + #[no_trace] + path: RefCell>, +} + +impl Path2D { + pub(crate) fn new() -> Path2D { + Self { + reflector_: Reflector::new(), + path: RefCell::new(vec![]), + } + } + pub(crate) fn new_with_path(other: &Path2D) -> Path2D { + Self { + reflector_: Reflector::new(), + path: other.path.clone(), + } + } + pub(crate) fn new_with_str(path: &str) -> Path2D { + let mut path_segments = Vec::new(); + + for segment in PathParser::new(path) { + if let Ok(segment) = segment { + path_segments.push(segment); + } else { + break; + } + } + + Self { + reflector_: Reflector::new(), + path: RefCell::new(path_segments), + } + } + pub(crate) fn push(&self, seg: PathSegment) { + self.path.borrow_mut().push(seg); + } + pub(crate) fn segments(&self) -> Vec { + self.path.borrow().clone() + } +} + +impl Path2DMethods for Path2D { + /// + fn AddPath(&self, other: &Path2D) { + // Step 7. Add all the subpaths in c to a. + let mut dest = self.path.borrow_mut(); + dest.extend(other.path.borrow().iter().copied()); + } + + /// + fn ClosePath(&self) { + self.push(PathSegment::ClosePath); + } + + /// + fn MoveTo(&self, x: f64, y: f64) { + // Step 1. If either of the arguments are infinite or NaN, then return. + if !(x.is_finite() && y.is_finite()) { + return; + } + + // Step 2. Create a new subpath with the specified point as its first (and only) point. + self.push(PathSegment::MoveTo { + x: x as f32, + y: y as f32, + }); + } + + /// + fn LineTo(&self, x: f64, y: f64) { + // Step 1. If either of the arguments are infinite or NaN, then return. + if !(x.is_finite() && y.is_finite()) { + return; + } + + self.push(PathSegment::LineTo { + x: x as f32, + y: y as f32, + }); + } + + /// + fn QuadraticCurveTo(&self, cpx: f64, cpy: f64, x: f64, y: f64) { + // Step 1. If any of the arguments are infinite or NaN, then return. + if !(cpx.is_finite() && cpy.is_finite() && x.is_finite() && y.is_finite()) { + return; + } + + self.push(PathSegment::Quadratic { + cpx: cpx as f32, + cpy: cpy as f32, + x: x as f32, + y: y as f32, + }); + } + + /// + fn BezierCurveTo(&self, cp1x: f64, cp1y: f64, cp2x: f64, cp2y: f64, x: f64, y: f64) { + // Step 1. If any of the arguments are infinite or NaN, then return. + if !(cp1x.is_finite() && + cp1y.is_finite() && + cp2x.is_finite() && + cp2y.is_finite() && + x.is_finite() && + y.is_finite()) + { + return; + } + + self.push(PathSegment::Bezier { + cp1x: cp1x as f32, + cp1y: cp1y as f32, + cp2x: cp2x as f32, + cp2y: cp2y as f32, + x: x as f32, + y: y as f32, + }); + } + + /// + fn ArcTo(&self, x1: f64, y1: f64, x2: f64, y2: f64, r: f64) -> Fallible<()> { + // Step 1. If any of the arguments are infinite or NaN, then return. + if !(x1.is_finite() && y1.is_finite() && x2.is_finite() && y2.is_finite() && r.is_finite()) + { + return Ok(()); + } + + // Step 3. If radius is negative, then throw an "IndexSizeError" DOMException. + if r < 0.0 { + return Err(Error::IndexSize); + } + + self.push(PathSegment::ArcTo { + cp1x: x1 as f32, + cp1y: y1 as f32, + cp2x: x2 as f32, + cp2y: y2 as f32, + radius: r as f32, + }); + Ok(()) + } + + /// + fn Rect(&self, x: f64, y: f64, w: f64, h: f64) { + // Step 1. If any of the arguments are infinite or NaN, then return. + if !(x.is_finite() && y.is_finite() && w.is_finite() && h.is_finite()) { + return; + } + // Step 2. Create a new subpath containing just the four points + // (x, y), (x+w, y), (x+w, y+h), (x, y+h), in that order, + // with those four points connected by straight lines. + self.push(PathSegment::MoveTo { + x: x as f32, + y: y as f32, + }); + self.push(PathSegment::LineTo { + x: (x + w) as f32, + y: y as f32, + }); + self.push(PathSegment::LineTo { + x: (x + w) as f32, + y: (y + h) as f32, + }); + self.push(PathSegment::LineTo { + x: x as f32, + y: (y + h) as f32, + }); + // Step 3. Mark the subpath as closed. + self.push(PathSegment::ClosePath); + + // Step 4. Create a new subpath with the point (x, y) as the only point in the subpath. + self.push(PathSegment::MoveTo { + x: x as f32, + y: y as f32, + }); + } + + /// + fn Arc( + &self, + x: f64, + y: f64, + r: f64, + start: f64, + end: f64, + anticlockwise: bool, + ) -> Fallible<()> { + // Step 1. If any of the arguments are infinite or NaN, then return. + if !(x.is_finite() && + y.is_finite() && + r.is_finite() && + start.is_finite() && + end.is_finite()) + { + return Ok(()); + } + + // Step 2. If either radiusX or radiusY are negative, then throw an "IndexSizeError" DOMException. + if r < 0.0 { + return Err(Error::IndexSize); + } + + self.push(PathSegment::Ellipse { + x: x as f32, + y: y as f32, + radius_x: r as f32, + radius_y: r as f32, + rotation: 0., + start_angle: start as f32, + end_angle: end as f32, + anticlockwise, + }); + Ok(()) + } + + /// + fn Ellipse( + &self, + x: f64, + y: f64, + rx: f64, + ry: f64, + rotation: f64, + start: f64, + end: f64, + anticlockwise: bool, + ) -> Fallible<()> { + // Step 1. If any of the arguments are infinite or NaN, then return. + if !(x.is_finite() && + y.is_finite() && + rx.is_finite() && + ry.is_finite() && + rotation.is_finite() && + start.is_finite() && + end.is_finite()) + { + return Ok(()); + } + + // Step 2. If either radiusX or radiusY are negative, then throw an "IndexSizeError" DOMException. + if rx < 0.0 || ry < 0.0 { + return Err(Error::IndexSize); + } + + self.push(PathSegment::Ellipse { + x: x as f32, + y: y as f32, + radius_x: rx as f32, + radius_y: ry as f32, + rotation: rotation as f32, + start_angle: start as f32, + end_angle: end as f32, + anticlockwise, + }); + Ok(()) + } + + /// + fn Constructor( + global: &GlobalScope, + proto: Option, + can_gc: CanGc, + ) -> DomRoot { + reflect_dom_object_with_proto(Box::new(Self::new()), global, proto, can_gc) + } + + /// + fn Constructor_( + global: &GlobalScope, + proto: Option, + can_gc: CanGc, + other: &Path2D, + ) -> DomRoot { + reflect_dom_object_with_proto(Box::new(Self::new_with_path(other)), global, proto, can_gc) + } + + /// + fn Constructor__( + global: &GlobalScope, + proto: Option, + can_gc: CanGc, + path_string: DOMString, + ) -> DomRoot { + reflect_dom_object_with_proto( + Box::new(Self::new_with_str(path_string.str())), + global, + proto, + can_gc, + ) + } +} diff --git a/components/script/lib.rs b/components/script/lib.rs index 16af5491629..d0afb2551b0 100644 --- a/components/script/lib.rs +++ b/components/script/lib.rs @@ -78,6 +78,8 @@ mod drag_data_store; mod links; mod xpath; +mod svgpath; + pub use init::init; pub use script_runtime::JSEngineSetup; pub use script_thread::ScriptThread; diff --git a/components/script/svgpath/mod.rs b/components/script/svgpath/mod.rs new file mode 100644 index 00000000000..f3093e3d282 --- /dev/null +++ b/components/script/svgpath/mod.rs @@ -0,0 +1,13 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +mod number; +mod path; +mod stream; + +#[derive(Debug, Default, Eq, PartialEq)] +pub struct Error; + +pub(crate) use path::PathParser; +pub(crate) use stream::Stream; diff --git a/components/script/svgpath/number.rs b/components/script/svgpath/number.rs new file mode 100644 index 00000000000..b199b357868 --- /dev/null +++ b/components/script/svgpath/number.rs @@ -0,0 +1,198 @@ +// Copyright 2018 the SVG Types Authors +// Copyright 2025 the Servo Authors +// SPDX-License-Identifier: Apache-2.0 OR MIT + +use std::str::FromStr; + +use crate::svgpath::{Error, Stream}; + +/// An [SVG number](https://www.w3.org/TR/SVG2/types.html#InterfaceSVGNumber). +#[derive(Clone, Copy, Debug, PartialEq)] +pub struct Number(pub f32); + +impl std::str::FromStr for Number { + type Err = Error; + + fn from_str(text: &str) -> Result { + let mut s = Stream::from(text); + let n = s.parse_number()?; + s.skip_spaces(); + if !s.at_end() { + return Err(Error); + } + + Ok(Self(n)) + } +} + +impl Stream<'_> { + /// Parses number from the stream. + /// + /// This method will detect a number length and then + /// will pass a substring to the `f64::from_str` method. + /// + /// + pub fn parse_number(&mut self) -> Result { + // Strip off leading whitespaces. + self.skip_spaces(); + + if self.at_end() { + return Err(Error); + } + + self.parse_number_impl().map_err(|_| Error) + } + + fn parse_number_impl(&mut self) -> Result { + let start = self.pos(); + + let mut c = self.curr_byte()?; + + // Consume sign. + if matches!(c, b'+' | b'-') { + self.advance(1); + c = self.curr_byte()?; + } + + // Consume integer. + match c { + b'0'..=b'9' => self.skip_digits(), + b'.' => {}, + _ => return Err(Error), + } + + // Consume fraction. + if let Ok(b'.') = self.curr_byte() { + self.advance(1); + self.skip_digits(); + } + + if let Ok(c) = self.curr_byte() { + if matches!(c, b'e' | b'E') { + let c2 = self.next_byte()?; + // Check for `em`/`ex`. + if c2 != b'm' && c2 != b'x' { + self.advance(1); + + match self.curr_byte()? { + b'+' | b'-' => { + self.advance(1); + self.skip_digits(); + }, + b'0'..=b'9' => self.skip_digits(), + _ => { + return Err(Error); + }, + } + } + } + } + + let s = self.slice_back(start); + + // Use the default f32 parser now. + if let Ok(n) = f32::from_str(s) { + // inf, nan, etc. are an error. + if n.is_finite() { + return Ok(n); + } + } + + Err(Error) + } + + /// Parses number from a list of numbers. + pub fn parse_list_number(&mut self) -> Result { + if self.at_end() { + return Err(Error); + } + + let n = self.parse_number()?; + self.skip_spaces(); + self.parse_list_separator(); + Ok(n) + } +} + +/// A pull-based [``] parser. +/// +/// # Examples +/// +/// ``` +/// use svgtypes::NumberListParser; +/// +/// let mut p = NumberListParser::from("10, 20 -50"); +/// assert_eq!(p.next().unwrap().unwrap(), 10.0); +/// assert_eq!(p.next().unwrap().unwrap(), 20.0); +/// assert_eq!(p.next().unwrap().unwrap(), -50.0); +/// assert_eq!(p.next().is_none(), true); +/// ``` +/// +/// [``]: https://www.w3.org/TR/SVG2/types.html#InterfaceSVGNumberList +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct NumberListParser<'a>(Stream<'a>); + +impl<'a> From<&'a str> for NumberListParser<'a> { + #[inline] + fn from(v: &'a str) -> Self { + NumberListParser(Stream::from(v)) + } +} + +impl Iterator for NumberListParser<'_> { + type Item = Result; + + fn next(&mut self) -> Option { + if self.0.at_end() { + None + } else { + let v = self.0.parse_list_number(); + if v.is_err() { + self.0.jump_to_end(); + } + + Some(v) + } + } +} + +#[rustfmt::skip] +#[cfg(test)] +mod tests { + use crate::svgpath::Stream; + + macro_rules! test_p { + ($name:ident, $text:expr, $result:expr) => ( + #[test] + fn $name() { + let mut s = Stream::from($text); + assert_eq!(s.parse_number().unwrap(), $result); + } + ) + } + + test_p!(parse_1, "0", 0.0); + test_p!(parse_2, "1", 1.0); + test_p!(parse_3, "-1", -1.0); + test_p!(parse_4, " -1 ", -1.0); + test_p!(parse_5, " 1 ", 1.0); + test_p!(parse_6, ".4", 0.4); + test_p!(parse_7, "-.4", -0.4); + test_p!(parse_8, "-.4text", -0.4); + test_p!(parse_9, "-.01 text", -0.01); + test_p!(parse_10, "-.01 4", -0.01); + test_p!(parse_11, ".0000000000008", 0.0000000000008); + test_p!(parse_12, "1000000000000", 1000000000000.0); + test_p!(parse_13, "123456.123456", 123456.123456); + test_p!(parse_14, "+10", 10.0); + test_p!(parse_15, "1e2", 100.0); + test_p!(parse_16, "1e+2", 100.0); + test_p!(parse_17, "1E2", 100.0); + test_p!(parse_18, "1e-2", 0.01); + test_p!(parse_19, "1ex", 1.0); + test_p!(parse_20, "1em", 1.0); + test_p!(parse_21, "12345678901234567890", 12345678901234567000.0); + test_p!(parse_22, "0.", 0.0); + test_p!(parse_23, "1.3e-2", 0.013); + // test_number!(parse_24, "1e", 1.0); // TODO: this +} diff --git a/components/script/svgpath/path.rs b/components/script/svgpath/path.rs new file mode 100644 index 00000000000..7d97df22cd2 --- /dev/null +++ b/components/script/svgpath/path.rs @@ -0,0 +1,393 @@ +// Copyright 2021 the SVG Types Authors +// Copyright 2025 the Servo Authors +// SPDX-License-Identifier: Apache-2.0 OR MIT + +use canvas_traits::canvas::PathSegment; + +use crate::svgpath::{Error, Stream}; + +pub struct PathParser<'a> { + stream: Stream<'a>, + state: State, + last_cmd: u8, +} + +impl<'a> PathParser<'a> { + pub fn new(string: &'a str) -> Self { + Self { + stream: Stream::from(string), + state: State::default(), + last_cmd: b' ', + } + } +} + +impl Iterator for PathParser<'_> { + type Item = Result; + + fn next(&mut self) -> Option { + self.stream.skip_spaces(); + + let Ok(curr_byte) = self.stream.curr_byte() else { + return None; + }; + + let cmd = if self.last_cmd == b' ' { + if let move_to @ (b'm' | b'M') = curr_byte { + self.stream.advance(1); + move_to + } else { + return Some(Err(Error)); + } + } else if curr_byte.is_ascii_alphabetic() { + self.stream.advance(1); + curr_byte + } else { + match self.last_cmd { + b'm' => b'l', + b'M' => b'L', + b'z' | b'Z' => return Some(Err(Error)), + cmd => cmd, + } + }; + + self.last_cmd = cmd; + Some(to_point(&mut self.stream, cmd, &mut self.state)) + } +} + +#[derive(Default)] +pub struct State { + start: (f32, f32), + pos: (f32, f32), + quad: (f32, f32), + cubic: (f32, f32), +} + +pub fn to_point(s: &mut Stream<'_>, cmd: u8, state: &mut State) -> Result { + let abs = cmd.is_ascii_uppercase(); + let cmd = cmd.to_ascii_lowercase(); + let (dx, dy) = if abs { (0., 0.) } else { state.pos }; + let seg = match cmd { + b'm' => PathSegment::MoveTo { + x: s.parse_list_number()? + dx, + y: s.parse_list_number()? + dy, + }, + b'l' => PathSegment::LineTo { + x: s.parse_list_number()? + dx, + y: s.parse_list_number()? + dy, + }, + b'h' => PathSegment::LineTo { + x: s.parse_list_number()? + dx, + y: state.pos.1, + }, + b'v' => PathSegment::LineTo { + x: state.pos.0, + y: s.parse_list_number()? + dy, + }, + b'c' => PathSegment::Bezier { + cp1x: s.parse_list_number()? + dx, + cp1y: s.parse_list_number()? + dy, + cp2x: s.parse_list_number()? + dx, + cp2y: s.parse_list_number()? + dy, + x: s.parse_list_number()? + dx, + y: s.parse_list_number()? + dy, + }, + b's' => PathSegment::Bezier { + cp1x: state.cubic.0, + cp1y: state.cubic.1, + cp2x: s.parse_list_number()? + dx, + cp2y: s.parse_list_number()? + dy, + x: s.parse_list_number()? + dx, + y: s.parse_list_number()? + dy, + }, + b'q' => PathSegment::Quadratic { + cpx: s.parse_list_number()? + dx, + cpy: s.parse_list_number()? + dy, + x: s.parse_list_number()? + dx, + y: s.parse_list_number()? + dy, + }, + b't' => PathSegment::Quadratic { + cpx: state.quad.0, + cpy: state.quad.1, + x: s.parse_list_number()? + dx, + y: s.parse_list_number()? + dy, + }, + b'a' => PathSegment::SvgArc { + radius_x: s.parse_list_number()?, + radius_y: s.parse_list_number()?, + rotation: s.parse_list_number()?, + large_arc: s.parse_flag()?, + sweep: s.parse_flag()?, + x: s.parse_list_number()? + dx, + y: s.parse_list_number()? + dy, + }, + b'z' => PathSegment::ClosePath, + _ => return Err(crate::svgpath::Error), + }; + + match seg { + PathSegment::MoveTo { x, y } => { + state.start = (x, y); + state.pos = (x, y); + state.quad = (x, y); + state.cubic = (x, y); + }, + PathSegment::LineTo { x, y } | PathSegment::SvgArc { x, y, .. } => { + state.pos = (x, y); + state.quad = (x, y); + state.cubic = (x, y); + }, + PathSegment::Bezier { + cp2x, cp2y, x, y, .. + } => { + state.pos = (x, y); + state.quad = (x, y); + state.cubic = (x * 2.0 - cp2x, y * 2.0 - cp2y); + }, + PathSegment::Quadratic { cpx, cpy, x, y, .. } => { + state.pos = (x, y); + state.quad = (x * 2.0 - cpx, y * 2.0 - cpy); + state.cubic = (x, y); + }, + PathSegment::ClosePath => { + state.pos = state.start; + state.quad = state.start; + state.cubic = state.start; + }, + _ => {}, + } + + Ok(seg) +} + +#[rustfmt::skip] +#[cfg(test)] +mod tests { + use super::*; + + macro_rules! test { + ($name:ident, $text:expr, $( $seg:expr ),*) => ( + #[test] + fn $name() { + let mut s = PathParser::new($text); + $( + assert_eq!(s.next().unwrap().unwrap(), $seg); + )* + + if let Some(res) = s.next() { + assert!(res.is_err()); + } + } + ) + } + + test!(null, "", ); + test!(not_a_path, "q", ); + test!(not_a_move_to, "L 20 30", ); + test!(stop_on_err_1, "M 10 20 L 30 40 L 50", + PathSegment::MoveTo { x: 10.0, y: 20.0 }, + PathSegment::LineTo { x: 30.0, y: 40.0 } + ); + + test!(move_to_1, "M 10 20", PathSegment::MoveTo { x: 10.0, y: 20.0 }); + test!(move_to_2, "m 10 20", PathSegment::MoveTo { x: 10.0, y: 20.0 }); + test!(move_to_3, "M 10 20 30 40 50 60", + PathSegment::MoveTo { x: 10.0, y: 20.0 }, + PathSegment::LineTo { x: 30.0, y: 40.0 }, + PathSegment::LineTo { x: 50.0, y: 60.0 } + ); + test!(move_to_4, "M 10 20 30 40 50 60 M 70 80 90 100 110 120", + PathSegment::MoveTo { x: 10.0, y: 20.0 }, + PathSegment::LineTo { x: 30.0, y: 40.0 }, + PathSegment::LineTo { x: 50.0, y: 60.0 }, + PathSegment::MoveTo { x: 70.0, y: 80.0 }, + PathSegment::LineTo { x: 90.0, y: 100.0 }, + PathSegment::LineTo { x: 110.0, y: 120.0 } + ); + + test!(arc_to_1, "M 10 20 A 5 5 30 1 1 20 20", + PathSegment::MoveTo { x: 10.0, y: 20.0 }, + PathSegment::SvgArc { + radius_x: 5.0, radius_y: 5.0, + rotation: 30.0, + large_arc: true, sweep: true, + x: 20.0, y: 20.0 + } + ); + + test!(arc_to_2, "M 10 20 a 5 5 30 0 0 20 20", + PathSegment::MoveTo { x: 10.0, y: 20.0 }, + PathSegment::SvgArc { + radius_x: 5.0, radius_y: 5.0, + rotation: 30.0, + large_arc: false, sweep: false, + x: 30.0, y: 40.0 + } + ); + + test!(arc_to_10, "M10-20A5.5.3-4 010-.1", + PathSegment::MoveTo { x: 10.0, y: -20.0 }, + PathSegment::SvgArc { + radius_x: 5.5, radius_y: 0.3, + rotation: -4.0, + large_arc: false, sweep: true, + x: 0.0, y: -0.1 + } + ); + + test!(separator_1, "M 10 20 L 5 15 C 10 20 30 40 50 60", + PathSegment::MoveTo { x: 10.0, y: 20.0 }, + PathSegment::LineTo { x: 5.0, y: 15.0 }, + PathSegment::Bezier { + cp1x: 10.0, cp1y: 20.0, + cp2x: 30.0, cp2y: 40.0, + x: 50.0, y: 60.0, + } + ); + + test!(separator_2, "M 10, 20 L 5, 15 C 10, 20 30, 40 50, 60", + PathSegment::MoveTo { x: 10.0, y: 20.0 }, + PathSegment::LineTo { x: 5.0, y: 15.0 }, + PathSegment::Bezier { + cp1x: 10.0, cp1y: 20.0, + cp2x: 30.0, cp2y: 40.0, + x: 50.0, y: 60.0, + } + ); + + test!(separator_3, "M 10,20 L 5,15 C 10,20 30,40 50,60", + PathSegment::MoveTo { x: 10.0, y: 20.0 }, + PathSegment::LineTo { x: 5.0, y: 15.0 }, + PathSegment::Bezier { + cp1x: 10.0, cp1y: 20.0, + cp2x: 30.0, cp2y: 40.0, + x: 50.0, y: 60.0, + } + ); + + test!(separator_4, "M10, 20 L5, 15 C10, 20 30 40 50 60", + PathSegment::MoveTo { x: 10.0, y: 20.0 }, + PathSegment::LineTo { x: 5.0, y: 15.0 }, + PathSegment::Bezier { + cp1x: 10.0, cp1y: 20.0, + cp2x: 30.0, cp2y: 40.0, + x: 50.0, y: 60.0, + } + ); + + test!(separator_5, "M10 20V30H40V50H60Z", + PathSegment::MoveTo { x: 10.0, y: 20.0 }, + PathSegment::LineTo { x: 10.0, y: 30.0 }, + PathSegment::LineTo { x: 40.0, y: 30.0 }, + PathSegment::LineTo { x: 40.0, y: 50.0 }, + PathSegment::LineTo { x: 60.0, y: 50.0 }, + PathSegment::ClosePath + ); + + test!(all_segments_1, "M 10 20 L 30 40 H 50 V 60 C 70 80 90 100 110 120 S 130 140 150 160 + Q 170 180 190 200 T 210 220 A 50 50 30 1 1 230 240 Z", + PathSegment::MoveTo { x: 10.0, y: 20.0 }, + PathSegment::LineTo { x: 30.0, y: 40.0 }, + PathSegment::LineTo { x: 50.0, y: 40.0 }, + PathSegment::LineTo { x: 50.0, y: 60.0 }, + PathSegment::Bezier { + cp1x: 70.0, cp1y: 80.0, + cp2x: 90.0, cp2y: 100.0, + x: 110.0, y: 120.0, + }, + PathSegment::Bezier { + cp1x: 130.0, cp1y: 140.0, + cp2x: 130.0, cp2y: 140.0, + x: 150.0, y: 160.0, + }, + PathSegment::Quadratic { + cpx: 170.0, cpy: 180.0, + x: 190.0, y: 200.0, + }, + PathSegment::Quadratic { + cpx: 210.0, cpy: 220.0, + x: 210.0, y: 220.0, + }, + PathSegment::SvgArc { + radius_x: 50.0, radius_y: 50.0, + rotation: 30.0, + large_arc: true, sweep: true, + x: 230.0, y: 240.0 + }, + PathSegment::ClosePath + ); + + test!(all_segments_2, "m 10 20 l 30 40 h 50 v 60 c 70 80 90 100 110 120 s 130 140 150 160 + q 170 180 190 200 t 210 220 a 50 50 30 1 1 230 240 z", + PathSegment::MoveTo { x: 10.0, y: 20.0 }, + PathSegment::LineTo { x: 40.0, y: 60.0 }, + PathSegment::LineTo { x: 90.0, y: 60.0 }, + PathSegment::LineTo { x: 90.0, y: 120.0 }, + PathSegment::Bezier { + cp1x: 160.0, cp1y: 200.0, + cp2x: 180.0, cp2y: 220.0, + x: 200.0, y: 240.0, + }, + PathSegment::Bezier { + cp1x: 220.0, cp1y: 260.0, //? + cp2x: 330.0, cp2y: 380.0, + x: 350.0, y: 400.0, + }, + PathSegment::Quadratic { + cpx: 520.0, cpy: 580.0, + x: 540.0, y: 600.0, + }, + PathSegment::Quadratic { + cpx: 560.0, cpy: 620.0, //? + x: 750.0, y: 820.0 + }, + PathSegment::SvgArc { + radius_x: 50.0, radius_y: 50.0, + rotation: 30.0, + large_arc: true, sweep: true, + x: 980.0, y: 1060.0 + }, + PathSegment::ClosePath + ); + + test!(close_path_1, "M10 20 L 30 40 ZM 100 200 L 300 400", + PathSegment::MoveTo { x: 10.0, y: 20.0 }, + PathSegment::LineTo { x: 30.0, y: 40.0 }, + PathSegment::ClosePath, + PathSegment::MoveTo { x: 100.0, y: 200.0 }, + PathSegment::LineTo { x: 300.0, y: 400.0 } + ); + + test!(close_path_2, "M10 20 L 30 40 zM 100 200 L 300 400", + PathSegment::MoveTo { x: 10.0, y: 20.0 }, + PathSegment::LineTo { x: 30.0, y: 40.0 }, + PathSegment::ClosePath, + PathSegment::MoveTo { x: 100.0, y: 200.0 }, + PathSegment::LineTo { x: 300.0, y: 400.0 } + ); + + test!(close_path_3, "M10 20 L 30 40 Z Z Z", + PathSegment::MoveTo { x: 10.0, y: 20.0 }, + PathSegment::LineTo { x: 30.0, y: 40.0 }, + PathSegment::ClosePath, + PathSegment::ClosePath, + PathSegment::ClosePath + ); + + // first token should be EndOfStream + test!(invalid_1, "M\t.", ); + + // ClosePath can't be followed by a number + test!(invalid_2, "M 0 0 Z 2", + PathSegment::MoveTo { x: 0.0, y: 0.0 }, + PathSegment::ClosePath + ); + + // ClosePath can be followed by any command + test!(invalid_3, "M 0 0 Z H 10", + PathSegment::MoveTo { x: 0.0, y: 0.0 }, + PathSegment::ClosePath, + PathSegment::LineTo { x: 10.0, y: 0.0 } + ); +} diff --git a/components/script/svgpath/stream.rs b/components/script/svgpath/stream.rs new file mode 100644 index 00000000000..2ed044ba3b6 --- /dev/null +++ b/components/script/svgpath/stream.rs @@ -0,0 +1,162 @@ +// Copyright 2018 the SVG Types Authors +// Copyright 2025 the Servo Authors +// SPDX-License-Identifier: Apache-2.0 OR MIT + +use crate::svgpath::Error; + +/// A streaming text parsing interface. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct Stream<'a> { + text: &'a str, + pos: usize, +} + +impl<'a> From<&'a str> for Stream<'a> { + #[inline] + fn from(text: &'a str) -> Self { + Stream { text, pos: 0 } + } +} + +impl<'a> Stream<'a> { + /// Returns the current position in bytes. + #[inline] + pub fn pos(&self) -> usize { + self.pos + } + + /// Sets current position equal to the end. + /// + /// Used to indicate end of parsing on error. + #[inline] + pub fn jump_to_end(&mut self) { + self.pos = self.text.len(); + } + + /// Checks if the stream is reached the end. + /// + /// Any [`pos()`] value larger than original text length indicates stream end. + /// + /// Accessing stream after reaching end via safe methods will produce + /// an error. + /// + /// Accessing stream after reaching end via *_unchecked methods will produce + /// a Rust's bound checking error. + /// + /// [`pos()`]: #method.pos + #[inline] + pub fn at_end(&self) -> bool { + self.pos >= self.text.len() + } + + /// Returns a byte from a current stream position. + #[inline] + pub fn curr_byte(&self) -> Result { + if self.at_end() { + return Err(Error); + } + + Ok(self.curr_byte_unchecked()) + } + + /// Returns a byte from a current stream position. + /// + /// # Panics + /// + /// - if the current position is after the end of the data + #[inline] + pub fn curr_byte_unchecked(&self) -> u8 { + self.text.as_bytes()[self.pos] + } + + /// Checks that current byte is equal to provided. + /// + /// Returns `false` if no bytes left. + #[inline] + pub fn is_curr_byte_eq(&self, c: u8) -> bool { + if !self.at_end() { + self.curr_byte_unchecked() == c + } else { + false + } + } + + /// Returns a next byte from a current stream position. + #[inline] + pub fn next_byte(&self) -> Result { + if self.pos + 1 >= self.text.len() { + return Err(Error); + } + + Ok(self.text.as_bytes()[self.pos + 1]) + } + + /// Advances by `n` bytes. + #[inline] + pub fn advance(&mut self, n: usize) { + debug_assert!(self.pos + n <= self.text.len()); + self.pos += n; + } + + /// Skips whitespaces. + /// + /// Accepted values: `' ' \n \r \t`. + pub fn skip_spaces(&mut self) { + while !self.at_end() && matches!(self.curr_byte_unchecked(), b' ' | b'\t' | b'\n' | b'\r') { + self.advance(1); + } + } + + /// Consumes bytes by the predicate. + pub fn skip_bytes(&mut self, f: F) + where + F: Fn(&Stream<'_>, u8) -> bool, + { + while !self.at_end() { + let c = self.curr_byte_unchecked(); + if f(self, c) { + self.advance(1); + } else { + break; + } + } + } + + /// Slices data from `pos` to the current position. + #[inline] + pub fn slice_back(&self, pos: usize) -> &'a str { + &self.text[pos..self.pos] + } + + /// Skips digits. + pub fn skip_digits(&mut self) { + self.skip_bytes(|_, c| c.is_ascii_digit()); + } + + #[inline] + pub(crate) fn parse_list_separator(&mut self) { + if self.is_curr_byte_eq(b',') { + self.advance(1); + } + } + + // By the SVG spec 'large-arc' and 'sweep' must contain only one char + // and can be written without any separators, e.g.: 10 20 30 01 10 20. + pub(crate) fn parse_flag(&mut self) -> Result { + self.skip_spaces(); + + let c = self.curr_byte()?; + match c { + b'0' | b'1' => { + self.advance(1); + if self.is_curr_byte_eq(b',') { + self.advance(1); + } + self.skip_spaces(); + + Ok(c == b'1') + }, + _ => Err(Error), + } + } +} diff --git a/components/script_bindings/webidls/CanvasRenderingContext2D.webidl b/components/script_bindings/webidls/CanvasRenderingContext2D.webidl index 4479d068186..92f61126859 100644 --- a/components/script_bindings/webidls/CanvasRenderingContext2D.webidl +++ b/components/script_bindings/webidls/CanvasRenderingContext2D.webidl @@ -117,15 +117,15 @@ interface mixin CanvasDrawPath { // path API (see also CanvasPath) undefined beginPath(); undefined fill(optional CanvasFillRule fillRule = "nonzero"); - //void fill(Path2D path, optional CanvasFillRule fillRule = "nonzero"); + undefined fill(Path2D path, optional CanvasFillRule fillRule = "nonzero"); undefined stroke(); - //void stroke(Path2D path); + undefined stroke(Path2D path); undefined clip(optional CanvasFillRule fillRule = "nonzero"); - //void clip(Path2D path, optional CanvasFillRule fillRule = "nonzero"); + undefined clip(Path2D path, optional CanvasFillRule fillRule = "nonzero"); boolean isPointInPath(unrestricted double x, unrestricted double y, optional CanvasFillRule fillRule = "nonzero"); - //boolean isPointInPath(Path2D path, unrestricted double x, unrestricted double y, - // optional CanvasFillRule fillRule = "nonzero"); + boolean isPointInPath(Path2D path, unrestricted double x, unrestricted double y, + optional CanvasFillRule fillRule = "nonzero"); //boolean isPointInStroke(unrestricted double x, unrestricted double y); //boolean isPointInStroke(Path2D path, unrestricted double x, unrestricted double y); }; @@ -262,3 +262,12 @@ interface ImageData { [Throws] readonly attribute Uint8ClampedArray data; //readonly attribute PredefinedColorSpace colorSpace; }; + +[Exposed=(Window,Worker)] +interface Path2D { + constructor(); + constructor(Path2D other); + constructor(DOMString pathString); + undefined addPath(Path2D path/*, SVGMatrix? transformation*/); +}; +Path2D includes CanvasPath; diff --git a/components/shared/canvas/canvas.rs b/components/shared/canvas/canvas.rs index 1db7675cf42..b01246bf9c1 100644 --- a/components/shared/canvas/canvas.rs +++ b/components/shared/canvas/canvas.rs @@ -13,6 +13,59 @@ use serde_bytes::ByteBuf; use style::color::AbsoluteColor; use style::properties::style_structs::Font as FontStyleStruct; +#[derive(Clone, Copy, Debug, Deserialize, MallocSizeOf, PartialEq, Serialize)] +pub enum PathSegment { + ClosePath, + MoveTo { + x: f32, + y: f32, + }, + LineTo { + x: f32, + y: f32, + }, + Quadratic { + cpx: f32, + cpy: f32, + x: f32, + y: f32, + }, + Bezier { + cp1x: f32, + cp1y: f32, + cp2x: f32, + cp2y: f32, + x: f32, + y: f32, + }, + ArcTo { + cp1x: f32, + cp1y: f32, + cp2x: f32, + cp2y: f32, + radius: f32, + }, + Ellipse { + x: f32, + y: f32, + radius_x: f32, + radius_y: f32, + rotation: f32, + start_angle: f32, + end_angle: f32, + anticlockwise: bool, + }, + SvgArc { + radius_x: f32, + radius_y: f32, + rotation: f32, + large_arc: bool, + sweep: bool, + x: f32, + y: f32, + }, +} + #[derive(Clone, Debug, Deserialize, Serialize)] pub enum FillRule { Nonzero, @@ -41,14 +94,17 @@ pub enum Canvas2dMsg { BezierCurveTo(Point2D, Point2D, Point2D), ClearRect(Rect), Clip, + ClipPath(Vec), ClosePath, Ellipse(Point2D, f32, f32, f32, f32, f32, bool), Fill(FillOrStrokeStyle), + FillPath(FillOrStrokeStyle, Vec), FillText(String, f64, f64, Option, FillOrStrokeStyle, bool), FillRect(Rect, FillOrStrokeStyle), GetImageData(Rect, Size2D, IpcBytesSender), GetTransform(IpcSender>), - IsPointInPath(f64, f64, FillRule, IpcSender), + IsPointInCurrentPath(f64, f64, FillRule, IpcSender), + IsPointInPath(Vec, f64, f64, FillRule, IpcSender), LineTo(Point2D), MoveTo(Point2D), MeasureText(String, IpcSender), @@ -59,6 +115,7 @@ pub enum Canvas2dMsg { SaveContext, StrokeRect(Rect, FillOrStrokeStyle), Stroke(FillOrStrokeStyle), + StrokePath(FillOrStrokeStyle, Vec), SetLineWidth(f32), SetLineCap(LineCapStyle), SetLineJoin(LineJoinStyle), diff --git a/python/tidy/tidy.py b/python/tidy/tidy.py index 4109e95786c..f312811456b 100644 --- a/python/tidy/tidy.py +++ b/python/tidy/tidy.py @@ -215,6 +215,9 @@ def uncomment(line): def is_apache_licensed(header): + if "SPDX-License-Identifier: Apache-2.0 OR MIT" in header: + return True + if APACHE in header: return any(c in header for c in COPYRIGHT) diff --git a/tests/wpt/meta-legacy-layout/html/canvas/element/layers/2d.layer.nested.html.ini b/tests/wpt/meta-legacy-layout/html/canvas/element/layers/2d.layer.nested.html.ini new file mode 100644 index 00000000000..5d0bc4fed5a --- /dev/null +++ b/tests/wpt/meta-legacy-layout/html/canvas/element/layers/2d.layer.nested.html.ini @@ -0,0 +1,2 @@ +[2d.layer.nested.html] + expected: FAIL diff --git a/tests/wpt/meta-legacy-layout/html/canvas/element/layers/2d.layer.shadow-from-outside-canvas.long-distance-with-clipping.ctx-filter.html.ini b/tests/wpt/meta-legacy-layout/html/canvas/element/layers/2d.layer.shadow-from-outside-canvas.long-distance-with-clipping.ctx-filter.html.ini new file mode 100644 index 00000000000..d62728c4e39 --- /dev/null +++ b/tests/wpt/meta-legacy-layout/html/canvas/element/layers/2d.layer.shadow-from-outside-canvas.long-distance-with-clipping.ctx-filter.html.ini @@ -0,0 +1,2 @@ +[2d.layer.shadow-from-outside-canvas.long-distance-with-clipping.ctx-filter.html] + expected: FAIL diff --git a/tests/wpt/meta-legacy-layout/html/canvas/element/layers/2d.layer.shadow-from-outside-canvas.long-distance-with-clipping.layer-filter.tentative.html.ini b/tests/wpt/meta-legacy-layout/html/canvas/element/layers/2d.layer.shadow-from-outside-canvas.long-distance-with-clipping.layer-filter.tentative.html.ini new file mode 100644 index 00000000000..3194f2c628e --- /dev/null +++ b/tests/wpt/meta-legacy-layout/html/canvas/element/layers/2d.layer.shadow-from-outside-canvas.long-distance-with-clipping.layer-filter.tentative.html.ini @@ -0,0 +1,2 @@ +[2d.layer.shadow-from-outside-canvas.long-distance-with-clipping.layer-filter.tentative.html] + expected: FAIL diff --git a/tests/wpt/meta-legacy-layout/html/canvas/element/layers/2d.layer.shadow-from-outside-canvas.short-distance-with-clipping.ctx-filter.html.ini b/tests/wpt/meta-legacy-layout/html/canvas/element/layers/2d.layer.shadow-from-outside-canvas.short-distance-with-clipping.ctx-filter.html.ini new file mode 100644 index 00000000000..2081a100db5 --- /dev/null +++ b/tests/wpt/meta-legacy-layout/html/canvas/element/layers/2d.layer.shadow-from-outside-canvas.short-distance-with-clipping.ctx-filter.html.ini @@ -0,0 +1,2 @@ +[2d.layer.shadow-from-outside-canvas.short-distance-with-clipping.ctx-filter.html] + expected: FAIL diff --git a/tests/wpt/meta-legacy-layout/html/canvas/element/layers/2d.layer.shadow-from-outside-canvas.short-distance-with-clipping.layer-filter.tentative.html.ini b/tests/wpt/meta-legacy-layout/html/canvas/element/layers/2d.layer.shadow-from-outside-canvas.short-distance-with-clipping.layer-filter.tentative.html.ini new file mode 100644 index 00000000000..95c1d08d9a0 --- /dev/null +++ b/tests/wpt/meta-legacy-layout/html/canvas/element/layers/2d.layer.shadow-from-outside-canvas.short-distance-with-clipping.layer-filter.tentative.html.ini @@ -0,0 +1,2 @@ +[2d.layer.shadow-from-outside-canvas.short-distance-with-clipping.layer-filter.tentative.html] + expected: FAIL diff --git a/tests/wpt/meta-legacy-layout/html/canvas/offscreen/layers/2d.layer.nested.html.ini b/tests/wpt/meta-legacy-layout/html/canvas/offscreen/layers/2d.layer.nested.html.ini new file mode 100644 index 00000000000..5d0bc4fed5a --- /dev/null +++ b/tests/wpt/meta-legacy-layout/html/canvas/offscreen/layers/2d.layer.nested.html.ini @@ -0,0 +1,2 @@ +[2d.layer.nested.html] + expected: FAIL diff --git a/tests/wpt/meta-legacy-layout/html/canvas/offscreen/layers/2d.layer.shadow-from-outside-canvas.long-distance-with-clipping.ctx-filter.html.ini b/tests/wpt/meta-legacy-layout/html/canvas/offscreen/layers/2d.layer.shadow-from-outside-canvas.long-distance-with-clipping.ctx-filter.html.ini new file mode 100644 index 00000000000..d62728c4e39 --- /dev/null +++ b/tests/wpt/meta-legacy-layout/html/canvas/offscreen/layers/2d.layer.shadow-from-outside-canvas.long-distance-with-clipping.ctx-filter.html.ini @@ -0,0 +1,2 @@ +[2d.layer.shadow-from-outside-canvas.long-distance-with-clipping.ctx-filter.html] + expected: FAIL diff --git a/tests/wpt/meta-legacy-layout/html/canvas/offscreen/layers/2d.layer.shadow-from-outside-canvas.long-distance-with-clipping.layer-filter.tentative.html.ini b/tests/wpt/meta-legacy-layout/html/canvas/offscreen/layers/2d.layer.shadow-from-outside-canvas.long-distance-with-clipping.layer-filter.tentative.html.ini new file mode 100644 index 00000000000..3194f2c628e --- /dev/null +++ b/tests/wpt/meta-legacy-layout/html/canvas/offscreen/layers/2d.layer.shadow-from-outside-canvas.long-distance-with-clipping.layer-filter.tentative.html.ini @@ -0,0 +1,2 @@ +[2d.layer.shadow-from-outside-canvas.long-distance-with-clipping.layer-filter.tentative.html] + expected: FAIL diff --git a/tests/wpt/meta-legacy-layout/html/canvas/offscreen/layers/2d.layer.shadow-from-outside-canvas.short-distance-with-clipping.ctx-filter.html.ini b/tests/wpt/meta-legacy-layout/html/canvas/offscreen/layers/2d.layer.shadow-from-outside-canvas.short-distance-with-clipping.ctx-filter.html.ini new file mode 100644 index 00000000000..2081a100db5 --- /dev/null +++ b/tests/wpt/meta-legacy-layout/html/canvas/offscreen/layers/2d.layer.shadow-from-outside-canvas.short-distance-with-clipping.ctx-filter.html.ini @@ -0,0 +1,2 @@ +[2d.layer.shadow-from-outside-canvas.short-distance-with-clipping.ctx-filter.html] + expected: FAIL diff --git a/tests/wpt/meta-legacy-layout/html/canvas/offscreen/layers/2d.layer.shadow-from-outside-canvas.short-distance-with-clipping.layer-filter.tentative.html.ini b/tests/wpt/meta-legacy-layout/html/canvas/offscreen/layers/2d.layer.shadow-from-outside-canvas.short-distance-with-clipping.layer-filter.tentative.html.ini new file mode 100644 index 00000000000..95c1d08d9a0 --- /dev/null +++ b/tests/wpt/meta-legacy-layout/html/canvas/offscreen/layers/2d.layer.shadow-from-outside-canvas.short-distance-with-clipping.layer-filter.tentative.html.ini @@ -0,0 +1,2 @@ +[2d.layer.shadow-from-outside-canvas.short-distance-with-clipping.layer-filter.tentative.html] + expected: FAIL diff --git a/tests/wpt/meta/html/canvas/element/layers/2d.layer.nested.html.ini b/tests/wpt/meta/html/canvas/element/layers/2d.layer.nested.html.ini new file mode 100644 index 00000000000..5d0bc4fed5a --- /dev/null +++ b/tests/wpt/meta/html/canvas/element/layers/2d.layer.nested.html.ini @@ -0,0 +1,2 @@ +[2d.layer.nested.html] + expected: FAIL diff --git a/tests/wpt/meta/html/canvas/element/layers/2d.layer.shadow-from-outside-canvas.long-distance-with-clipping.ctx-filter.html.ini b/tests/wpt/meta/html/canvas/element/layers/2d.layer.shadow-from-outside-canvas.long-distance-with-clipping.ctx-filter.html.ini new file mode 100644 index 00000000000..d62728c4e39 --- /dev/null +++ b/tests/wpt/meta/html/canvas/element/layers/2d.layer.shadow-from-outside-canvas.long-distance-with-clipping.ctx-filter.html.ini @@ -0,0 +1,2 @@ +[2d.layer.shadow-from-outside-canvas.long-distance-with-clipping.ctx-filter.html] + expected: FAIL diff --git a/tests/wpt/meta/html/canvas/element/layers/2d.layer.shadow-from-outside-canvas.long-distance-with-clipping.layer-filter.tentative.html.ini b/tests/wpt/meta/html/canvas/element/layers/2d.layer.shadow-from-outside-canvas.long-distance-with-clipping.layer-filter.tentative.html.ini new file mode 100644 index 00000000000..3194f2c628e --- /dev/null +++ b/tests/wpt/meta/html/canvas/element/layers/2d.layer.shadow-from-outside-canvas.long-distance-with-clipping.layer-filter.tentative.html.ini @@ -0,0 +1,2 @@ +[2d.layer.shadow-from-outside-canvas.long-distance-with-clipping.layer-filter.tentative.html] + expected: FAIL diff --git a/tests/wpt/meta/html/canvas/element/layers/2d.layer.shadow-from-outside-canvas.short-distance-with-clipping.ctx-filter.html.ini b/tests/wpt/meta/html/canvas/element/layers/2d.layer.shadow-from-outside-canvas.short-distance-with-clipping.ctx-filter.html.ini new file mode 100644 index 00000000000..2081a100db5 --- /dev/null +++ b/tests/wpt/meta/html/canvas/element/layers/2d.layer.shadow-from-outside-canvas.short-distance-with-clipping.ctx-filter.html.ini @@ -0,0 +1,2 @@ +[2d.layer.shadow-from-outside-canvas.short-distance-with-clipping.ctx-filter.html] + expected: FAIL diff --git a/tests/wpt/meta/html/canvas/element/layers/2d.layer.shadow-from-outside-canvas.short-distance-with-clipping.layer-filter.tentative.html.ini b/tests/wpt/meta/html/canvas/element/layers/2d.layer.shadow-from-outside-canvas.short-distance-with-clipping.layer-filter.tentative.html.ini new file mode 100644 index 00000000000..95c1d08d9a0 --- /dev/null +++ b/tests/wpt/meta/html/canvas/element/layers/2d.layer.shadow-from-outside-canvas.short-distance-with-clipping.layer-filter.tentative.html.ini @@ -0,0 +1,2 @@ +[2d.layer.shadow-from-outside-canvas.short-distance-with-clipping.layer-filter.tentative.html] + expected: FAIL diff --git a/tests/wpt/meta/html/canvas/element/path-objects/2d.path.arc.negative.html.ini b/tests/wpt/meta/html/canvas/element/path-objects/2d.path.arc.negative.html.ini deleted file mode 100644 index 4b585270b2e..00000000000 --- a/tests/wpt/meta/html/canvas/element/path-objects/2d.path.arc.negative.html.ini +++ /dev/null @@ -1,4 +0,0 @@ -[2d.path.arc.negative.html] - [arc() with negative radius throws INDEX_SIZE_ERR] - expected: FAIL - diff --git a/tests/wpt/meta/html/canvas/element/path-objects/2d.path.arcTo.negative.html.ini b/tests/wpt/meta/html/canvas/element/path-objects/2d.path.arcTo.negative.html.ini deleted file mode 100644 index fef6c797f0e..00000000000 --- a/tests/wpt/meta/html/canvas/element/path-objects/2d.path.arcTo.negative.html.ini +++ /dev/null @@ -1,4 +0,0 @@ -[2d.path.arcTo.negative.html] - [arcTo() with negative radius throws an exception] - expected: FAIL - diff --git a/tests/wpt/meta/html/canvas/element/path-objects/2d.path.isPointInpath.invalid.html.ini b/tests/wpt/meta/html/canvas/element/path-objects/2d.path.isPointInpath.invalid.html.ini deleted file mode 100644 index f45442d6bba..00000000000 --- a/tests/wpt/meta/html/canvas/element/path-objects/2d.path.isPointInpath.invalid.html.ini +++ /dev/null @@ -1,4 +0,0 @@ -[2d.path.isPointInpath.invalid.html] - [Verify isPointInPath throws exceptions with invalid inputs.] - expected: FAIL - diff --git a/tests/wpt/meta/html/canvas/offscreen/layers/2d.layer.nested.html.ini b/tests/wpt/meta/html/canvas/offscreen/layers/2d.layer.nested.html.ini new file mode 100644 index 00000000000..5d0bc4fed5a --- /dev/null +++ b/tests/wpt/meta/html/canvas/offscreen/layers/2d.layer.nested.html.ini @@ -0,0 +1,2 @@ +[2d.layer.nested.html] + expected: FAIL diff --git a/tests/wpt/meta/html/canvas/offscreen/layers/2d.layer.shadow-from-outside-canvas.long-distance-with-clipping.ctx-filter.html.ini b/tests/wpt/meta/html/canvas/offscreen/layers/2d.layer.shadow-from-outside-canvas.long-distance-with-clipping.ctx-filter.html.ini new file mode 100644 index 00000000000..d62728c4e39 --- /dev/null +++ b/tests/wpt/meta/html/canvas/offscreen/layers/2d.layer.shadow-from-outside-canvas.long-distance-with-clipping.ctx-filter.html.ini @@ -0,0 +1,2 @@ +[2d.layer.shadow-from-outside-canvas.long-distance-with-clipping.ctx-filter.html] + expected: FAIL diff --git a/tests/wpt/meta/html/canvas/offscreen/layers/2d.layer.shadow-from-outside-canvas.long-distance-with-clipping.layer-filter.tentative.html.ini b/tests/wpt/meta/html/canvas/offscreen/layers/2d.layer.shadow-from-outside-canvas.long-distance-with-clipping.layer-filter.tentative.html.ini new file mode 100644 index 00000000000..3194f2c628e --- /dev/null +++ b/tests/wpt/meta/html/canvas/offscreen/layers/2d.layer.shadow-from-outside-canvas.long-distance-with-clipping.layer-filter.tentative.html.ini @@ -0,0 +1,2 @@ +[2d.layer.shadow-from-outside-canvas.long-distance-with-clipping.layer-filter.tentative.html] + expected: FAIL diff --git a/tests/wpt/meta/html/canvas/offscreen/layers/2d.layer.shadow-from-outside-canvas.short-distance-with-clipping.ctx-filter.html.ini b/tests/wpt/meta/html/canvas/offscreen/layers/2d.layer.shadow-from-outside-canvas.short-distance-with-clipping.ctx-filter.html.ini new file mode 100644 index 00000000000..2081a100db5 --- /dev/null +++ b/tests/wpt/meta/html/canvas/offscreen/layers/2d.layer.shadow-from-outside-canvas.short-distance-with-clipping.ctx-filter.html.ini @@ -0,0 +1,2 @@ +[2d.layer.shadow-from-outside-canvas.short-distance-with-clipping.ctx-filter.html] + expected: FAIL diff --git a/tests/wpt/meta/html/canvas/offscreen/layers/2d.layer.shadow-from-outside-canvas.short-distance-with-clipping.layer-filter.tentative.html.ini b/tests/wpt/meta/html/canvas/offscreen/layers/2d.layer.shadow-from-outside-canvas.short-distance-with-clipping.layer-filter.tentative.html.ini new file mode 100644 index 00000000000..95c1d08d9a0 --- /dev/null +++ b/tests/wpt/meta/html/canvas/offscreen/layers/2d.layer.shadow-from-outside-canvas.short-distance-with-clipping.layer-filter.tentative.html.ini @@ -0,0 +1,2 @@ +[2d.layer.shadow-from-outside-canvas.short-distance-with-clipping.layer-filter.tentative.html] + expected: FAIL diff --git a/tests/wpt/meta/html/canvas/offscreen/path-objects/2d.path.arc.negative.html.ini b/tests/wpt/meta/html/canvas/offscreen/path-objects/2d.path.arc.negative.html.ini deleted file mode 100644 index 4b585270b2e..00000000000 --- a/tests/wpt/meta/html/canvas/offscreen/path-objects/2d.path.arc.negative.html.ini +++ /dev/null @@ -1,4 +0,0 @@ -[2d.path.arc.negative.html] - [arc() with negative radius throws INDEX_SIZE_ERR] - expected: FAIL - diff --git a/tests/wpt/meta/html/canvas/offscreen/path-objects/2d.path.arc.negative.worker.js.ini b/tests/wpt/meta/html/canvas/offscreen/path-objects/2d.path.arc.negative.worker.js.ini deleted file mode 100644 index ac7a3a23e80..00000000000 --- a/tests/wpt/meta/html/canvas/offscreen/path-objects/2d.path.arc.negative.worker.js.ini +++ /dev/null @@ -1,4 +0,0 @@ -[2d.path.arc.negative.worker.html] - [arc() with negative radius throws INDEX_SIZE_ERR] - expected: FAIL - diff --git a/tests/wpt/meta/html/canvas/offscreen/path-objects/2d.path.arcTo.negative.html.ini b/tests/wpt/meta/html/canvas/offscreen/path-objects/2d.path.arcTo.negative.html.ini deleted file mode 100644 index fef6c797f0e..00000000000 --- a/tests/wpt/meta/html/canvas/offscreen/path-objects/2d.path.arcTo.negative.html.ini +++ /dev/null @@ -1,4 +0,0 @@ -[2d.path.arcTo.negative.html] - [arcTo() with negative radius throws an exception] - expected: FAIL - diff --git a/tests/wpt/meta/html/canvas/offscreen/path-objects/2d.path.arcTo.negative.worker.js.ini b/tests/wpt/meta/html/canvas/offscreen/path-objects/2d.path.arcTo.negative.worker.js.ini deleted file mode 100644 index e47251dbcd1..00000000000 --- a/tests/wpt/meta/html/canvas/offscreen/path-objects/2d.path.arcTo.negative.worker.js.ini +++ /dev/null @@ -1,4 +0,0 @@ -[2d.path.arcTo.negative.worker.html] - [arcTo() with negative radius throws an exception] - expected: FAIL - diff --git a/tests/wpt/meta/html/canvas/offscreen/path-objects/2d.path.isPointInpath.invalid.html.ini b/tests/wpt/meta/html/canvas/offscreen/path-objects/2d.path.isPointInpath.invalid.html.ini deleted file mode 100644 index f81268e5c75..00000000000 --- a/tests/wpt/meta/html/canvas/offscreen/path-objects/2d.path.isPointInpath.invalid.html.ini +++ /dev/null @@ -1,3 +0,0 @@ -[2d.path.isPointInpath.invalid.html] - [Verify isPointInPath throws exceptions with invalid inputs.] - expected: FAIL diff --git a/tests/wpt/meta/html/canvas/offscreen/path-objects/2d.path.isPointInpath.invalid.worker.js.ini b/tests/wpt/meta/html/canvas/offscreen/path-objects/2d.path.isPointInpath.invalid.worker.js.ini deleted file mode 100644 index a7ec32a9fbb..00000000000 --- a/tests/wpt/meta/html/canvas/offscreen/path-objects/2d.path.isPointInpath.invalid.worker.js.ini +++ /dev/null @@ -1,3 +0,0 @@ -[2d.path.isPointInpath.invalid.worker.html] - [Verify isPointInPath throws exceptions with invalid inputs.] - expected: FAIL diff --git a/tests/wpt/meta/html/dom/idlharness.any.js.ini b/tests/wpt/meta/html/dom/idlharness.any.js.ini index 5e0b7c05c14..1ffefa4d7d9 100644 --- a/tests/wpt/meta/html/dom/idlharness.any.js.ini +++ b/tests/wpt/meta/html/dom/idlharness.any.js.ini @@ -5,57 +5,9 @@ [ImageData interface: attribute colorSpace] expected: FAIL - [Path2D interface: existence and properties of interface object] - expected: FAIL - - [Path2D interface object length] - expected: FAIL - - [Path2D interface object name] - expected: FAIL - - [Path2D interface: existence and properties of interface prototype object] - expected: FAIL - - [Path2D interface: existence and properties of interface prototype object's "constructor" property] - expected: FAIL - - [Path2D interface: existence and properties of interface prototype object's @@unscopables property] - expected: FAIL - - [Path2D interface: operation addPath(Path2D, optional DOMMatrix2DInit)] - expected: FAIL - - [Path2D interface: operation closePath()] - expected: FAIL - - [Path2D interface: operation moveTo(unrestricted double, unrestricted double)] - expected: FAIL - - [Path2D interface: operation lineTo(unrestricted double, unrestricted double)] - expected: FAIL - - [Path2D interface: operation quadraticCurveTo(unrestricted double, unrestricted double, unrestricted double, unrestricted double)] - expected: FAIL - - [Path2D interface: operation bezierCurveTo(unrestricted double, unrestricted double, unrestricted double, unrestricted double, unrestricted double, unrestricted double)] - expected: FAIL - - [Path2D interface: operation arcTo(unrestricted double, unrestricted double, unrestricted double, unrestricted double, unrestricted double)] - expected: FAIL - - [Path2D interface: operation rect(unrestricted double, unrestricted double, unrestricted double, unrestricted double)] - expected: FAIL - [Path2D interface: operation roundRect(unrestricted double, unrestricted double, unrestricted double, unrestricted double, optional (unrestricted double or DOMPointInit or sequence<(unrestricted double or DOMPointInit)>))] expected: FAIL - [Path2D interface: operation arc(unrestricted double, unrestricted double, unrestricted double, unrestricted double, unrestricted double, optional boolean)] - expected: FAIL - - [Path2D interface: operation ellipse(unrestricted double, unrestricted double, unrestricted double, unrestricted double, unrestricted double, unrestricted double, unrestricted double, optional boolean)] - expected: FAIL - [ImageBitmapRenderingContext interface: existence and properties of interface object] 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 73aae83e466..92bcad853c6 100644 --- a/tests/wpt/meta/html/dom/idlharness.https.html.ini +++ b/tests/wpt/meta/html/dom/idlharness.https.html.ini @@ -176,12 +176,6 @@ [DataTransfer interface: operation getData(DOMString)] expected: FAIL - [Path2D interface: operation closePath()] - expected: FAIL - - [Path2D interface: existence and properties of interface prototype object's @@unscopables property] - expected: FAIL - [History interface: attribute scrollRestoration] expected: FAIL @@ -368,9 +362,6 @@ [Stringification of window.applicationCache] expected: FAIL - [Path2D interface: operation rect(unrestricted double, unrestricted double, unrestricted double, unrestricted double)] - expected: FAIL - [DataTransfer interface: attribute dropEffect] expected: FAIL @@ -455,9 +446,6 @@ [ApplicationCache interface: constant UNCACHED on interface prototype object] expected: FAIL - [Path2D interface: operation ellipse(unrestricted double, unrestricted double, unrestricted double, unrestricted double, unrestricted double, unrestricted double, unrestricted double, optional boolean)] - expected: FAIL - [SVGElement interface: attribute dataset] expected: FAIL @@ -482,9 +470,6 @@ [CanvasRenderingContext2D interface: document.createElement("canvas").getContext("2d") must inherit property "drawFocusIfNeeded(Element)" with the proper type] expected: FAIL - [Path2D interface: operation arcTo(unrestricted double, unrestricted double, unrestricted double, unrestricted double, unrestricted double)] - expected: FAIL - [SVGElement interface: attribute autofocus] expected: FAIL @@ -500,9 +485,6 @@ [SVGElement interface: attribute onmouseover] expected: FAIL - [Path2D interface: operation addPath(Path2D, optional DOMMatrix2DInit)] - expected: FAIL - [BarProp interface: existence and properties of interface object] expected: FAIL @@ -545,9 +527,6 @@ [ApplicationCache interface: attribute onnoupdate] expected: FAIL - [Path2D interface: existence and properties of interface object] - expected: FAIL - [External interface: window.external must inherit property "IsSearchProviderInstalled()" with the proper type] expected: FAIL @@ -569,9 +548,6 @@ [External must be primary interface of window.external] expected: FAIL - [Path2D interface object name] - expected: FAIL - [ApplicationCache interface: constant OBSOLETE on interface object] expected: FAIL @@ -593,12 +569,6 @@ [SVGElement interface: attribute onwebkitanimationiteration] expected: FAIL - [Path2D interface object length] - expected: FAIL - - [Path2D interface: existence and properties of interface prototype object] - expected: FAIL - [DragEvent interface: existence and properties of interface prototype object] expected: FAIL @@ -620,15 +590,9 @@ [DOMStringList interface: location.ancestorOrigins must inherit property "item(unsigned long)" with the proper type] expected: FAIL - [Path2D interface: existence and properties of interface prototype object's "constructor" property] - expected: FAIL - [SVGElement interface: attribute onmouseenter] expected: FAIL - [Path2D interface: operation quadraticCurveTo(unrestricted double, unrestricted double, unrestricted double, unrestricted double)] - expected: FAIL - [CanvasRenderingContext2D interface: operation strokeText(DOMString, unrestricted double, unrestricted double, optional unrestricted double)] expected: FAIL @@ -638,15 +602,9 @@ [SharedWorker interface object length] expected: FAIL - [Path2D interface: operation lineTo(unrestricted double, unrestricted double)] - expected: FAIL - [DragEvent interface: existence and properties of interface object] expected: FAIL - [Path2D interface: operation arc(unrestricted double, unrestricted double, unrestricted double, unrestricted double, unrestricted double, optional boolean)] - expected: FAIL - [ApplicationCache interface: window.applicationCache must inherit property "onprogress" with the proper type] expected: FAIL @@ -695,9 +653,6 @@ [ImageBitmapRenderingContext interface: attribute canvas] expected: FAIL - [Path2D interface: operation bezierCurveTo(unrestricted double, unrestricted double, unrestricted double, unrestricted double, unrestricted double, unrestricted double)] - expected: FAIL - [ApplicationCache interface: window.applicationCache must inherit property "status" with the proper type] expected: FAIL @@ -866,9 +821,6 @@ [SVGElement interface: attribute onratechange] expected: FAIL - [Path2D interface: operation moveTo(unrestricted double, unrestricted double)] - expected: FAIL - [DragEvent interface: existence and properties of interface prototype object's "constructor" property] expected: FAIL @@ -4784,57 +4736,9 @@ [ImageData interface: new ImageData(10, 10) must inherit property "colorSpace" with the proper type] expected: FAIL - [Path2D interface: existence and properties of interface object] - expected: FAIL - - [Path2D interface object length] - expected: FAIL - - [Path2D interface object name] - expected: FAIL - - [Path2D interface: existence and properties of interface prototype object] - expected: FAIL - - [Path2D interface: existence and properties of interface prototype object's "constructor" property] - expected: FAIL - - [Path2D interface: existence and properties of interface prototype object's @@unscopables property] - expected: FAIL - - [Path2D interface: operation addPath(Path2D, optional DOMMatrix2DInit)] - expected: FAIL - - [Path2D interface: operation closePath()] - expected: FAIL - - [Path2D interface: operation moveTo(unrestricted double, unrestricted double)] - expected: FAIL - - [Path2D interface: operation lineTo(unrestricted double, unrestricted double)] - expected: FAIL - - [Path2D interface: operation quadraticCurveTo(unrestricted double, unrestricted double, unrestricted double, unrestricted double)] - expected: FAIL - - [Path2D interface: operation bezierCurveTo(unrestricted double, unrestricted double, unrestricted double, unrestricted double, unrestricted double, unrestricted double)] - expected: FAIL - - [Path2D interface: operation arcTo(unrestricted double, unrestricted double, unrestricted double, unrestricted double, unrestricted double)] - expected: FAIL - - [Path2D interface: operation rect(unrestricted double, unrestricted double, unrestricted double, unrestricted double)] - expected: FAIL - [Path2D interface: operation roundRect(unrestricted double, unrestricted double, unrestricted double, unrestricted double, optional (unrestricted double or DOMPointInit or sequence<(unrestricted double or DOMPointInit)>))] expected: FAIL - [Path2D interface: operation arc(unrestricted double, unrestricted double, unrestricted double, unrestricted double, unrestricted double, optional boolean)] - expected: FAIL - - [Path2D interface: operation ellipse(unrestricted double, unrestricted double, unrestricted double, unrestricted double, unrestricted double, unrestricted double, unrestricted double, optional boolean)] - expected: FAIL - [ImageBitmapRenderingContext interface: existence and properties of interface object] expected: FAIL diff --git a/tests/wpt/meta/html/semantics/embedded-content/the-canvas-element/historical.html.ini b/tests/wpt/meta/html/semantics/embedded-content/the-canvas-element/historical.html.ini deleted file mode 100644 index c71abde1354..00000000000 --- a/tests/wpt/meta/html/semantics/embedded-content/the-canvas-element/historical.html.ini +++ /dev/null @@ -1,2 +0,0 @@ -[historical.html] - expected: ERROR diff --git a/tests/wpt/meta/workers/semantics/interface-objects/001.worker.js.ini b/tests/wpt/meta/workers/semantics/interface-objects/001.worker.js.ini index c2f6b74fb71..a275ec93375 100644 --- a/tests/wpt/meta/workers/semantics/interface-objects/001.worker.js.ini +++ b/tests/wpt/meta/workers/semantics/interface-objects/001.worker.js.ini @@ -5,9 +5,6 @@ [The CanvasPath interface object should be exposed.] expected: FAIL - [The Path2D interface object should be exposed.] - expected: FAIL - [The IDBRequest interface object should be exposed.] expected: FAIL diff --git a/tests/wpt/mozilla/meta/MANIFEST.json b/tests/wpt/mozilla/meta/MANIFEST.json index 2d414e70a50..d158393e162 100644 --- a/tests/wpt/mozilla/meta/MANIFEST.json +++ b/tests/wpt/mozilla/meta/MANIFEST.json @@ -13503,14 +13503,14 @@ ] ], "interfaces.https.html": [ - "81f4d942f94366d8f9ecf22cfc3e1e22fe4ab8f1", + "ad0b03ac70483c152220978cee8b49e74b330fc6", [ null, {} ] ], "interfaces.worker.js": [ - "06eb8d3ba2334951cb1e0f791527ba118d4f13ec", + "f93f9c9f6c877e1915217b64591fe7e34fa7244c", [ "mozilla/interfaces.worker.html", {} diff --git a/tests/wpt/mozilla/tests/mozilla/interfaces.https.html b/tests/wpt/mozilla/tests/mozilla/interfaces.https.html index 81f4d942f94..ad0b03ac704 100644 --- a/tests/wpt/mozilla/tests/mozilla/interfaces.https.html +++ b/tests/wpt/mozilla/tests/mozilla/interfaces.https.html @@ -223,6 +223,7 @@ test_interfaces([ "OscillatorNode", "PageTransitionEvent", "PannerNode", + "Path2D", "Performance", "PerformanceEntry", "PerformanceMark", diff --git a/tests/wpt/mozilla/tests/mozilla/interfaces.worker.js b/tests/wpt/mozilla/tests/mozilla/interfaces.worker.js index 06eb8d3ba23..f93f9c9f6c8 100644 --- a/tests/wpt/mozilla/tests/mozilla/interfaces.worker.js +++ b/tests/wpt/mozilla/tests/mozilla/interfaces.worker.js @@ -45,6 +45,7 @@ test_interfaces([ "Notification", "OffscreenCanvas", "OffscreenCanvasRenderingContext2D", + "Path2D", "Performance", "PerformanceEntry", "PerformanceMark",