diff options
Diffstat (limited to 'components')
48 files changed, 1627 insertions, 1354 deletions
diff --git a/components/canvas/backend.rs b/components/canvas/backend.rs new file mode 100644 index 00000000000..53acbea8b8d --- /dev/null +++ b/components/canvas/backend.rs @@ -0,0 +1,243 @@ +/* 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 canvas_traits::canvas::{ + CompositionOrBlending, FillOrStrokeStyle, LineCapStyle, LineJoinStyle, +}; +use euclid::Angle; +use euclid::default::{Point2D, Rect, Size2D, Transform2D, Vector2D}; +use lyon_geom::Arc; +use style::color::AbsoluteColor; + +use crate::canvas_data::{CanvasPaintState, Filter, TextRun}; + +pub(crate) trait Backend: Clone + Sized { + type Pattern<'a>: PatternHelpers + Clone; + type StrokeOptions: StrokeOptionsHelpers + Clone; + type Color: Clone; + type DrawOptions: DrawOptionsHelpers + Clone; + type CompositionOp; + type DrawTarget: GenericDrawTarget<Self>; + type PathBuilder: GenericPathBuilder<Self>; + type SourceSurface; + type Bytes<'a>: AsRef<[u8]>; + type Path: PathHelpers<Self> + Clone; + type GradientStop; + type GradientStops; + + fn get_composition_op(&self, opts: &Self::DrawOptions) -> Self::CompositionOp; + fn need_to_draw_shadow(&self, color: &Self::Color) -> bool; + fn set_shadow_color(&mut self, color: AbsoluteColor, state: &mut CanvasPaintState<'_, Self>); + fn set_fill_style( + &mut self, + style: FillOrStrokeStyle, + state: &mut CanvasPaintState<'_, Self>, + drawtarget: &Self::DrawTarget, + ); + fn set_stroke_style( + &mut self, + style: FillOrStrokeStyle, + state: &mut CanvasPaintState<'_, Self>, + drawtarget: &Self::DrawTarget, + ); + fn set_global_composition( + &mut self, + op: CompositionOrBlending, + state: &mut CanvasPaintState<'_, Self>, + ); + fn create_drawtarget(&self, size: Size2D<u64>) -> Self::DrawTarget; + fn new_paint_state<'a>(&self) -> CanvasPaintState<'a, Self>; +} + +// This defines required methods for a DrawTarget (currently only implemented for raqote). The +// prototypes are derived from the now-removed Azure backend's methods. +pub(crate) trait GenericDrawTarget<B: Backend> { + fn clear_rect(&mut self, rect: &Rect<f32>); + fn copy_surface( + &mut self, + surface: B::SourceSurface, + source: Rect<i32>, + destination: Point2D<i32>, + ); + fn create_path_builder(&self) -> B::PathBuilder; + fn create_similar_draw_target(&self, size: &Size2D<i32>) -> Self; + fn create_source_surface_from_data(&self, data: &[u8]) -> Option<B::SourceSurface>; + fn draw_surface( + &mut self, + surface: B::SourceSurface, + dest: Rect<f64>, + source: Rect<f64>, + filter: Filter, + draw_options: &B::DrawOptions, + ); + fn draw_surface_with_shadow( + &self, + surface: B::SourceSurface, + dest: &Point2D<f32>, + color: &B::Color, + offset: &Vector2D<f32>, + sigma: f32, + operator: B::CompositionOp, + ); + fn fill(&mut self, path: &B::Path, pattern: B::Pattern<'_>, draw_options: &B::DrawOptions); + fn fill_text( + &mut self, + text_runs: Vec<TextRun>, + start: Point2D<f32>, + pattern: &B::Pattern<'_>, + draw_options: &B::DrawOptions, + ); + fn fill_rect( + &mut self, + rect: &Rect<f32>, + pattern: B::Pattern<'_>, + draw_options: Option<&B::DrawOptions>, + ); + fn get_size(&self) -> Size2D<i32>; + fn get_transform(&self) -> Transform2D<f32>; + fn pop_clip(&mut self); + fn push_clip(&mut self, path: &B::Path); + fn set_transform(&mut self, matrix: &Transform2D<f32>); + fn stroke( + &mut self, + path: &B::Path, + pattern: B::Pattern<'_>, + stroke_options: &B::StrokeOptions, + draw_options: &B::DrawOptions, + ); + fn stroke_line( + &mut self, + start: Point2D<f32>, + end: Point2D<f32>, + pattern: B::Pattern<'_>, + stroke_options: &B::StrokeOptions, + draw_options: &B::DrawOptions, + ); + fn stroke_rect( + &mut self, + rect: &Rect<f32>, + pattern: B::Pattern<'_>, + stroke_options: &B::StrokeOptions, + draw_options: &B::DrawOptions, + ); + fn surface(&self) -> B::SourceSurface; + fn bytes(&'_ self) -> B::Bytes<'_>; +} + +/// A generic PathBuilder that abstracts the interface for azure's and raqote's PathBuilder. +pub(crate) trait GenericPathBuilder<B: Backend> { + fn arc( + &mut self, + origin: Point2D<f32>, + radius: f32, + start_angle: f32, + end_angle: f32, + anticlockwise: bool, + ); + fn bezier_curve_to( + &mut self, + control_point1: &Point2D<f32>, + control_point2: &Point2D<f32>, + control_point3: &Point2D<f32>, + ); + fn close(&mut self); + #[allow(clippy::too_many_arguments)] + fn ellipse( + &mut self, + origin: Point2D<f32>, + radius_x: f32, + radius_y: f32, + rotation_angle: f32, + start_angle: f32, + end_angle: f32, + anticlockwise: bool, + ) { + let mut start = Angle::radians(start_angle); + let mut end = Angle::radians(end_angle); + + // Wrap angles mod 2 * PI if necessary + if !anticlockwise && start > end + Angle::two_pi() || + anticlockwise && end > start + Angle::two_pi() + { + start = start.positive(); + end = end.positive(); + } + + // Calculate the total arc we're going to sweep. + let sweep = match anticlockwise { + true => { + if end - start == Angle::two_pi() { + -Angle::two_pi() + } else if end > start { + -(Angle::two_pi() - (end - start)) + } else { + -(start - end) + } + }, + false => { + if start - end == Angle::two_pi() { + Angle::two_pi() + } else if start > end { + Angle::two_pi() - (start - end) + } else { + end - start + } + }, + }; + + let arc: Arc<f32> = Arc { + center: origin, + radii: Vector2D::new(radius_x, radius_y), + start_angle: start, + sweep_angle: sweep, + x_rotation: Angle::radians(rotation_angle), + }; + + self.line_to(arc.from()); + + arc.for_each_quadratic_bezier(&mut |q| { + self.quadratic_curve_to(&q.ctrl, &q.to); + }); + } + fn get_current_point(&mut self) -> Option<Point2D<f32>>; + fn line_to(&mut self, point: Point2D<f32>); + fn move_to(&mut self, point: Point2D<f32>); + fn quadratic_curve_to(&mut self, control_point: &Point2D<f32>, end_point: &Point2D<f32>); + fn svg_arc( + &mut self, + radius_x: f32, + radius_y: f32, + rotation_angle: f32, + large_arc: bool, + sweep: bool, + end_point: Point2D<f32>, + ); + fn finish(&mut self) -> B::Path; +} + +pub(crate) trait PatternHelpers { + fn is_zero_size_gradient(&self) -> bool; + fn draw_rect(&self, rect: &Rect<f32>) -> Rect<f32>; +} + +pub(crate) trait StrokeOptionsHelpers { + fn set_line_width(&mut self, _val: f32); + fn set_miter_limit(&mut self, _val: f32); + fn set_line_join(&mut self, val: LineJoinStyle); + fn set_line_cap(&mut self, val: LineCapStyle); + fn set_line_dash(&mut self, items: Vec<f32>); + fn set_line_dash_offset(&mut self, offset: f32); +} + +pub(crate) trait DrawOptionsHelpers { + fn set_alpha(&mut self, val: f32); +} + +pub(crate) trait PathHelpers<B: Backend> { + fn transformed_copy_to_builder(&self, transform: &Transform2D<f32>) -> B::PathBuilder; + + fn contains_point(&self, x: f64, y: f64, path_transform: &Transform2D<f32>) -> bool; + + fn copy_to_builder(&self) -> B::PathBuilder; +} diff --git a/components/canvas/canvas_data.rs b/components/canvas/canvas_data.rs index 2667b7f6b44..ea30589d0af 100644 --- a/components/canvas/canvas_data.rs +++ b/components/canvas/canvas_data.rs @@ -2,6 +2,7 @@ * 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::marker::PhantomData; use std::mem; use std::sync::Arc; @@ -16,7 +17,6 @@ use fonts::{ }; use ipc_channel::ipc::{IpcSender, IpcSharedMemory}; use log::warn; -use num_traits::ToPrimitive; use range::Range; use servo_arc::Arc as ServoArc; use snapshot::Snapshot; @@ -26,14 +26,17 @@ use unicode_script::Script; use webrender_api::units::RectExt as RectExt_; use webrender_api::{ImageDescriptor, ImageDescriptorFlags, ImageFormat, ImageKey}; -use crate::raqote_backend::Repetition; +use crate::backend::{ + Backend, DrawOptionsHelpers as _, GenericDrawTarget as _, GenericPathBuilder, PathHelpers, + PatternHelpers, StrokeOptionsHelpers as _, +}; // Asserts on WR texture cache update for zero sized image with raw data. // https://github.com/servo/webrender/blob/main/webrender/src/texture_cache.rs#L1475 const MIN_WR_IMAGE_SIZE: Size2D<u64> = Size2D::new(1, 1); -fn to_path(path: &[PathSegment], mut builder: Box<dyn GenericPathBuilder>) -> Path { - let mut build_ref = PathBuilderRef { +fn to_path<B: Backend>(path: &[PathSegment], mut builder: B::PathBuilder) -> B::Path { + let mut build_ref = PathBuilderRef::<B> { builder: &mut builder, transform: Transform2D::identity(), }; @@ -112,20 +115,20 @@ fn to_path(path: &[PathSegment], mut builder: Box<dyn GenericPathBuilder>) -> Pa /// draw the path, we convert it back to userspace and draw it /// with the correct transform applied. /// TODO: De-abstract now that Azure is removed? -enum PathState { +enum PathState<B: Backend> { /// Path builder in user-space. If a transform has been applied /// but no further path operations have occurred, it is stored /// in the optional field. - UserSpacePathBuilder(Box<dyn GenericPathBuilder>, Option<Transform2D<f32>>), + UserSpacePathBuilder(B::PathBuilder, Option<Transform2D<f32>>), /// Path builder in device-space. - DeviceSpacePathBuilder(Box<dyn GenericPathBuilder>), + DeviceSpacePathBuilder(B::PathBuilder), /// Path in user-space. If a transform has been applied but /// but no further path operations have occurred, it is stored /// in the optional field. - UserSpacePath(Path, Option<Transform2D<f32>>), + UserSpacePath(B::Path, Option<Transform2D<f32>>), } -impl PathState { +impl<B: Backend> PathState<B> { fn is_path(&self) -> bool { match *self { PathState::UserSpacePath(..) => true, @@ -133,7 +136,7 @@ impl PathState { } } - fn path(&self) -> &Path { + fn path(&self) -> &B::Path { match *self { PathState::UserSpacePath(ref p, _) => p, PathState::UserSpacePathBuilder(..) | PathState::DeviceSpacePathBuilder(..) => { @@ -143,84 +146,14 @@ impl PathState { } } -pub trait Backend { - fn get_composition_op(&self, opts: &DrawOptions) -> CompositionOp; - fn need_to_draw_shadow(&self, color: &Color) -> bool; - fn set_shadow_color(&mut self, color: AbsoluteColor, state: &mut CanvasPaintState<'_>); - fn set_fill_style( - &mut self, - style: FillOrStrokeStyle, - state: &mut CanvasPaintState<'_>, - drawtarget: &dyn GenericDrawTarget, - ); - fn set_stroke_style( - &mut self, - style: FillOrStrokeStyle, - state: &mut CanvasPaintState<'_>, - drawtarget: &dyn GenericDrawTarget, - ); - fn set_global_composition( - &mut self, - op: CompositionOrBlending, - state: &mut CanvasPaintState<'_>, - ); - fn create_drawtarget(&self, size: Size2D<u64>) -> Box<dyn GenericDrawTarget>; - fn recreate_paint_state<'a>(&self, state: &CanvasPaintState<'a>) -> CanvasPaintState<'a>; -} - -/// A generic PathBuilder that abstracts the interface for azure's and raqote's PathBuilder. -/// TODO: De-abstract now that Azure is removed? -pub trait GenericPathBuilder { - fn arc( - &mut self, - origin: Point2D<f32>, - radius: f32, - start_angle: f32, - end_angle: f32, - anticlockwise: bool, - ); - fn bezier_curve_to( - &mut self, - control_point1: &Point2D<f32>, - control_point2: &Point2D<f32>, - control_point3: &Point2D<f32>, - ); - fn close(&mut self); - #[allow(clippy::too_many_arguments)] - fn ellipse( - &mut self, - origin: Point2D<f32>, - radius_x: f32, - radius_y: f32, - rotation_angle: f32, - start_angle: f32, - end_angle: f32, - anticlockwise: bool, - ); - fn get_current_point(&mut self) -> Option<Point2D<f32>>; - fn line_to(&mut self, point: Point2D<f32>); - fn move_to(&mut self, point: Point2D<f32>); - fn quadratic_curve_to(&mut self, control_point: &Point2D<f32>, end_point: &Point2D<f32>); - fn svg_arc( - &mut self, - radius_x: f32, - radius_y: f32, - rotation_angle: f32, - large_arc: bool, - sweep: bool, - end_point: Point2D<f32>, - ); - fn finish(&mut self) -> Path; -} - /// A wrapper around a stored PathBuilder and an optional transformation that should be /// applied to any points to ensure they are in the matching device space. -struct PathBuilderRef<'a> { - builder: &'a mut Box<dyn GenericPathBuilder>, +struct PathBuilderRef<'a, B: Backend> { + builder: &'a mut B::PathBuilder, transform: Transform2D<f32>, } -impl PathBuilderRef<'_> { +impl<B: Backend> PathBuilderRef<'_, B> { fn line_to(&mut self, pt: &Point2D<f32>) { let pt = self.transform.transform_point(*pt); self.builder.line_to(pt); @@ -341,7 +274,7 @@ impl PathBuilderRef<'_> { } #[allow(clippy::too_many_arguments)] - pub fn ellipse( + pub(crate) fn ellipse( &mut self, center: &Point2D<f32>, radius_x: f32, @@ -430,9 +363,9 @@ impl UnshapedTextRun<'_> { } } -pub struct TextRun { - pub font: FontRef, - pub glyphs: Arc<GlyphStore>, +pub(crate) struct TextRun { + pub(crate) font: FontRef, + pub(crate) glyphs: Arc<GlyphStore>, } impl TextRun { @@ -458,149 +391,31 @@ impl TextRun { } } -// This defines required methods for a DrawTarget (currently only implemented for raqote). The -// prototypes are derived from the now-removed Azure backend's methods. -pub trait GenericDrawTarget { - fn clear_rect(&mut self, rect: &Rect<f32>); - fn copy_surface( - &mut self, - surface: SourceSurface, - source: Rect<i32>, - destination: Point2D<i32>, - ); - fn create_gradient_stops(&self, gradient_stops: Vec<GradientStop>) -> GradientStops; - fn create_path_builder(&self) -> Box<dyn GenericPathBuilder>; - fn create_similar_draw_target(&self, size: &Size2D<i32>) -> Box<dyn GenericDrawTarget>; - fn create_source_surface_from_data(&self, data: &[u8]) -> Option<SourceSurface>; - fn draw_surface( - &mut self, - surface: SourceSurface, - dest: Rect<f64>, - source: Rect<f64>, - filter: Filter, - draw_options: &DrawOptions, - ); - fn draw_surface_with_shadow( - &self, - surface: SourceSurface, - dest: &Point2D<f32>, - color: &Color, - offset: &Vector2D<f32>, - sigma: f32, - operator: CompositionOp, - ); - fn fill(&mut self, path: &Path, pattern: Pattern, draw_options: &DrawOptions); - fn fill_text( - &mut self, - text_runs: Vec<TextRun>, - start: Point2D<f32>, - pattern: &Pattern, - draw_options: &DrawOptions, - ); - fn fill_rect(&mut self, rect: &Rect<f32>, pattern: Pattern, draw_options: Option<&DrawOptions>); - fn get_size(&self) -> Size2D<i32>; - fn get_transform(&self) -> Transform2D<f32>; - fn pop_clip(&mut self); - fn push_clip(&mut self, path: &Path); - fn set_transform(&mut self, matrix: &Transform2D<f32>); - fn snapshot(&self) -> SourceSurface; - fn stroke( - &mut self, - path: &Path, - pattern: Pattern, - stroke_options: &StrokeOptions, - draw_options: &DrawOptions, - ); - fn stroke_line( - &mut self, - start: Point2D<f32>, - end: Point2D<f32>, - pattern: Pattern, - stroke_options: &StrokeOptions, - draw_options: &DrawOptions, - ); - fn stroke_rect( - &mut self, - rect: &Rect<f32>, - pattern: Pattern, - stroke_options: &StrokeOptions, - draw_options: &DrawOptions, - ); - fn snapshot_data(&self) -> &[u8]; -} - -pub enum GradientStop { - Raqote(raqote::GradientStop), -} - -pub enum GradientStops { - Raqote(Vec<raqote::GradientStop>), -} - -#[derive(Clone)] -pub enum Color { - Raqote(raqote::SolidSource), -} - -#[derive(Clone)] -pub enum CompositionOp { - Raqote(raqote::BlendMode), -} - -#[derive(Clone)] -pub enum SourceSurface { - Raqote(Vec<u8>), // TODO: See if we can avoid the alloc (probably?) -} - -#[derive(Clone)] -pub enum Path { - Raqote(raqote::Path), -} - -#[derive(Clone)] -pub enum Pattern<'a> { - Raqote(crate::raqote_backend::Pattern<'a>), -} - -#[derive(Clone)] -pub enum DrawOptions { - Raqote(raqote::DrawOptions), -} - -#[derive(Clone)] -pub enum StrokeOptions { - Raqote(raqote::StrokeStyle), -} - #[derive(Clone, Copy)] -pub enum Filter { +pub(crate) enum Filter { Bilinear, Nearest, } -pub struct CanvasData<'a> { - backend: Box<dyn Backend>, - drawtarget: Box<dyn GenericDrawTarget>, - path_state: Option<PathState>, - state: CanvasPaintState<'a>, - saved_states: Vec<CanvasPaintState<'a>>, +pub(crate) struct CanvasData<'a, B: Backend> { + backend: B, + drawtarget: B::DrawTarget, + path_state: Option<PathState<B>>, + state: CanvasPaintState<'a, B>, + saved_states: Vec<CanvasPaintState<'a, B>>, compositor_api: CrossProcessCompositorApi, image_key: ImageKey, font_context: Arc<FontContext>, } -fn create_backend() -> Box<dyn Backend> { - Box::new(crate::raqote_backend::RaqoteBackend) -} - -impl<'a> CanvasData<'a> { - pub fn new( +impl<'a, B: Backend> CanvasData<'a, B> { + pub(crate) fn new( size: Size2D<u64>, compositor_api: CrossProcessCompositorApi, font_context: Arc<FontContext>, - ) -> CanvasData<'a> { + backend: B, + ) -> CanvasData<'a, B> { let size = size.max(MIN_WR_IMAGE_SIZE); - let backend = create_backend(); let draw_target = backend.create_drawtarget(size); let image_key = compositor_api.generate_image_key().unwrap(); let descriptor = ImageDescriptor { @@ -611,13 +426,13 @@ impl<'a> CanvasData<'a> { flags: ImageDescriptorFlags::empty(), }; let data = - SerializableImageData::Raw(IpcSharedMemory::from_bytes(draw_target.snapshot_data())); + SerializableImageData::Raw(IpcSharedMemory::from_bytes(draw_target.bytes().as_ref())); compositor_api.update_images(vec![ImageUpdate::AddImage(image_key, descriptor, data)]); CanvasData { + state: backend.new_paint_state(), backend, drawtarget: draw_target, path_state: None, - state: CanvasPaintState::default(), saved_states: vec![], compositor_api, image_key, @@ -625,11 +440,11 @@ impl<'a> CanvasData<'a> { } } - pub fn image_key(&self) -> ImageKey { + pub(crate) fn image_key(&self) -> ImageKey { self.image_key } - pub fn draw_image( + pub(crate) fn draw_image( &mut self, image_data: &[u8], image_size: Size2D<u64>, @@ -648,8 +463,8 @@ impl<'a> CanvasData<'a> { }; let draw_options = self.state.draw_options.clone(); - let writer = |draw_target: &mut dyn GenericDrawTarget| { - write_image( + let writer = |draw_target: &mut B::DrawTarget| { + write_image::<B>( draw_target, image_data, source_rect.size, @@ -669,15 +484,15 @@ impl<'a> CanvasData<'a> { // TODO(pylbrecht) pass another closure for raqote self.draw_with_shadow(&rect, writer); } else { - writer(&mut *self.drawtarget); + writer(&mut self.drawtarget); } } - pub fn save_context_state(&mut self) { + pub(crate) fn save_context_state(&mut self) { self.saved_states.push(self.state.clone()); } - pub fn restore_context_state(&mut self) { + pub(crate) fn restore_context_state(&mut self) { if let Some(state) = self.saved_states.pop() { let _ = mem::replace(&mut self.state, state); self.drawtarget.set_transform(&self.state.transform); @@ -685,7 +500,7 @@ impl<'a> CanvasData<'a> { } } - pub fn fill_text_with_size( + pub(crate) fn fill_text_with_size( &mut self, text: String, x: f64, @@ -764,7 +579,7 @@ impl<'a> CanvasData<'a> { } /// <https://html.spec.whatwg.org/multipage/#text-preparation-algorithm> - pub fn fill_text( + pub(crate) fn fill_text( &mut self, text: String, x: f64, @@ -782,7 +597,7 @@ impl<'a> CanvasData<'a> { /// <https://html.spec.whatwg.org/multipage/#text-preparation-algorithm> /// <https://html.spec.whatwg.org/multipage/#dom-context-2d-measuretext> - pub fn measure_text(&mut self, text: String) -> TextMetrics { + pub(crate) fn measure_text(&mut self, text: String) -> TextMetrics { // > Step 2: Replace all ASCII whitespace in text with U+0020 SPACE characters. let text = replace_ascii_whitespace(text); let Some(ref font_style) = self.state.font_style else { @@ -934,49 +749,15 @@ impl<'a> CanvasData<'a> { point2(x + anchor_x, y + anchor_y) } - pub fn fill_rect(&mut self, rect: &Rect<f32>) { + pub(crate) fn fill_rect(&mut self, rect: &Rect<f32>) { if self.state.fill_style.is_zero_size_gradient() { return; // Paint nothing if gradient size is zero. } - let draw_rect = match &self.state.fill_style { - Pattern::Raqote(pattern) => match pattern { - crate::raqote_backend::Pattern::Surface(pattern) => { - let pattern_rect = Rect::new(Point2D::origin(), pattern.size()); - let mut draw_rect = rect.intersection(&pattern_rect).unwrap_or(Rect::zero()); - - match pattern.repetition() { - Repetition::NoRepeat => { - draw_rect.size.width = - draw_rect.size.width.min(pattern_rect.size.width); - draw_rect.size.height = - draw_rect.size.height.min(pattern_rect.size.height); - }, - Repetition::RepeatX => { - draw_rect.size.width = rect.size.width; - draw_rect.size.height = - draw_rect.size.height.min(pattern_rect.size.height); - }, - Repetition::RepeatY => { - draw_rect.size.height = rect.size.height; - draw_rect.size.width = - draw_rect.size.width.min(pattern_rect.size.width); - }, - Repetition::Repeat => { - draw_rect = *rect; - }, - } - - draw_rect - }, - crate::raqote_backend::Pattern::Color(..) | - crate::raqote_backend::Pattern::LinearGradient(..) | - crate::raqote_backend::Pattern::RadialGradient(..) => *rect, - }, - }; + let draw_rect = self.state.fill_style.draw_rect(rect); if self.need_to_draw_shadow() { - self.draw_with_shadow(&draw_rect, |new_draw_target: &mut dyn GenericDrawTarget| { + self.draw_with_shadow(&draw_rect, |new_draw_target: &mut B::DrawTarget| { new_draw_target.fill_rect( &draw_rect, self.state.fill_style.clone(), @@ -992,17 +773,17 @@ impl<'a> CanvasData<'a> { } } - pub fn clear_rect(&mut self, rect: &Rect<f32>) { + pub(crate) fn clear_rect(&mut self, rect: &Rect<f32>) { self.drawtarget.clear_rect(rect); } - pub fn stroke_rect(&mut self, rect: &Rect<f32>) { + pub(crate) fn stroke_rect(&mut self, rect: &Rect<f32>) { if self.state.stroke_style.is_zero_size_gradient() { return; // Paint nothing if gradient size is zero. } if self.need_to_draw_shadow() { - self.draw_with_shadow(rect, |new_draw_target: &mut dyn GenericDrawTarget| { + self.draw_with_shadow(rect, |new_draw_target: &mut B::DrawTarget| { new_draw_target.stroke_rect( rect, self.state.stroke_style.clone(), @@ -1030,12 +811,12 @@ impl<'a> CanvasData<'a> { } } - pub fn begin_path(&mut self) { + pub(crate) fn begin_path(&mut self) { // Erase any traces of previous paths that existed before this. self.path_state = None; } - pub fn close_path(&mut self) { + pub(crate) fn close_path(&mut self) { self.path_builder().close(); } @@ -1097,14 +878,14 @@ impl<'a> CanvasData<'a> { assert!(self.path_state.as_ref().unwrap().is_path()) } - fn path(&self) -> &Path { + fn path(&self) -> &B::Path { self.path_state .as_ref() .expect("Should have called ensure_path()") .path() } - pub fn fill(&mut self) { + pub(crate) fn fill(&mut self) { if self.state.fill_style.is_zero_size_gradient() { return; // Paint nothing if gradient size is zero. } @@ -1113,16 +894,16 @@ impl<'a> CanvasData<'a> { self.drawtarget.fill( &self.path().clone(), self.state.fill_style.clone(), - &self.state.draw_options, + &self.state.draw_options.clone(), ); } - pub fn fill_path(&mut self, path: &[PathSegment]) { + pub(crate) 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()); + let path = to_path::<B>(path, self.drawtarget.create_path_builder()); self.drawtarget.fill( &path, @@ -1131,7 +912,7 @@ impl<'a> CanvasData<'a> { ); } - pub fn stroke(&mut self) { + pub(crate) fn stroke(&mut self) { if self.state.stroke_style.is_zero_size_gradient() { return; // Paint nothing if gradient size is zero. } @@ -1145,12 +926,12 @@ impl<'a> CanvasData<'a> { ); } - pub fn stroke_path(&mut self, path: &[PathSegment]) { + pub(crate) 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()); + let path = to_path::<B>(path, self.drawtarget.create_path_builder()); self.drawtarget.stroke( &path, @@ -1160,18 +941,18 @@ impl<'a> CanvasData<'a> { ); } - pub fn clip(&mut self) { + pub(crate) 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()); + pub(crate) fn clip_path(&mut self, path: &[PathSegment]) { + let path = to_path::<B>(path, self.drawtarget.create_path_builder()); self.drawtarget.push_clip(&path); } - pub fn is_point_in_path( + pub(crate) fn is_point_in_path( &mut self, x: f64, y: f64, @@ -1190,7 +971,7 @@ impl<'a> CanvasData<'a> { chan.send(result).unwrap(); } - pub fn is_point_in_path_( + pub(crate) fn is_point_in_path_( &mut self, path: &[PathSegment], x: f64, @@ -1202,7 +983,7 @@ impl<'a> CanvasData<'a> { Some(PathState::UserSpacePath(_, Some(transform))) => transform, Some(_) | None => &self.drawtarget.get_transform(), }; - let result = to_path(path, self.drawtarget.create_path_builder()).contains_point( + let result = to_path::<B>(path, self.drawtarget.create_path_builder()).contains_point( x, y, path_transform, @@ -1210,15 +991,15 @@ impl<'a> CanvasData<'a> { chan.send(result).unwrap(); } - pub fn move_to(&mut self, point: &Point2D<f32>) { + pub(crate) fn move_to(&mut self, point: &Point2D<f32>) { self.path_builder().move_to(point); } - pub fn line_to(&mut self, point: &Point2D<f32>) { + pub(crate) fn line_to(&mut self, point: &Point2D<f32>) { self.path_builder().line_to(point); } - fn path_builder(&mut self) -> PathBuilderRef { + fn path_builder(&mut self) -> PathBuilderRef<B> { if self.path_state.is_none() { self.path_state = Some(PathState::UserSpacePathBuilder( self.drawtarget.create_path_builder(), @@ -1283,18 +1064,18 @@ impl<'a> CanvasData<'a> { } } - pub fn rect(&mut self, rect: &Rect<f32>) { + pub(crate) fn rect(&mut self, rect: &Rect<f32>) { self.path_builder().rect(rect); } - pub fn quadratic_curve_to(&mut self, cp: &Point2D<f32>, endpoint: &Point2D<f32>) { + pub(crate) fn quadratic_curve_to(&mut self, cp: &Point2D<f32>, endpoint: &Point2D<f32>) { if self.path_state.is_none() { self.move_to(cp); } self.path_builder().quadratic_curve_to(cp, endpoint); } - pub fn bezier_curve_to( + pub(crate) fn bezier_curve_to( &mut self, cp1: &Point2D<f32>, cp2: &Point2D<f32>, @@ -1306,7 +1087,7 @@ impl<'a> CanvasData<'a> { self.path_builder().bezier_curve_to(cp1, cp2, endpoint); } - pub fn arc( + pub(crate) fn arc( &mut self, center: &Point2D<f32>, radius: f32, @@ -1318,12 +1099,12 @@ impl<'a> CanvasData<'a> { .arc(center, radius, start_angle, end_angle, ccw); } - pub fn arc_to(&mut self, cp1: &Point2D<f32>, cp2: &Point2D<f32>, radius: f32) { + pub(crate) fn arc_to(&mut self, cp1: &Point2D<f32>, cp2: &Point2D<f32>, radius: f32) { self.path_builder().arc_to(cp1, cp2, radius); } #[allow(clippy::too_many_arguments)] - pub fn ellipse( + pub(crate) fn ellipse( &mut self, center: &Point2D<f32>, radius_x: f32, @@ -1344,45 +1125,45 @@ impl<'a> CanvasData<'a> { ); } - pub fn set_fill_style(&mut self, style: FillOrStrokeStyle) { + pub(crate) fn set_fill_style(&mut self, style: FillOrStrokeStyle) { self.backend - .set_fill_style(style, &mut self.state, &*self.drawtarget); + .set_fill_style(style, &mut self.state, &self.drawtarget); } - pub fn set_stroke_style(&mut self, style: FillOrStrokeStyle) { + pub(crate) fn set_stroke_style(&mut self, style: FillOrStrokeStyle) { self.backend - .set_stroke_style(style, &mut self.state, &*self.drawtarget); + .set_stroke_style(style, &mut self.state, &self.drawtarget); } - pub fn set_line_width(&mut self, width: f32) { + pub(crate) fn set_line_width(&mut self, width: f32) { self.state.stroke_opts.set_line_width(width); } - pub fn set_line_cap(&mut self, cap: LineCapStyle) { + pub(crate) fn set_line_cap(&mut self, cap: LineCapStyle) { self.state.stroke_opts.set_line_cap(cap); } - pub fn set_line_join(&mut self, join: LineJoinStyle) { + pub(crate) fn set_line_join(&mut self, join: LineJoinStyle) { self.state.stroke_opts.set_line_join(join); } - pub fn set_miter_limit(&mut self, limit: f32) { + pub(crate) fn set_miter_limit(&mut self, limit: f32) { self.state.stroke_opts.set_miter_limit(limit); } - pub fn set_line_dash(&mut self, items: Vec<f32>) { + pub(crate) fn set_line_dash(&mut self, items: Vec<f32>) { self.state.stroke_opts.set_line_dash(items); } - pub fn set_line_dash_offset(&mut self, offset: f32) { + pub(crate) fn set_line_dash_offset(&mut self, offset: f32) { self.state.stroke_opts.set_line_dash_offset(offset); } - pub fn get_transform(&self) -> Transform2D<f32> { + pub(crate) fn get_transform(&self) -> Transform2D<f32> { self.drawtarget.get_transform() } - pub fn set_transform(&mut self, transform: &Transform2D<f32>) { + pub(crate) fn set_transform(&mut self, transform: &Transform2D<f32>) { // If there is an in-progress path, store the existing transformation required // to move between device and user space. match self.path_state.as_mut() { @@ -1398,32 +1179,28 @@ impl<'a> CanvasData<'a> { self.drawtarget.set_transform(transform) } - pub fn set_global_alpha(&mut self, alpha: f32) { + pub(crate) fn set_global_alpha(&mut self, alpha: f32) { self.state.draw_options.set_alpha(alpha); } - pub fn set_global_composition(&mut self, op: CompositionOrBlending) { + pub(crate) fn set_global_composition(&mut self, op: CompositionOrBlending) { self.backend.set_global_composition(op, &mut self.state); } - pub fn recreate(&mut self, size: Option<Size2D<u64>>) { + pub(crate) fn recreate(&mut self, size: Option<Size2D<u64>>) { let size = size .unwrap_or_else(|| self.drawtarget.get_size().to_u64()) .max(MIN_WR_IMAGE_SIZE); self.drawtarget = self .backend .create_drawtarget(Size2D::new(size.width, size.height)); - self.state = self.backend.recreate_paint_state(&self.state); + self.state = self.backend.new_paint_state(); self.saved_states.clear(); self.update_image_rendering(); } - pub fn snapshot(&self) { - self.drawtarget.snapshot_data(); - } - /// Update image in WebRender - pub fn update_image_rendering(&mut self) { + pub(crate) fn update_image_rendering(&mut self) { let descriptor = ImageDescriptor { size: self.drawtarget.get_size().cast_unit(), stride: None, @@ -1432,7 +1209,7 @@ impl<'a> CanvasData<'a> { flags: ImageDescriptorFlags::empty(), }; let data = SerializableImageData::Raw(IpcSharedMemory::from_bytes( - self.drawtarget.snapshot_data(), + self.drawtarget.bytes().as_ref(), )); self.compositor_api @@ -1444,7 +1221,7 @@ impl<'a> CanvasData<'a> { } // https://html.spec.whatwg.org/multipage/#dom-context-2d-putimagedata - pub fn put_image_data(&mut self, mut imagedata: Vec<u8>, rect: Rect<u64>) { + pub(crate) fn put_image_data(&mut self, mut imagedata: Vec<u8>, rect: Rect<u64>) { assert_eq!(imagedata.len() % 4, 0); assert_eq!(rect.size.area() as usize, imagedata.len() / 4); pixels::rgba8_byte_swap_and_premultiply_inplace(&mut imagedata); @@ -1459,31 +1236,31 @@ impl<'a> CanvasData<'a> { ); } - pub fn set_shadow_offset_x(&mut self, value: f64) { + pub(crate) fn set_shadow_offset_x(&mut self, value: f64) { self.state.shadow_offset_x = value; } - pub fn set_shadow_offset_y(&mut self, value: f64) { + pub(crate) fn set_shadow_offset_y(&mut self, value: f64) { self.state.shadow_offset_y = value; } - pub fn set_shadow_blur(&mut self, value: f64) { + pub(crate) fn set_shadow_blur(&mut self, value: f64) { self.state.shadow_blur = value; } - pub fn set_shadow_color(&mut self, value: AbsoluteColor) { + pub(crate) fn set_shadow_color(&mut self, value: AbsoluteColor) { self.backend.set_shadow_color(value, &mut self.state); } - pub fn set_font(&mut self, font_style: FontStyleStruct) { + pub(crate) fn set_font(&mut self, font_style: FontStyleStruct) { self.state.font_style = Some(ServoArc::new(font_style)) } - pub fn set_text_align(&mut self, text_align: TextAlign) { + pub(crate) fn set_text_align(&mut self, text_align: TextAlign) { self.state.text_align = text_align; } - pub fn set_text_baseline(&mut self, text_baseline: TextBaseline) { + pub(crate) fn set_text_baseline(&mut self, text_baseline: TextBaseline) { self.state.text_baseline = text_baseline; } @@ -1495,7 +1272,7 @@ impl<'a> CanvasData<'a> { self.state.shadow_blur != 0.0f64) } - fn create_draw_target_for_shadow(&self, source_rect: &Rect<f32>) -> Box<dyn GenericDrawTarget> { + fn create_draw_target_for_shadow(&self, source_rect: &Rect<f32>) -> B::DrawTarget { let mut draw_target = self.drawtarget.create_similar_draw_target(&Size2D::new( source_rect.size.width as i32, source_rect.size.height as i32, @@ -1509,13 +1286,13 @@ impl<'a> CanvasData<'a> { fn draw_with_shadow<F>(&self, rect: &Rect<f32>, draw_shadow_source: F) where - F: FnOnce(&mut dyn GenericDrawTarget), + F: FnOnce(&mut B::DrawTarget), { let shadow_src_rect = self.state.transform.outer_transformed_rect(rect); let mut new_draw_target = self.create_draw_target_for_shadow(&shadow_src_rect); - draw_shadow_source(&mut *new_draw_target); + draw_shadow_source(&mut new_draw_target); self.drawtarget.draw_surface_with_shadow( - new_draw_target.snapshot(), + new_draw_target.surface(), &Point2D::new(shadow_src_rect.origin.x, shadow_src_rect.origin.y), &self.state.shadow_color, &Vector2D::new( @@ -1531,7 +1308,7 @@ impl<'a> CanvasData<'a> { /// canvas_size: The size of the canvas we're reading from /// read_rect: The area of the canvas we want to read from #[allow(unsafe_code)] - pub fn read_pixels( + pub(crate) fn read_pixels( &self, read_rect: Option<Rect<u64>>, canvas_size: Option<Size2D<u64>>, @@ -1546,11 +1323,11 @@ impl<'a> CanvasData<'a> { { vec![] } else { - let bytes = self.drawtarget.snapshot_data(); - pixels::rgba8_get_rect(bytes, canvas_size, read_rect).to_vec() + pixels::rgba8_get_rect(self.drawtarget.bytes().as_ref(), canvas_size, read_rect) + .to_vec() } } else { - self.drawtarget.snapshot_data().to_vec() + self.drawtarget.bytes().as_ref().to_vec() }; Snapshot::from_vec( @@ -1564,7 +1341,7 @@ impl<'a> CanvasData<'a> { } } -impl Drop for CanvasData<'_> { +impl<B: Backend> Drop for CanvasData<'_, B> { fn drop(&mut self) { self.compositor_api .update_images(vec![ImageUpdate::DeleteImage(self.image_key)]); @@ -1575,20 +1352,21 @@ const HANGING_BASELINE_DEFAULT: f32 = 0.8; const IDEOGRAPHIC_BASELINE_DEFAULT: f32 = 0.5; #[derive(Clone)] -pub struct CanvasPaintState<'a> { - pub draw_options: DrawOptions, - pub fill_style: Pattern<'a>, - pub stroke_style: Pattern<'a>, - pub stroke_opts: StrokeOptions, +pub(crate) struct CanvasPaintState<'a, B: Backend> { + pub(crate) draw_options: B::DrawOptions, + pub(crate) fill_style: B::Pattern<'a>, + pub(crate) stroke_style: B::Pattern<'a>, + pub(crate) stroke_opts: B::StrokeOptions, /// The current 2D transform matrix. - pub transform: Transform2D<f32>, - pub shadow_offset_x: f64, - pub shadow_offset_y: f64, - pub shadow_blur: f64, - pub shadow_color: Color, - pub font_style: Option<ServoArc<FontStyleStruct>>, - pub text_align: TextAlign, - pub text_baseline: TextBaseline, + pub(crate) transform: Transform2D<f32>, + pub(crate) shadow_offset_x: f64, + pub(crate) shadow_offset_y: f64, + pub(crate) shadow_blur: f64, + pub(crate) shadow_color: B::Color, + pub(crate) font_style: Option<ServoArc<FontStyleStruct>>, + pub(crate) text_align: TextAlign, + pub(crate) text_baseline: TextBaseline, + pub(crate) _backend: PhantomData<B>, } /// It writes an image to the destination target @@ -1598,14 +1376,14 @@ pub struct CanvasPaintState<'a> { /// dest_rect: Area of the destination target where the pixels will be copied /// smoothing_enabled: It determines if smoothing is applied to the image result /// premultiply: Determines whenever the image data should be premultiplied or not -fn write_image( - draw_target: &mut dyn GenericDrawTarget, +fn write_image<B: Backend>( + draw_target: &mut B::DrawTarget, mut image_data: Vec<u8>, image_size: Size2D<f64>, dest_rect: Rect<f64>, smoothing_enabled: bool, premultiply: bool, - draw_options: &DrawOptions, + draw_options: &B::DrawOptions, ) { if image_data.is_empty() { return; @@ -1634,25 +1412,11 @@ fn write_image( draw_target.draw_surface(source_surface, dest_rect, image_rect, filter, draw_options); } -pub trait RectToi32 { - fn to_i32(&self) -> Rect<i32>; +pub(crate) trait RectToi32 { fn ceil(&self) -> Rect<f64>; } impl RectToi32 for Rect<f64> { - fn to_i32(&self) -> Rect<i32> { - Rect::new( - Point2D::new( - self.origin.x.to_i32().unwrap(), - self.origin.y.to_i32().unwrap(), - ), - Size2D::new( - self.size.width.to_i32().unwrap(), - self.size.height.to_i32().unwrap(), - ), - ) - } - fn ceil(&self) -> Rect<f64> { Rect::new( Point2D::new(self.origin.x.ceil(), self.origin.y.ceil()), @@ -1661,22 +1425,6 @@ impl RectToi32 for Rect<f64> { } } -pub trait RectExt { - fn to_u64(&self) -> Rect<u64>; -} - -impl RectExt for Rect<f64> { - fn to_u64(&self) -> Rect<u64> { - self.cast() - } -} - -impl RectExt for Rect<u32> { - fn to_u64(&self) -> Rect<u64> { - self.cast() - } -} - fn replace_ascii_whitespace(text: String) -> String { text.chars() .map(|c| match c { diff --git a/components/canvas/canvas_paint_thread.rs b/components/canvas/canvas_paint_thread.rs index bb940d7ef81..82a221d560d 100644 --- a/components/canvas/canvas_paint_thread.rs +++ b/components/canvas/canvas_paint_thread.rs @@ -11,18 +11,21 @@ use canvas_traits::ConstellationCanvasMsg; use canvas_traits::canvas::*; use compositing_traits::CrossProcessCompositorApi; use crossbeam_channel::{Sender, select, unbounded}; -use euclid::default::Size2D; +use euclid::default::{Point2D, Rect, Size2D, Transform2D}; use fonts::{FontContext, SystemFontServiceProxy}; use ipc_channel::ipc::{self, IpcSender}; use ipc_channel::router::ROUTER; use log::warn; use net_traits::ResourceThreads; +use style::color::AbsoluteColor; +use style::properties::style_structs::Font as FontStyleStruct; use webrender_api::ImageKey; use crate::canvas_data::*; +use crate::raqote_backend::RaqoteBackend; pub struct CanvasPaintThread<'a> { - canvases: HashMap<CanvasId, CanvasData<'a>>, + canvases: HashMap<CanvasId, Canvas<'a>>, next_canvas_id: CanvasId, compositor_api: CrossProcessCompositorApi, font_context: Arc<FontContext>, @@ -113,10 +116,14 @@ impl<'a> CanvasPaintThread<'a> { let canvas_id = self.next_canvas_id; self.next_canvas_id.0 += 1; - let canvas_data = - CanvasData::new(size, self.compositor_api.clone(), self.font_context.clone()); + let canvas_data = CanvasData::new( + size, + self.compositor_api.clone(), + self.font_context.clone(), + RaqoteBackend, + ); let image_key = canvas_data.image_key(); - self.canvases.insert(canvas_id, canvas_data); + self.canvases.insert(canvas_id, Canvas::Raqote(canvas_data)); (canvas_id, image_key) } @@ -276,7 +283,347 @@ impl<'a> CanvasPaintThread<'a> { } } - fn canvas(&mut self, canvas_id: CanvasId) -> &mut CanvasData<'a> { + fn canvas(&mut self, canvas_id: CanvasId) -> &mut Canvas<'a> { self.canvases.get_mut(&canvas_id).expect("Bogus canvas id") } } + +enum Canvas<'a> { + Raqote(CanvasData<'a, RaqoteBackend>), +} + +impl Canvas<'_> { + fn set_fill_style(&mut self, style: FillOrStrokeStyle) { + match self { + Canvas::Raqote(canvas_data) => canvas_data.set_fill_style(style), + } + } + + fn fill(&mut self) { + match self { + Canvas::Raqote(canvas_data) => canvas_data.fill(), + } + } + + fn fill_text(&mut self, text: String, x: f64, y: f64, max_width: Option<f64>, is_rtl: bool) { + match self { + Canvas::Raqote(canvas_data) => canvas_data.fill_text(text, x, y, max_width, is_rtl), + } + } + + fn fill_rect(&mut self, rect: &Rect<f32>) { + match self { + Canvas::Raqote(canvas_data) => canvas_data.fill_rect(rect), + } + } + + fn set_stroke_style(&mut self, style: FillOrStrokeStyle) { + match self { + Canvas::Raqote(canvas_data) => canvas_data.set_stroke_style(style), + } + } + + fn stroke_rect(&mut self, rect: &Rect<f32>) { + match self { + Canvas::Raqote(canvas_data) => canvas_data.stroke_rect(rect), + } + } + + fn begin_path(&mut self) { + match self { + Canvas::Raqote(canvas_data) => canvas_data.begin_path(), + } + } + + fn close_path(&mut self) { + match self { + Canvas::Raqote(canvas_data) => canvas_data.close_path(), + } + } + + fn fill_path(&mut self, path: &[PathSegment]) { + match self { + Canvas::Raqote(canvas_data) => canvas_data.fill_path(path), + } + } + + fn stroke(&mut self) { + match self { + Canvas::Raqote(canvas_data) => canvas_data.stroke(), + } + } + + fn stroke_path(&mut self, path: &[PathSegment]) { + match self { + Canvas::Raqote(canvas_data) => canvas_data.stroke_path(path), + } + } + + fn clip(&mut self) { + match self { + Canvas::Raqote(canvas_data) => canvas_data.clip(), + } + } + + fn is_point_in_path(&mut self, x: f64, y: f64, fill_rule: FillRule, chan: IpcSender<bool>) { + match self { + Canvas::Raqote(canvas_data) => canvas_data.is_point_in_path(x, y, fill_rule, chan), + } + } + + fn is_point_in_path_( + &mut self, + path: &[PathSegment], + x: f64, + y: f64, + fill_rule: FillRule, + chan: IpcSender<bool>, + ) { + match self { + Canvas::Raqote(canvas_data) => { + canvas_data.is_point_in_path_(path, x, y, fill_rule, chan) + }, + } + } + + fn clear_rect(&mut self, rect: &Rect<f32>) { + match self { + Canvas::Raqote(canvas_data) => canvas_data.clear_rect(rect), + } + } + + fn draw_image( + &mut self, + data: &[u8], + size: Size2D<u64>, + dest_rect: Rect<f64>, + source_rect: Rect<f64>, + smoothing_enabled: bool, + is_premultiplied: bool, + ) { + match self { + Canvas::Raqote(canvas_data) => canvas_data.draw_image( + data, + size, + dest_rect, + source_rect, + smoothing_enabled, + is_premultiplied, + ), + } + } + + fn read_pixels( + &mut self, + read_rect: Option<Rect<u64>>, + canvas_size: Option<Size2D<u64>>, + ) -> snapshot::Snapshot { + match self { + Canvas::Raqote(canvas_data) => canvas_data.read_pixels(read_rect, canvas_size), + } + } + + fn move_to(&mut self, point: &Point2D<f32>) { + match self { + Canvas::Raqote(canvas_data) => canvas_data.move_to(point), + } + } + + fn line_to(&mut self, point: &Point2D<f32>) { + match self { + Canvas::Raqote(canvas_data) => canvas_data.line_to(point), + } + } + + fn rect(&mut self, rect: &Rect<f32>) { + match self { + Canvas::Raqote(canvas_data) => canvas_data.rect(rect), + } + } + + fn quadratic_curve_to(&mut self, cp: &Point2D<f32>, pt: &Point2D<f32>) { + match self { + Canvas::Raqote(canvas_data) => canvas_data.quadratic_curve_to(cp, pt), + } + } + + fn bezier_curve_to(&mut self, cp1: &Point2D<f32>, cp2: &Point2D<f32>, pt: &Point2D<f32>) { + match self { + Canvas::Raqote(canvas_data) => canvas_data.bezier_curve_to(cp1, cp2, pt), + } + } + + fn arc(&mut self, center: &Point2D<f32>, radius: f32, start: f32, end: f32, ccw: bool) { + match self { + Canvas::Raqote(canvas_data) => canvas_data.arc(center, radius, start, end, ccw), + } + } + + fn arc_to(&mut self, cp1: &Point2D<f32>, cp2: &Point2D<f32>, radius: f32) { + match self { + Canvas::Raqote(canvas_data) => canvas_data.arc_to(cp1, cp2, radius), + } + } + + #[allow(clippy::too_many_arguments)] + fn ellipse( + &mut self, + center: &Point2D<f32>, + radius_x: f32, + radius_y: f32, + rotation: f32, + start: f32, + end: f32, + ccw: bool, + ) { + match self { + Canvas::Raqote(canvas_data) => { + canvas_data.ellipse(center, radius_x, radius_y, rotation, start, end, ccw) + }, + } + } + + fn restore_context_state(&mut self) { + match self { + Canvas::Raqote(canvas_data) => canvas_data.restore_context_state(), + } + } + + fn save_context_state(&mut self) { + match self { + Canvas::Raqote(canvas_data) => canvas_data.save_context_state(), + } + } + + fn set_line_width(&mut self, width: f32) { + match self { + Canvas::Raqote(canvas_data) => canvas_data.set_line_width(width), + } + } + + fn set_line_cap(&mut self, cap: LineCapStyle) { + match self { + Canvas::Raqote(canvas_data) => canvas_data.set_line_cap(cap), + } + } + + fn set_line_join(&mut self, join: LineJoinStyle) { + match self { + Canvas::Raqote(canvas_data) => canvas_data.set_line_join(join), + } + } + + fn set_miter_limit(&mut self, limit: f32) { + match self { + Canvas::Raqote(canvas_data) => canvas_data.set_miter_limit(limit), + } + } + + fn set_line_dash(&mut self, items: Vec<f32>) { + match self { + Canvas::Raqote(canvas_data) => canvas_data.set_line_dash(items), + } + } + + fn set_line_dash_offset(&mut self, offset: f32) { + match self { + Canvas::Raqote(canvas_data) => canvas_data.set_line_dash_offset(offset), + } + } + + fn set_transform(&mut self, matrix: &Transform2D<f32>) { + match self { + Canvas::Raqote(canvas_data) => canvas_data.set_transform(matrix), + } + } + + fn set_global_alpha(&mut self, alpha: f32) { + match self { + Canvas::Raqote(canvas_data) => canvas_data.set_global_alpha(alpha), + } + } + + fn set_global_composition(&mut self, op: CompositionOrBlending) { + match self { + Canvas::Raqote(canvas_data) => canvas_data.set_global_composition(op), + } + } + + fn set_shadow_offset_x(&mut self, value: f64) { + match self { + Canvas::Raqote(canvas_data) => canvas_data.set_shadow_offset_x(value), + } + } + + fn set_shadow_offset_y(&mut self, value: f64) { + match self { + Canvas::Raqote(canvas_data) => canvas_data.set_shadow_offset_y(value), + } + } + + fn set_shadow_blur(&mut self, value: f64) { + match self { + Canvas::Raqote(canvas_data) => canvas_data.set_shadow_blur(value), + } + } + + fn set_shadow_color(&mut self, color: AbsoluteColor) { + match self { + Canvas::Raqote(canvas_data) => canvas_data.set_shadow_color(color), + } + } + + fn set_font(&mut self, font_style: FontStyleStruct) { + match self { + Canvas::Raqote(canvas_data) => canvas_data.set_font(font_style), + } + } + + fn set_text_align(&mut self, text_align: TextAlign) { + match self { + Canvas::Raqote(canvas_data) => canvas_data.set_text_align(text_align), + } + } + + fn set_text_baseline(&mut self, text_baseline: TextBaseline) { + match self { + Canvas::Raqote(canvas_data) => canvas_data.set_text_baseline(text_baseline), + } + } + + fn measure_text(&mut self, text: String) -> TextMetrics { + match self { + Canvas::Raqote(canvas_data) => canvas_data.measure_text(text), + } + } + + fn clip_path(&mut self, path: &[PathSegment]) { + match self { + Canvas::Raqote(canvas_data) => canvas_data.clip_path(path), + } + } + + fn get_transform(&self) -> Transform2D<f32> { + match self { + Canvas::Raqote(canvas_data) => canvas_data.get_transform(), + } + } + + fn put_image_data(&mut self, unwrap: Vec<u8>, rect: Rect<u64>) { + match self { + Canvas::Raqote(canvas_data) => canvas_data.put_image_data(unwrap, rect), + } + } + + fn update_image_rendering(&mut self) { + match self { + Canvas::Raqote(canvas_data) => canvas_data.update_image_rendering(), + } + } + + fn recreate(&mut self, size: Option<Size2D<u64>>) { + match self { + Canvas::Raqote(canvas_data) => canvas_data.recreate(size), + } + } +} diff --git a/components/canvas/lib.rs b/components/canvas/lib.rs index 86c291fdc87..91ab58b0e8b 100644 --- a/components/canvas/lib.rs +++ b/components/canvas/lib.rs @@ -4,6 +4,7 @@ #![deny(unsafe_code)] +mod backend; mod raqote_backend; pub mod canvas_data; diff --git a/components/canvas/raqote_backend.rs b/components/canvas/raqote_backend.rs index 12137e41f41..efe0ffd05b8 100644 --- a/components/canvas/raqote_backend.rs +++ b/components/canvas/raqote_backend.rs @@ -7,20 +7,19 @@ use std::collections::HashMap; use canvas_traits::canvas::*; use cssparser::color::clamp_unit_f32; -use euclid::Angle; use euclid::default::{Point2D, Rect, Size2D, Transform2D, Vector2D}; use font_kit::font::Font; use fonts::{ByteIndex, FontIdentifier, FontTemplateRefMethods}; use log::warn; -use lyon_geom::Arc; use range::Range; use raqote::PathOp; use style::color::AbsoluteColor; -use crate::canvas_data::{ - self, Backend, CanvasPaintState, Color, CompositionOp, DrawOptions, Filter, GenericDrawTarget, - GenericPathBuilder, GradientStop, GradientStops, Path, SourceSurface, StrokeOptions, TextRun, +use crate::backend::{ + Backend, DrawOptionsHelpers, GenericDrawTarget, GenericPathBuilder, PathHelpers, + PatternHelpers, StrokeOptionsHelpers, }; +use crate::canvas_data::{CanvasPaintState, Filter, TextRun}; thread_local! { /// The shared font cache used by all canvases that render on a thread. It would be nicer @@ -30,80 +29,85 @@ thread_local! { static SHARED_FONT_CACHE: RefCell<HashMap<FontIdentifier, Font>> = RefCell::default(); } -#[derive(Default)] -pub struct RaqoteBackend; +#[derive(Clone, Default)] +pub(crate) struct RaqoteBackend; impl Backend for RaqoteBackend { - fn get_composition_op(&self, opts: &DrawOptions) -> CompositionOp { - CompositionOp::Raqote(opts.as_raqote().blend_mode) + type Pattern<'a> = Pattern<'a>; + type StrokeOptions = raqote::StrokeStyle; + type Color = raqote::SolidSource; + type DrawOptions = raqote::DrawOptions; + type CompositionOp = raqote::BlendMode; + type DrawTarget = raqote::DrawTarget; + type PathBuilder = PathBuilder; + type SourceSurface = Vec<u8>; // TODO: See if we can avoid the alloc (probably?) + type Bytes<'a> = &'a [u8]; + type Path = raqote::Path; + type GradientStop = raqote::GradientStop; + type GradientStops = Vec<raqote::GradientStop>; + + fn get_composition_op(&self, opts: &Self::DrawOptions) -> Self::CompositionOp { + opts.blend_mode } - fn need_to_draw_shadow(&self, color: &Color) -> bool { - color.as_raqote().a != 0 + fn need_to_draw_shadow(&self, color: &Self::Color) -> bool { + color.a != 0 } - fn set_shadow_color(&mut self, color: AbsoluteColor, state: &mut CanvasPaintState<'_>) { - state.shadow_color = Color::Raqote(color.to_raqote_style()); + fn set_shadow_color(&mut self, color: AbsoluteColor, state: &mut CanvasPaintState<'_, Self>) { + state.shadow_color = color.to_raqote_style(); } fn set_fill_style( &mut self, style: FillOrStrokeStyle, - state: &mut CanvasPaintState<'_>, - _drawtarget: &dyn GenericDrawTarget, + state: &mut CanvasPaintState<'_, Self>, + _drawtarget: &Self::DrawTarget, ) { if let Some(pattern) = style.to_raqote_pattern() { - state.fill_style = canvas_data::Pattern::Raqote(pattern); + state.fill_style = pattern; } } fn set_stroke_style( &mut self, style: FillOrStrokeStyle, - state: &mut CanvasPaintState<'_>, - _drawtarget: &dyn GenericDrawTarget, + state: &mut CanvasPaintState<'_, Self>, + _drawtarget: &Self::DrawTarget, ) { if let Some(pattern) = style.to_raqote_pattern() { - state.stroke_style = canvas_data::Pattern::Raqote(pattern); + state.stroke_style = pattern; } } fn set_global_composition( &mut self, op: CompositionOrBlending, - state: &mut CanvasPaintState<'_>, + state: &mut CanvasPaintState<'_, Self>, ) { - state.draw_options.as_raqote_mut().blend_mode = op.to_raqote_style(); - } - - fn create_drawtarget(&self, size: Size2D<u64>) -> Box<dyn GenericDrawTarget> { - Box::new(raqote::DrawTarget::new( - size.width as i32, - size.height as i32, - )) + state.draw_options.blend_mode = op.to_raqote_style(); } - fn recreate_paint_state<'a>(&self, _state: &CanvasPaintState<'a>) -> CanvasPaintState<'a> { - CanvasPaintState::default() + fn create_drawtarget(&self, size: Size2D<u64>) -> Self::DrawTarget { + raqote::DrawTarget::new(size.width as i32, size.height as i32) } -} -impl Default for CanvasPaintState<'_> { - fn default() -> Self { + fn new_paint_state<'a>(&self) -> CanvasPaintState<'a, Self> { let pattern = Pattern::Color(255, 0, 0, 0); CanvasPaintState { - draw_options: DrawOptions::Raqote(raqote::DrawOptions::new()), - fill_style: canvas_data::Pattern::Raqote(pattern.clone()), - stroke_style: canvas_data::Pattern::Raqote(pattern), - stroke_opts: StrokeOptions::Raqote(Default::default()), + draw_options: raqote::DrawOptions::new(), + fill_style: pattern.clone(), + stroke_style: pattern, + stroke_opts: Default::default(), transform: Transform2D::identity(), shadow_offset_x: 0.0, shadow_offset_y: 0.0, shadow_blur: 0.0, - shadow_color: Color::Raqote(raqote::SolidSource::from_unpremultiplied_argb(0, 0, 0, 0)), + shadow_color: raqote::SolidSource::from_unpremultiplied_argb(0, 0, 0, 0), font_style: None, text_align: TextAlign::default(), text_baseline: TextBaseline::default(), + _backend: std::marker::PhantomData, } } } @@ -230,136 +234,122 @@ impl Repetition { } } -impl canvas_data::Pattern<'_> { - pub fn source(&self) -> raqote::Source { +pub fn source<'a>(pattern: &Pattern<'a>) -> raqote::Source<'a> { + match pattern { + Pattern::Color(a, r, g, b) => raqote::Source::Solid( + raqote::SolidSource::from_unpremultiplied_argb(*a, *r, *g, *b), + ), + Pattern::LinearGradient(pattern) => raqote::Source::new_linear_gradient( + pattern.gradient.clone(), + pattern.start, + pattern.end, + raqote::Spread::Pad, + ), + Pattern::RadialGradient(pattern) => raqote::Source::new_two_circle_radial_gradient( + pattern.gradient.clone(), + pattern.center1, + pattern.radius1, + pattern.center2, + pattern.radius2, + raqote::Spread::Pad, + ), + Pattern::Surface(pattern) => raqote::Source::Image( + pattern.image, + pattern.extend, + pattern.filter, + pattern.transform, + ), + } +} + +impl PatternHelpers for Pattern<'_> { + fn is_zero_size_gradient(&self) -> bool { match self { - canvas_data::Pattern::Raqote(pattern) => match pattern { - Pattern::Color(a, r, g, b) => raqote::Source::Solid( - raqote::SolidSource::from_unpremultiplied_argb(*a, *r, *g, *b), - ), - Pattern::LinearGradient(pattern) => raqote::Source::new_linear_gradient( - pattern.gradient.clone(), - pattern.start, - pattern.end, - raqote::Spread::Pad, - ), - Pattern::RadialGradient(pattern) => raqote::Source::new_two_circle_radial_gradient( - pattern.gradient.clone(), - pattern.center1, - pattern.radius1, - pattern.center2, - pattern.radius2, - raqote::Spread::Pad, - ), - Pattern::Surface(pattern) => raqote::Source::Image( - pattern.image, - pattern.extend, - pattern.filter, - pattern.transform, - ), + Pattern::RadialGradient(pattern) => { + let centers_equal = pattern.center1 == pattern.center2; + let radii_equal = pattern.radius1 == pattern.radius2; + (centers_equal && radii_equal) || pattern.gradient.stops.is_empty() }, + Pattern::LinearGradient(pattern) => { + (pattern.start == pattern.end) || pattern.gradient.stops.is_empty() + }, + Pattern::Color(..) | Pattern::Surface(..) => false, } } - pub fn is_zero_size_gradient(&self) -> bool { + + fn draw_rect(&self, rect: &Rect<f32>) -> Rect<f32> { match self { - canvas_data::Pattern::Raqote(pattern) => match pattern { - Pattern::RadialGradient(pattern) => { - let centers_equal = pattern.center1 == pattern.center2; - let radii_equal = pattern.radius1 == pattern.radius2; - (centers_equal && radii_equal) || pattern.gradient.stops.is_empty() - }, - Pattern::LinearGradient(pattern) => { - (pattern.start == pattern.end) || pattern.gradient.stops.is_empty() - }, - Pattern::Color(..) | Pattern::Surface(..) => false, + Pattern::Surface(pattern) => { + let pattern_rect = Rect::new(Point2D::origin(), pattern.size()); + let mut draw_rect = rect.intersection(&pattern_rect).unwrap_or(Rect::zero()); + + match pattern.repetition() { + Repetition::NoRepeat => { + draw_rect.size.width = draw_rect.size.width.min(pattern_rect.size.width); + draw_rect.size.height = draw_rect.size.height.min(pattern_rect.size.height); + }, + Repetition::RepeatX => { + draw_rect.size.width = rect.size.width; + draw_rect.size.height = draw_rect.size.height.min(pattern_rect.size.height); + }, + Repetition::RepeatY => { + draw_rect.size.height = rect.size.height; + draw_rect.size.width = draw_rect.size.width.min(pattern_rect.size.width); + }, + Repetition::Repeat => { + draw_rect = *rect; + }, + } + + draw_rect }, + Pattern::Color(..) | Pattern::LinearGradient(..) | Pattern::RadialGradient(..) => *rect, } } } -impl StrokeOptions { - pub fn set_line_width(&mut self, _val: f32) { - match self { - StrokeOptions::Raqote(options) => options.width = _val, - } - } - pub fn set_miter_limit(&mut self, _val: f32) { - match self { - StrokeOptions::Raqote(options) => options.miter_limit = _val, - } +impl StrokeOptionsHelpers for raqote::StrokeStyle { + fn set_line_width(&mut self, _val: f32) { + self.width = _val; } - pub fn set_line_join(&mut self, val: LineJoinStyle) { - match self { - StrokeOptions::Raqote(options) => options.join = val.to_raqote_style(), - } + fn set_miter_limit(&mut self, _val: f32) { + self.miter_limit = _val; } - pub fn set_line_cap(&mut self, val: LineCapStyle) { - match self { - StrokeOptions::Raqote(options) => options.cap = val.to_raqote_style(), - } + fn set_line_join(&mut self, val: LineJoinStyle) { + self.join = val.to_raqote_style(); } - pub fn set_line_dash(&mut self, items: Vec<f32>) { - match self { - StrokeOptions::Raqote(options) => options.dash_array = items, - } + fn set_line_cap(&mut self, val: LineCapStyle) { + self.cap = val.to_raqote_style(); } - pub fn set_line_dash_offset(&mut self, offset: f32) { - match self { - StrokeOptions::Raqote(options) => options.dash_offset = offset, - } + fn set_line_dash(&mut self, items: Vec<f32>) { + self.dash_array = items; } - pub fn as_raqote(&self) -> &raqote::StrokeStyle { - match self { - StrokeOptions::Raqote(options) => options, - } + fn set_line_dash_offset(&mut self, offset: f32) { + self.dash_offset = offset; } } -impl DrawOptions { - pub fn set_alpha(&mut self, val: f32) { - match self { - DrawOptions::Raqote(draw_options) => draw_options.alpha = val, - } - } - pub fn as_raqote(&self) -> &raqote::DrawOptions { - match self { - DrawOptions::Raqote(options) => options, - } - } - fn as_raqote_mut(&mut self) -> &mut raqote::DrawOptions { - match self { - DrawOptions::Raqote(options) => options, - } +impl DrawOptionsHelpers for raqote::DrawOptions { + fn set_alpha(&mut self, val: f32) { + self.alpha = val; } } -impl Path { - pub fn transformed_copy_to_builder( - &self, - transform: &Transform2D<f32>, - ) -> Box<dyn GenericPathBuilder> { - Box::new(PathBuilder(Some(raqote::PathBuilder::from( - self.as_raqote().clone().transform(transform), - )))) +impl PathHelpers<RaqoteBackend> for raqote::Path { + fn transformed_copy_to_builder(&self, transform: &Transform2D<f32>) -> PathBuilder { + PathBuilder(Some(raqote::PathBuilder::from( + self.clone().transform(transform), + ))) } - pub fn contains_point(&self, x: f64, y: f64, path_transform: &Transform2D<f32>) -> bool { - self.as_raqote() - .clone() + fn contains_point(&self, x: f64, y: f64, path_transform: &Transform2D<f32>) -> bool { + self.clone() .transform(path_transform) .contains_point(0.1, x as f32, y as f32) } - pub fn copy_to_builder(&self) -> Box<dyn GenericPathBuilder> { - Box::new(PathBuilder(Some(raqote::PathBuilder::from( - self.as_raqote().clone(), - )))) - } - - pub fn as_raqote(&self) -> &raqote::Path { - match self { - Path::Raqote(p) => p, - } + fn copy_to_builder(&self) -> PathBuilder { + PathBuilder(Some(raqote::PathBuilder::from(self.clone()))) } } @@ -373,7 +363,7 @@ fn create_gradient_stops(gradient_stops: Vec<CanvasGradientStop>) -> Vec<raqote: stops } -impl GenericDrawTarget for raqote::DrawTarget { +impl GenericDrawTarget<RaqoteBackend> for raqote::DrawTarget { fn clear_rect(&mut self, rect: &Rect<f32>) { let mut pb = raqote::PathBuilder::new(); pb.rect( @@ -385,59 +375,47 @@ impl GenericDrawTarget for raqote::DrawTarget { let mut options = raqote::DrawOptions::new(); options.blend_mode = raqote::BlendMode::Clear; let pattern = Pattern::Color(0, 0, 0, 0); - GenericDrawTarget::fill( - self, - &Path::Raqote(pb.finish()), - canvas_data::Pattern::Raqote(pattern), - &DrawOptions::Raqote(options), - ); + <Self as GenericDrawTarget<RaqoteBackend>>::fill(self, &pb.finish(), pattern, &options); } #[allow(unsafe_code)] fn copy_surface( &mut self, - surface: SourceSurface, + surface: <RaqoteBackend as Backend>::SourceSurface, source: Rect<i32>, destination: Point2D<i32>, ) { let mut dt = raqote::DrawTarget::new(source.size.width, source.size.height); - let data = surface.as_raqote(); + let data = surface; let s = unsafe { std::slice::from_raw_parts(data.as_ptr() as *const u32, data.len() / 4) }; dt.get_data_mut().copy_from_slice(s); raqote::DrawTarget::copy_surface(self, &dt, source.to_box2d(), destination); } - // TODO(pylbrecht) - // Somehow a duplicate of `create_gradient_stops()` with different types. - // It feels cumbersome to convert GradientStop back and forth just to use - // `create_gradient_stops()`, so I'll leave this here for now. - fn create_gradient_stops(&self, gradient_stops: Vec<GradientStop>) -> GradientStops { - let mut stops = gradient_stops - .into_iter() - .map(|item| *item.as_raqote()) - .collect::<Vec<raqote::GradientStop>>(); - // https://www.w3.org/html/test/results/2dcontext/annotated-spec/canvas.html#testrefs.2d.gradient.interpolate.overlap - stops.sort_by(|a, b| a.position.partial_cmp(&b.position).unwrap()); - GradientStops::Raqote(stops) - } - fn create_path_builder(&self) -> Box<dyn GenericPathBuilder> { - Box::new(PathBuilder::new()) + fn create_path_builder(&self) -> <RaqoteBackend as Backend>::PathBuilder { + PathBuilder::new() } - fn create_similar_draw_target(&self, size: &Size2D<i32>) -> Box<dyn GenericDrawTarget> { - Box::new(raqote::DrawTarget::new(size.width, size.height)) + fn create_similar_draw_target( + &self, + size: &Size2D<i32>, + ) -> <RaqoteBackend as Backend>::DrawTarget { + raqote::DrawTarget::new(size.width, size.height) } - fn create_source_surface_from_data(&self, data: &[u8]) -> Option<SourceSurface> { - Some(SourceSurface::Raqote(data.to_vec())) + fn create_source_surface_from_data( + &self, + data: &[u8], + ) -> Option<<RaqoteBackend as Backend>::SourceSurface> { + Some(data.to_vec()) } #[allow(unsafe_code)] fn draw_surface( &mut self, - surface: SourceSurface, + surface: <RaqoteBackend as Backend>::SourceSurface, dest: Rect<f64>, source: Rect<f64>, filter: Filter, - draw_options: &DrawOptions, + draw_options: &<RaqoteBackend as Backend>::DrawOptions, ) { - let surface_data = surface.as_raqote(); + let surface_data = surface; let image = raqote::Image { width: source.size.width as i32, height: source.size.height as i32, @@ -470,33 +448,29 @@ impl GenericDrawTarget for raqote::DrawTarget { dest.size.height as f32, ); - GenericDrawTarget::fill( - self, - &Path::Raqote(pb.finish()), - canvas_data::Pattern::Raqote(pattern), - draw_options, - ); + <Self as GenericDrawTarget<RaqoteBackend>>::fill(self, &pb.finish(), pattern, draw_options); } fn draw_surface_with_shadow( &self, - _surface: SourceSurface, + _surface: <RaqoteBackend as Backend>::SourceSurface, _dest: &Point2D<f32>, - _color: &Color, + _color: &<RaqoteBackend as Backend>::Color, _offset: &Vector2D<f32>, _sigma: f32, - _operator: CompositionOp, + _operator: <RaqoteBackend as Backend>::CompositionOp, ) { warn!("no support for drawing shadows"); } - fn fill(&mut self, path: &Path, pattern: canvas_data::Pattern, draw_options: &DrawOptions) { - match draw_options.as_raqote().blend_mode { + fn fill( + &mut self, + path: &<RaqoteBackend as Backend>::Path, + pattern: <RaqoteBackend as Backend>::Pattern<'_>, + draw_options: &<RaqoteBackend as Backend>::DrawOptions, + ) { + match draw_options.blend_mode { raqote::BlendMode::Src => { self.clear(raqote::SolidSource::from_unpremultiplied_argb(0, 0, 0, 0)); - self.fill( - path.as_raqote(), - &pattern.source(), - draw_options.as_raqote(), - ); + self.fill(path, &source(&pattern), draw_options); }, raqote::BlendMode::Clear | raqote::BlendMode::SrcAtop | @@ -505,26 +479,19 @@ impl GenericDrawTarget for raqote::DrawTarget { raqote::BlendMode::Xor | raqote::BlendMode::DstOver | raqote::BlendMode::SrcOver => { - self.fill( - path.as_raqote(), - &pattern.source(), - draw_options.as_raqote(), - ); + self.fill(path, &source(&pattern), draw_options); }, raqote::BlendMode::SrcIn | raqote::BlendMode::SrcOut | raqote::BlendMode::DstIn | raqote::BlendMode::DstAtop => { - let mut options = *draw_options.as_raqote(); + let mut options = *draw_options; self.push_layer_with_blend(1., options.blend_mode); options.blend_mode = raqote::BlendMode::SrcOver; - self.fill(path.as_raqote(), &pattern.source(), &options); + self.fill(path, &source(&pattern), &options); self.pop_layer(); }, - _ => warn!( - "unrecognized blend mode: {:?}", - draw_options.as_raqote().blend_mode - ), + _ => warn!("unrecognized blend mode: {:?}", draw_options.blend_mode), } } @@ -532,8 +499,8 @@ impl GenericDrawTarget for raqote::DrawTarget { &mut self, text_runs: Vec<TextRun>, start: Point2D<f32>, - pattern: &canvas_data::Pattern, - draw_options: &DrawOptions, + pattern: &<RaqoteBackend as Backend>::Pattern<'_>, + draw_options: &<RaqoteBackend as Backend>::DrawOptions, ) { let mut advance = 0.; for run in text_runs.iter() { @@ -581,8 +548,8 @@ impl GenericDrawTarget for raqote::DrawTarget { run.font.descriptor.pt_size.to_f32_px(), &ids, &positions, - &pattern.source(), - draw_options.as_raqote(), + &source(pattern), + draw_options, ); }) } @@ -591,8 +558,8 @@ impl GenericDrawTarget for raqote::DrawTarget { fn fill_rect( &mut self, rect: &Rect<f32>, - pattern: canvas_data::Pattern, - draw_options: Option<&DrawOptions>, + pattern: <RaqoteBackend as Backend>::Pattern<'_>, + draw_options: Option<&<RaqoteBackend as Backend>::DrawOptions>, ) { let mut pb = raqote::PathBuilder::new(); pb.rect( @@ -602,16 +569,16 @@ impl GenericDrawTarget for raqote::DrawTarget { rect.size.height, ); let draw_options = if let Some(options) = draw_options { - *options.as_raqote() + *options } else { raqote::DrawOptions::new() }; - GenericDrawTarget::fill( + <Self as GenericDrawTarget<RaqoteBackend>>::fill( self, - &Path::Raqote(pb.finish()), + &pb.finish(), pattern, - &DrawOptions::Raqote(draw_options), + &draw_options, ); } fn get_size(&self) -> Size2D<i32> { @@ -623,41 +590,36 @@ impl GenericDrawTarget for raqote::DrawTarget { fn pop_clip(&mut self) { self.pop_clip(); } - fn push_clip(&mut self, path: &Path) { - self.push_clip(path.as_raqote()); + fn push_clip(&mut self, path: &<RaqoteBackend as Backend>::Path) { + self.push_clip(path); } fn set_transform(&mut self, matrix: &Transform2D<f32>) { self.set_transform(matrix); } - fn snapshot(&self) -> SourceSurface { - SourceSurface::Raqote(self.snapshot_data().to_vec()) + fn surface(&self) -> <RaqoteBackend as Backend>::SourceSurface { + self.bytes().to_vec() } fn stroke( &mut self, - path: &Path, - pattern: canvas_data::Pattern, - stroke_options: &StrokeOptions, - draw_options: &DrawOptions, + path: &<RaqoteBackend as Backend>::Path, + pattern: Pattern<'_>, + stroke_options: &<RaqoteBackend as Backend>::StrokeOptions, + draw_options: &<RaqoteBackend as Backend>::DrawOptions, ) { - self.stroke( - path.as_raqote(), - &pattern.source(), - stroke_options.as_raqote(), - draw_options.as_raqote(), - ); + self.stroke(path, &source(&pattern), stroke_options, draw_options); } fn stroke_line( &mut self, start: Point2D<f32>, end: Point2D<f32>, - pattern: canvas_data::Pattern, - stroke_options: &StrokeOptions, - draw_options: &DrawOptions, + pattern: <RaqoteBackend as Backend>::Pattern<'_>, + stroke_options: &<RaqoteBackend as Backend>::StrokeOptions, + draw_options: &<RaqoteBackend as Backend>::DrawOptions, ) { let mut pb = raqote::PathBuilder::new(); pb.move_to(start.x, start.y); pb.line_to(end.x, end.y); - let mut stroke_options = stroke_options.as_raqote().clone(); + let mut stroke_options = stroke_options.clone(); let cap = match stroke_options.join { raqote::LineJoin::Round => raqote::LineCap::Round, _ => raqote::LineCap::Butt, @@ -666,17 +628,17 @@ impl GenericDrawTarget for raqote::DrawTarget { self.stroke( &pb.finish(), - &pattern.source(), + &source(&pattern), &stroke_options, - draw_options.as_raqote(), + draw_options, ); } fn stroke_rect( &mut self, rect: &Rect<f32>, - pattern: canvas_data::Pattern, - stroke_options: &StrokeOptions, - draw_options: &DrawOptions, + pattern: <RaqoteBackend as Backend>::Pattern<'_>, + stroke_options: &<RaqoteBackend as Backend>::StrokeOptions, + draw_options: &<RaqoteBackend as Backend>::DrawOptions, ) { let mut pb = raqote::PathBuilder::new(); pb.rect( @@ -688,13 +650,13 @@ impl GenericDrawTarget for raqote::DrawTarget { self.stroke( &pb.finish(), - &pattern.source(), - stroke_options.as_raqote(), - draw_options.as_raqote(), + &source(&pattern), + stroke_options, + draw_options, ); } #[allow(unsafe_code)] - fn snapshot_data(&self) -> &[u8] { + fn bytes(&self) -> &[u8] { let v = self.get_data(); unsafe { std::slice::from_raw_parts(v.as_ptr() as *const u8, std::mem::size_of_val(v)) } } @@ -709,7 +671,7 @@ impl Filter { } } -struct PathBuilder(Option<raqote::PathBuilder>); +pub(crate) struct PathBuilder(Option<raqote::PathBuilder>); impl PathBuilder { fn new() -> PathBuilder { @@ -717,7 +679,7 @@ impl PathBuilder { } } -impl GenericPathBuilder for PathBuilder { +impl GenericPathBuilder<RaqoteBackend> for PathBuilder { fn arc( &mut self, origin: Point2D<f32>, @@ -726,7 +688,8 @@ impl GenericPathBuilder for PathBuilder { end_angle: f32, anticlockwise: bool, ) { - self.ellipse( + <PathBuilder as GenericPathBuilder<RaqoteBackend>>::ellipse( + self, origin, radius, radius, @@ -736,6 +699,7 @@ impl GenericPathBuilder for PathBuilder { anticlockwise, ); } + fn bezier_curve_to( &mut self, control_point1: &Point2D<f32>, @@ -751,66 +715,10 @@ impl GenericPathBuilder for PathBuilder { control_point3.y, ); } + fn close(&mut self) { self.0.as_mut().unwrap().close(); } - fn ellipse( - &mut self, - origin: Point2D<f32>, - radius_x: f32, - radius_y: f32, - rotation_angle: f32, - start_angle: f32, - end_angle: f32, - anticlockwise: bool, - ) { - let mut start = Angle::radians(start_angle); - let mut end = Angle::radians(end_angle); - - // Wrap angles mod 2 * PI if necessary - if !anticlockwise && start > end + Angle::two_pi() || - anticlockwise && end > start + Angle::two_pi() - { - start = start.positive(); - end = end.positive(); - } - - // Calculate the total arc we're going to sweep. - let sweep = match anticlockwise { - true => { - if end - start == Angle::two_pi() { - -Angle::two_pi() - } else if end > start { - -(Angle::two_pi() - (end - start)) - } else { - -(start - end) - } - }, - false => { - if start - end == Angle::two_pi() { - Angle::two_pi() - } else if start > end { - Angle::two_pi() - (start - end) - } else { - end - start - } - }, - }; - - let arc: Arc<f32> = Arc { - center: origin, - radii: Vector2D::new(radius_x, radius_y), - start_angle: start, - sweep_angle: sweep, - x_rotation: Angle::radians(rotation_angle), - }; - - self.line_to(arc.from()); - - arc.for_each_quadratic_bezier(&mut |q| { - self.quadratic_curve_to(&q.ctrl, &q.to); - }); - } fn svg_arc( &mut self, @@ -840,9 +748,9 @@ impl GenericPathBuilder for PathBuilder { fn get_current_point(&mut self) -> Option<Point2D<f32>> { let path = self.finish(); - self.0 = Some(path.as_raqote().clone().into()); + self.0 = Some(path.clone().into()); - path.as_raqote().ops.iter().last().and_then(|op| match op { + path.ops.iter().last().and_then(|op| match op { PathOp::MoveTo(point) | PathOp::LineTo(point) => Some(Point2D::new(point.x, point.y)), PathOp::CubicTo(_, _, point) => Some(Point2D::new(point.x, point.y)), PathOp::QuadTo(_, point) => Some(Point2D::new(point.x, point.y)), @@ -864,8 +772,8 @@ impl GenericPathBuilder for PathBuilder { end_point.y, ); } - fn finish(&mut self) -> Path { - Path::Raqote(self.0.take().unwrap().finish()) + fn finish(&mut self) -> raqote::Path { + self.0.take().unwrap().finish() } } @@ -977,14 +885,6 @@ impl ToRaqotePattern<'_> for FillOrStrokeStyle { } } -impl Color { - fn as_raqote(&self) -> &raqote::SolidSource { - match self { - Color::Raqote(s) => s, - } - } -} - impl ToRaqoteStyle for AbsoluteColor { type Target = raqote::SolidSource; @@ -1054,19 +954,3 @@ impl ToRaqoteStyle for CompositionStyle { } } } - -impl SourceSurface { - fn as_raqote(&self) -> &Vec<u8> { - match self { - SourceSurface::Raqote(s) => s, - } - } -} - -impl GradientStop { - fn as_raqote(&self) -> &raqote::GradientStop { - match self { - GradientStop::Raqote(s) => s, - } - } -} diff --git a/components/constellation/constellation.rs b/components/constellation/constellation.rs index ad89c435717..f3a15d7708d 100644 --- a/components/constellation/constellation.rs +++ b/components/constellation/constellation.rs @@ -371,6 +371,7 @@ pub struct Constellation<STF, SWF> { mem_profiler_chan: mem::ProfilerChan, /// A single WebRender document the constellation operates on. + #[cfg(feature = "webgpu")] webrender_document: DocumentId, /// Webrender related objects required by WebGPU threads @@ -715,6 +716,7 @@ where phantom: PhantomData, webdriver: WebDriverData::new(), document_states: HashMap::new(), + #[cfg(feature = "webgpu")] webrender_document: state.webrender_document, #[cfg(feature = "webgpu")] webrender_wgpu, diff --git a/components/devtools/actors/inspector/node.rs b/components/devtools/actors/inspector/node.rs index 10ff9801844..a731f15b2d8 100644 --- a/components/devtools/actors/inspector/node.rs +++ b/components/devtools/actors/inspector/node.rs @@ -78,6 +78,18 @@ pub struct NodeActorMsg { shadow_root_mode: Option<String>, traits: HashMap<String, ()>, attrs: Vec<AttrMsg>, + + /// The `DOCTYPE` name if this is a `DocumentType` node, `None` otherwise + #[serde(skip_serializing_if = "Option::is_none")] + name: Option<String>, + + /// The `DOCTYPE` public identifier if this is a `DocumentType` node, `None` otherwise + #[serde(skip_serializing_if = "Option::is_none")] + public_id: Option<String>, + + /// The `DOCTYPE` system identifier if this is a `DocumentType` node, `None` otherwise + #[serde(skip_serializing_if = "Option::is_none")] + system_id: Option<String>, } pub struct NodeActor { @@ -276,6 +288,9 @@ impl NodeInfoToProtocol for NodeInfo { value: attr.value, }) .collect(), + name: self.doctype_name, + public_id: self.doctype_public_identifier, + system_id: self.doctype_system_identifier, } } } diff --git a/components/layout/dom.rs b/components/layout/dom.rs index add4b3ac2d5..8f2697e670a 100644 --- a/components/layout/dom.rs +++ b/components/layout/dom.rs @@ -15,8 +15,7 @@ use script_layout_interface::wrapper_traits::{ LayoutDataTrait, LayoutNode, ThreadSafeLayoutElement, ThreadSafeLayoutNode, }; use script_layout_interface::{ - GenericLayoutDataTrait, HTMLCanvasDataSource, LayoutElementType, - LayoutNodeType as ScriptLayoutNodeType, + GenericLayoutDataTrait, LayoutElementType, LayoutNodeType as ScriptLayoutNodeType, }; use servo_arc::Arc as ServoArc; use style::properties::ComputedValues; @@ -29,7 +28,7 @@ use crate::flow::BlockLevelBox; use crate::flow::inline::InlineItem; use crate::fragment_tree::Fragment; use crate::geom::PhysicalSize; -use crate::replaced::{CanvasInfo, CanvasSource}; +use crate::replaced::CanvasInfo; use crate::table::TableLevelBox; use crate::taffy::TaffyItemBox; @@ -220,12 +219,7 @@ where fn as_canvas(self) -> Option<(CanvasInfo, PhysicalSize<f64>)> { let node = self.to_threadsafe(); let canvas_data = node.canvas_data()?; - let source = match canvas_data.source { - HTMLCanvasDataSource::WebGL(texture_id) => CanvasSource::WebGL(texture_id), - HTMLCanvasDataSource::Image(image_key) => CanvasSource::Image(image_key), - HTMLCanvasDataSource::WebGPU(image_key) => CanvasSource::WebGPU(image_key), - HTMLCanvasDataSource::Empty => CanvasSource::Empty, - }; + let source = canvas_data.source; Some(( CanvasInfo { source }, PhysicalSize::new(canvas_data.width.into(), canvas_data.height.into()), diff --git a/components/layout/flexbox/layout.rs b/components/layout/flexbox/layout.rs index a5540123681..e69b792e272 100644 --- a/components/layout/flexbox/layout.rs +++ b/components/layout/flexbox/layout.rs @@ -49,7 +49,6 @@ use crate::{ struct FlexContext<'a> { config: FlexContainerConfig, layout_context: &'a LayoutContext<'a>, - positioning_context: &'a mut PositioningContext, containing_block: &'a ContainingBlock<'a>, // For items container_inner_size_constraint: FlexRelativeVec2<SizeConstraint>, } @@ -657,7 +656,6 @@ impl FlexContainer { let mut flex_context = FlexContext { config: self.config.clone(), layout_context, - positioning_context, containing_block, // https://drafts.csswg.org/css-flexbox/#definite-sizes container_inner_size_constraint: self.config.flex_axis.vec2_to_flex_relative( @@ -1775,16 +1773,7 @@ impl FlexItem<'_> { ) -> Option<FlexItemLayoutResult> { let containing_block = flex_context.containing_block; let independent_formatting_context = &self.box_.independent_formatting_context; - let mut positioning_context = independent_formatting_context - .new_positioning_context() - .unwrap_or_else(|| { - PositioningContext::new_for_subtree( - flex_context - .positioning_context - .collects_for_nearest_positioned_ancestor(), - ) - }); - + let mut positioning_context = PositioningContext::default(); let item_writing_mode = independent_formatting_context.style().writing_mode; let item_is_horizontal = item_writing_mode.is_horizontal(); let flex_axis = flex_context.config.flex_axis; @@ -2617,17 +2606,7 @@ impl FlexItemBox { cross_size_stretches_to_container_size: bool, intrinsic_sizing_mode: IntrinsicSizingMode, ) -> Au { - let mut positioning_context = self - .independent_formatting_context - .new_positioning_context() - .unwrap_or_else(|| { - PositioningContext::new_for_subtree( - flex_context - .positioning_context - .collects_for_nearest_positioned_ancestor(), - ) - }); - + let mut positioning_context = PositioningContext::default(); let style = self.independent_formatting_context.style(); match &self.independent_formatting_context.contents { IndependentFormattingContextContents::Replaced(replaced) => { diff --git a/components/layout/flow/inline/line.rs b/components/layout/flow/inline/line.rs index e65eaed2367..80bab1080ed 100644 --- a/components/layout/flow/inline/line.rs +++ b/components/layout/flow/inline/line.rs @@ -331,7 +331,7 @@ impl LineItemLayout<'_, '_> { self.calculate_inline_box_block_start(inline_box_state, space_above_baseline); let positioning_context_or_start_offset_in_parent = - match inline_box.base.new_positioning_context() { + match PositioningContext::new_for_layout_box_base(&inline_box.base) { Some(positioning_context) => Either::Left(positioning_context), None => Either::Right(self.current_positioning_context_mut().len()), }; diff --git a/components/layout/flow/inline/mod.rs b/components/layout/flow/inline/mod.rs index 25fbaa324b1..2023f4e7174 100644 --- a/components/layout/flow/inline/mod.rs +++ b/components/layout/flow/inline/mod.rs @@ -2004,9 +2004,7 @@ impl IndependentFormattingContext { bidi_level: Level, ) { // We need to know the inline size of the atomic before deciding whether to do the line break. - let mut child_positioning_context = self - .new_positioning_context() - .unwrap_or_else(|| PositioningContext::new_for_subtree(true)); + let mut child_positioning_context = PositioningContext::default(); let IndependentFloatOrAtomicLayoutResult { mut fragment, baselines, diff --git a/components/layout/flow/mod.rs b/components/layout/flow/mod.rs index 983282dc389..772b150ae1c 100644 --- a/components/layout/flow/mod.rs +++ b/components/layout/flow/mod.rs @@ -689,16 +689,13 @@ fn layout_block_level_children_in_parallel( placement_state: &mut PlacementState, ignore_block_margins_for_stretch: LogicalSides1D<bool>, ) -> Vec<Fragment> { - let collects_for_nearest_positioned_ancestor = - positioning_context.collects_for_nearest_positioned_ancestor(); let mut layout_results: Vec<(Fragment, PositioningContext)> = Vec::with_capacity(child_boxes.len()); child_boxes .par_iter() .map(|child_box| { - let mut child_positioning_context = - PositioningContext::new_for_subtree(collects_for_nearest_positioned_ancestor); + let mut child_positioning_context = PositioningContext::default(); let fragment = child_box.borrow().layout( layout_context, &mut child_positioning_context, diff --git a/components/layout/flow/root.rs b/components/layout/flow/root.rs index c6498eeed63..bb9ff1d337a 100644 --- a/components/layout/flow/root.rs +++ b/components/layout/flow/root.rs @@ -385,8 +385,7 @@ impl BoxTree { style, }; - let mut positioning_context = - PositioningContext::new_for_containing_block_for_all_descendants(); + let mut positioning_context = PositioningContext::default(); let independent_layout = self.root.layout( layout_context, &mut positioning_context, diff --git a/components/layout/formatting_contexts.rs b/components/layout/formatting_contexts.rs index 4661c44592c..4982d0dae1a 100644 --- a/components/layout/formatting_contexts.rs +++ b/components/layout/formatting_contexts.rs @@ -295,10 +295,7 @@ impl IndependentNonReplacedContents { ); } - let mut child_positioning_context = PositioningContext::new_for_subtree( - positioning_context.collects_for_nearest_positioned_ancestor(), - ); - + let mut child_positioning_context = PositioningContext::default(); let result = self.layout_without_caching( layout_context, &mut child_positioning_context, diff --git a/components/layout/positioned.rs b/components/layout/positioned.rs index 6bfe2af87ef..ff361396fa3 100644 --- a/components/layout/positioned.rs +++ b/components/layout/positioned.rs @@ -43,16 +43,6 @@ pub(crate) struct AbsolutelyPositionedBox { } #[derive(Clone, MallocSizeOf)] -pub(crate) struct PositioningContext { - for_nearest_positioned_ancestor: Option<Vec<HoistedAbsolutelyPositionedBox>>, - - // For nearest `containing block for all descendants` as defined by the CSS transforms - // spec. - // https://www.w3.org/TR/css-transforms-1/#containing-block-for-all-descendants - for_nearest_containing_block_for_all_descendants: Vec<HoistedAbsolutelyPositionedBox>, -} - -#[derive(Clone, MallocSizeOf)] pub(crate) struct HoistedAbsolutelyPositionedBox { absolutely_positioned_box: ArcRefCell<AbsolutelyPositionedBox>, @@ -104,55 +94,26 @@ impl AbsolutelyPositionedBox { } } -impl IndependentFormattingContext { - #[inline] - pub(crate) fn new_positioning_context(&self) -> Option<PositioningContext> { - self.base.new_positioning_context() - } -} - -impl LayoutBoxBase { - #[inline] - pub(crate) fn new_positioning_context(&self) -> Option<PositioningContext> { - PositioningContext::new_for_style(&self.style, &self.base_fragment_info.flags) - } +#[derive(Clone, Default, MallocSizeOf)] +pub(crate) struct PositioningContext { + absolutes: Vec<HoistedAbsolutelyPositionedBox>, } impl PositioningContext { - pub(crate) fn new_for_containing_block_for_all_descendants() -> Self { - Self { - for_nearest_positioned_ancestor: None, - for_nearest_containing_block_for_all_descendants: Vec::new(), - } - } - - /// Create a [PositioningContext] to use for laying out a subtree. The idea is that - /// when subtree layout is finished, the newly hoisted boxes can be processed - /// (normally adjusting their static insets) and then appended to the parent - /// [PositioningContext]. - pub(crate) fn new_for_subtree(collects_for_nearest_positioned_ancestor: bool) -> Self { - Self { - for_nearest_positioned_ancestor: if collects_for_nearest_positioned_ancestor { - Some(Vec::new()) - } else { - None - }, - for_nearest_containing_block_for_all_descendants: Vec::new(), - } - } - - pub(crate) fn collects_for_nearest_positioned_ancestor(&self) -> bool { - self.for_nearest_positioned_ancestor.is_some() + #[inline] + pub(crate) fn new_for_layout_box_base(layout_box_base: &LayoutBoxBase) -> Option<Self> { + Self::new_for_style_and_fragment_flags( + &layout_box_base.style, + &layout_box_base.base_fragment_info.flags, + ) } - fn new_for_style(style: &ComputedValues, flags: &FragmentFlags) -> Option<Self> { - if style.establishes_containing_block_for_all_descendants(*flags) { - Some(Self::new_for_containing_block_for_all_descendants()) - } else if style.establishes_containing_block_for_absolute_descendants(*flags) { - Some(Self { - for_nearest_positioned_ancestor: Some(Vec::new()), - for_nearest_containing_block_for_all_descendants: Vec::new(), - }) + fn new_for_style_and_fragment_flags( + style: &ComputedValues, + flags: &FragmentFlags, + ) -> Option<Self> { + if style.establishes_containing_block_for_absolute_descendants(*flags) { + Some(Self::default()) } else { None } @@ -195,20 +156,9 @@ impl PositioningContext { offset: &PhysicalVec<Au>, index: PositioningContextLength, ) { - if let Some(hoisted_boxes) = self.for_nearest_positioned_ancestor.as_mut() { - hoisted_boxes - .iter_mut() - .skip(index.for_nearest_positioned_ancestor) - .for_each(|hoisted_fragment| { - hoisted_fragment - .fragment - .borrow_mut() - .adjust_offsets(offset) - }) - } - self.for_nearest_containing_block_for_all_descendants + self.absolutes .iter_mut() - .skip(index.for_nearest_containing_block_for_all_descendants) + .skip(index.0) .for_each(|hoisted_fragment| { hoisted_fragment .fragment @@ -227,19 +177,23 @@ impl PositioningContext { base: &LayoutBoxBase, fragment_layout_fn: impl FnOnce(&mut Self) -> BoxFragment, ) -> BoxFragment { - // Try to create a context, but if one isn't necessary, simply create the fragment - // using the given closure and the current `PositioningContext`. - let mut new_context = match base.new_positioning_context() { - Some(new_context) => new_context, - None => return fragment_layout_fn(self), - }; + // If a new `PositioningContext` isn't necessary, simply create the fragment using + // the given closure and the current `PositioningContext`. + let establishes_containing_block_for_absolutes = base + .style + .establishes_containing_block_for_absolute_descendants(base.base_fragment_info.flags); + if !establishes_containing_block_for_absolutes { + return fragment_layout_fn(self); + } + let mut new_context = PositioningContext::default(); let mut new_fragment = fragment_layout_fn(&mut new_context); - new_context.layout_collected_children(layout_context, &mut new_fragment); - // If the new context has any hoisted boxes for the nearest containing block for - // pass them up the tree. + // Lay out all of the absolutely positioned children for this fragment, and, if it + // isn't a containing block for fixed elements, then pass those up to the parent. + new_context.layout_collected_children(layout_context, &mut new_fragment); self.append(new_context); + if base.style.clone_position() == Position::Relative { new_fragment.content_rect.origin += relative_adjustement(&base.style, containing_block) .to_physical_vector(containing_block.style.writing_mode) @@ -248,13 +202,61 @@ impl PositioningContext { new_fragment } + fn take_boxes_for_fragment( + &mut self, + new_fragment: &BoxFragment, + boxes_to_layout_out: &mut Vec<HoistedAbsolutelyPositionedBox>, + boxes_to_continue_hoisting_out: &mut Vec<HoistedAbsolutelyPositionedBox>, + ) { + debug_assert!( + new_fragment + .style + .establishes_containing_block_for_absolute_descendants(new_fragment.base.flags) + ); + + if new_fragment + .style + .establishes_containing_block_for_all_descendants(new_fragment.base.flags) + { + boxes_to_layout_out.append(&mut self.absolutes); + return; + } + + // TODO: This could potentially use `extract_if` when that is stabilized. + let (mut boxes_to_layout, mut boxes_to_continue_hoisting) = self + .absolutes + .drain(..) + .partition(|hoisted_box| hoisted_box.position() != Position::Fixed); + boxes_to_layout_out.append(&mut boxes_to_layout); + boxes_to_continue_hoisting_out.append(&mut boxes_to_continue_hoisting); + } + // Lay out the hoisted boxes collected into this `PositioningContext` and add them // to the given `BoxFragment`. - pub fn layout_collected_children( + pub(crate) fn layout_collected_children( &mut self, layout_context: &LayoutContext, new_fragment: &mut BoxFragment, ) { + if self.absolutes.is_empty() { + return; + } + + // Sometimes we create temporary PositioningContexts just to collect hoisted absolutes and + // then these are processed later. In that case and if this fragment doesn't establish a + // containing block for absolutes at all, we just do nothing. All hoisted fragments will + // later be passed up to a parent PositioningContext. + // + // Handling this case here, when the PositioningContext is completely ineffectual other than + // as a temporary container for hoisted boxes, means that callers can execute less conditional + // code. + if !new_fragment + .style + .establishes_containing_block_for_absolute_descendants(new_fragment.base.flags) + { + return; + } + let padding_rect = PhysicalRect::new( // Ignore the content rect’s position in its own containing block: PhysicalPoint::origin(), @@ -268,83 +270,58 @@ impl PositioningContext { style: &new_fragment.style, }; - let take_hoisted_boxes_pending_layout = - |context: &mut Self| match context.for_nearest_positioned_ancestor.as_mut() { - Some(fragments) => mem::take(fragments), - None => mem::take(&mut context.for_nearest_containing_block_for_all_descendants), - }; + let mut fixed_position_boxes_to_hoist = Vec::new(); + let mut boxes_to_layout = Vec::new(); + self.take_boxes_for_fragment( + new_fragment, + &mut boxes_to_layout, + &mut fixed_position_boxes_to_hoist, + ); - // Loop because it’s possible that we discover (the static position of) - // more absolutely-positioned boxes while doing layout for others. - let mut hoisted_boxes = take_hoisted_boxes_pending_layout(self); - let mut laid_out_child_fragments = Vec::new(); - while !hoisted_boxes.is_empty() { + // Laying out a `position: absolute` child (which only establishes a containing block for + // `position: absolute` descendants) can result in more `position: fixed` descendants + // collecting in `self.absolutes`. We need to loop here in order to keep either laying them + // out or putting them into `fixed_position_boxes_to_hoist`. We know there aren't any more + // when `self.absolutes` is empty. + while !boxes_to_layout.is_empty() { HoistedAbsolutelyPositionedBox::layout_many( layout_context, - &mut hoisted_boxes, - &mut laid_out_child_fragments, - &mut self.for_nearest_containing_block_for_all_descendants, + std::mem::take(&mut boxes_to_layout), + &mut new_fragment.children, + &mut self.absolutes, &containing_block, new_fragment.padding, ); - hoisted_boxes = take_hoisted_boxes_pending_layout(self); - } - new_fragment.children.extend(laid_out_child_fragments); - } - - pub(crate) fn push(&mut self, box_: HoistedAbsolutelyPositionedBox) { - if let Some(nearest) = &mut self.for_nearest_positioned_ancestor { - let position = box_ - .absolutely_positioned_box - .borrow() - .context - .style() - .clone_position(); - match position { - Position::Fixed => {}, // fall through - Position::Absolute => return nearest.push(box_), - Position::Static | Position::Relative | Position::Sticky => unreachable!(), - } + self.take_boxes_for_fragment( + new_fragment, + &mut boxes_to_layout, + &mut fixed_position_boxes_to_hoist, + ); } - self.for_nearest_containing_block_for_all_descendants - .push(box_) + + // We replace here instead of simply preserving these in `take_boxes_for_fragment` + // so that we don't have to continually re-iterate over them when laying out in the + // loop above. + self.absolutes = fixed_position_boxes_to_hoist; } - pub(crate) fn is_empty(&self) -> bool { - self.for_nearest_containing_block_for_all_descendants - .is_empty() && - self.for_nearest_positioned_ancestor - .as_ref() - .is_none_or(|vector| vector.is_empty()) + pub(crate) fn push(&mut self, hoisted_box: HoistedAbsolutelyPositionedBox) { + debug_assert!(matches!( + hoisted_box.position(), + Position::Absolute | Position::Fixed + )); + self.absolutes.push(hoisted_box); } - pub(crate) fn append(&mut self, other: Self) { - if other.is_empty() { + pub(crate) fn append(&mut self, mut other: Self) { + if other.absolutes.is_empty() { return; } - - vec_append_owned( - &mut self.for_nearest_containing_block_for_all_descendants, - other.for_nearest_containing_block_for_all_descendants, - ); - - match ( - self.for_nearest_positioned_ancestor.as_mut(), - other.for_nearest_positioned_ancestor, - ) { - (Some(us), Some(them)) => vec_append_owned(us, them), - (None, Some(them)) => { - // This is the case where we have laid out the absolute children in a containing - // block for absolutes and we then are passing up the fixed-position descendants - // to the containing block for all descendants. - vec_append_owned( - &mut self.for_nearest_containing_block_for_all_descendants, - them, - ); - }, - (None, None) => {}, - _ => unreachable!(), + if self.absolutes.is_empty() { + self.absolutes = other.absolutes; + } else { + self.absolutes.append(&mut other.absolutes) } } @@ -354,19 +331,16 @@ impl PositioningContext { initial_containing_block: &DefiniteContainingBlock, fragments: &mut Vec<Fragment>, ) { - debug_assert!(self.for_nearest_positioned_ancestor.is_none()); - - // Loop because it’s possible that we discover (the static position of) - // more absolutely-positioned boxes while doing layout for others. - while !self - .for_nearest_containing_block_for_all_descendants - .is_empty() - { + // Laying out a `position: absolute` child (which only establishes a containing block for + // `position: absolute` descendants) can result in more `position: fixed` descendants + // collecting in `self.absolutes`. We need to loop here in order to keep laying them out. We + // know there aren't any more when `self.absolutes` is empty. + while !self.absolutes.is_empty() { HoistedAbsolutelyPositionedBox::layout_many( layout_context, - &mut mem::take(&mut self.for_nearest_containing_block_for_all_descendants), + mem::take(&mut self.absolutes), fragments, - &mut self.for_nearest_containing_block_for_all_descendants, + &mut self.absolutes, initial_containing_block, Default::default(), ) @@ -375,58 +349,46 @@ impl PositioningContext { /// Get the length of this [PositioningContext]. pub(crate) fn len(&self) -> PositioningContextLength { - PositioningContextLength { - for_nearest_positioned_ancestor: self - .for_nearest_positioned_ancestor - .as_ref() - .map_or(0, |vec| vec.len()), - for_nearest_containing_block_for_all_descendants: self - .for_nearest_containing_block_for_all_descendants - .len(), - } + PositioningContextLength(self.absolutes.len()) } /// Truncate this [PositioningContext] to the given [PositioningContextLength]. This /// is useful for "unhoisting" boxes in this context and returning it to the state at /// the time that [`PositioningContext::len()`] was called. pub(crate) fn truncate(&mut self, length: &PositioningContextLength) { - if let Some(vec) = self.for_nearest_positioned_ancestor.as_mut() { - vec.truncate(length.for_nearest_positioned_ancestor); - } - self.for_nearest_containing_block_for_all_descendants - .truncate(length.for_nearest_containing_block_for_all_descendants); + self.absolutes.truncate(length.0) } } /// A data structure which stores the size of a positioning context. #[derive(Clone, Copy, Debug, PartialEq)] -pub(crate) struct PositioningContextLength { - /// The number of boxes that will be hoisted the the nearest positioned ancestor for - /// layout. - for_nearest_positioned_ancestor: usize, - /// The number of boxes that will be hoisted the the nearest ancestor which - /// establishes a containing block for all descendants for layout. - for_nearest_containing_block_for_all_descendants: usize, -} +pub(crate) struct PositioningContextLength(usize); impl Zero for PositioningContextLength { fn zero() -> Self { - PositioningContextLength { - for_nearest_positioned_ancestor: 0, - for_nearest_containing_block_for_all_descendants: 0, - } + Self(0) } fn is_zero(&self) -> bool { - self.for_nearest_positioned_ancestor == 0 && - self.for_nearest_containing_block_for_all_descendants == 0 + self.0.is_zero() } } impl HoistedAbsolutelyPositionedBox { + fn position(&self) -> Position { + let position = self + .absolutely_positioned_box + .borrow() + .context + .style() + .clone_position(); + assert!(position == Position::Fixed || position == Position::Absolute); + position + } + pub(crate) fn layout_many( layout_context: &LayoutContext, - boxes: &mut [Self], + mut boxes: Vec<Self>, fragments: &mut Vec<Fragment>, for_nearest_containing_block_for_all_descendants: &mut Vec<HoistedAbsolutelyPositionedBox>, containing_block: &DefiniteContainingBlock, @@ -473,7 +435,7 @@ impl HoistedAbsolutelyPositionedBox { pub(crate) fn layout( &mut self, layout_context: &LayoutContext, - for_nearest_containing_block_for_all_descendants: &mut Vec<HoistedAbsolutelyPositionedBox>, + hoisted_absolutes_from_children: &mut Vec<HoistedAbsolutelyPositionedBox>, containing_block: &DefiniteContainingBlock, containing_block_padding: PhysicalSides<Au>, ) -> Fragment { @@ -596,7 +558,7 @@ impl HoistedAbsolutelyPositionedBox { .sizes })); - let mut positioning_context = context.new_positioning_context().unwrap(); + let mut positioning_context = PositioningContext::default(); let mut new_fragment = { let content_size: LogicalVec2<Au>; let fragments; @@ -709,6 +671,10 @@ impl HoistedAbsolutelyPositionedBox { ) .with_specific_layout_info(specific_layout_info) }; + + // This is an absolutely positioned element, which means it also establishes a + // containing block for absolutes. We lay out any absolutely positioned children + // here and pass the rest to `hoisted_absolutes_from_children.` positioning_context.layout_collected_children(layout_context, &mut new_fragment); // Any hoisted boxes that remain in this positioning context are going to be hoisted @@ -721,8 +687,7 @@ impl HoistedAbsolutelyPositionedBox { PositioningContextLength::zero(), ); - for_nearest_containing_block_for_all_descendants - .extend(positioning_context.for_nearest_containing_block_for_all_descendants); + hoisted_absolutes_from_children.extend(positioning_context.absolutes); let fragment = Fragment::Box(ArcRefCell::new(new_fragment)); context.base.set_fragment(fragment.clone()); @@ -1024,14 +989,6 @@ impl AbsoluteAxisSolver<'_> { } } -fn vec_append_owned<T>(a: &mut Vec<T>, mut b: Vec<T>) { - if a.is_empty() { - *a = b - } else { - a.append(&mut b) - } -} - /// <https://drafts.csswg.org/css2/visuren.html#relative-positioning> pub(crate) fn relative_adjustement( style: &ComputedValues, diff --git a/components/layout/replaced.rs b/components/layout/replaced.rs index b82fb947074..bbebc57aa97 100644 --- a/components/layout/replaced.rs +++ b/components/layout/replaced.rs @@ -3,7 +3,6 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ use std::cell::LazyCell; -use std::fmt; use std::sync::Arc; use app_units::Au; @@ -96,33 +95,9 @@ impl NaturalSizes { } } -#[derive(MallocSizeOf)] -pub(crate) enum CanvasSource { - WebGL(ImageKey), - Image(ImageKey), - WebGPU(ImageKey), - /// transparent black - Empty, -} - -impl fmt::Debug for CanvasSource { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!( - f, - "{}", - match *self { - CanvasSource::WebGL(_) => "WebGL", - CanvasSource::Image(_) => "Image", - CanvasSource::WebGPU(_) => "WebGPU", - CanvasSource::Empty => "Empty", - } - ) - } -} - #[derive(Debug, MallocSizeOf)] pub(crate) struct CanvasInfo { - pub source: CanvasSource, + pub source: Option<ImageKey>, } #[derive(Debug, MallocSizeOf)] @@ -388,12 +363,10 @@ impl ReplacedContents { return vec![]; } - let image_key = match canvas_info.source { - CanvasSource::WebGL(image_key) => image_key, - CanvasSource::WebGPU(image_key) => image_key, - CanvasSource::Image(image_key) => image_key, - CanvasSource::Empty => return vec![], + let Some(image_key) = canvas_info.source else { + return vec![]; }; + vec![Fragment::Image(ArcRefCell::new(ImageFragment { base: self.base_fragment_info.into(), style: style.clone(), diff --git a/components/layout/style_ext.rs b/components/layout/style_ext.rs index 023db6b07f1..68a4481a2be 100644 --- a/components/layout/style_ext.rs +++ b/components/layout/style_ext.rs @@ -712,15 +712,19 @@ impl ComputedValuesExt for ComputedValues { // From <https://www.w3.org/TR/css-transforms-1/#transform-rendering> // > For elements whose layout is governed by the CSS box model, any value other than // > `none` for the `transform` property results in the creation of a stacking context. + // + // From <https://www.w3.org/TR/css-transforms-2/#individual-transforms> + // > all other values […] create a stacking context and containing block for all + // > descendants, per usual for transforms. + // + // From <https://www.w3.org/TR/css-transforms-2/#perspective-property> + // > any value other than none establishes a stacking context. + // // From <https://www.w3.org/TR/css-transforms-2/#transform-style-property> // > A computed value of `preserve-3d` for `transform-style` on a transformable element // > establishes both a stacking context and a containing block for all descendants. - // From <https://www.w3.org/TR/css-transforms-2/#perspective-property> - // > any value other than none establishes a stacking context. - // TODO: handle individual transform properties (`translate`, `scale` and `rotate`). - // <https://www.w3.org/TR/css-transforms-2/#individual-transforms> if self.is_transformable(fragment_flags) && - (!self.get_box().transform.0.is_empty() || + (self.has_transform_or_perspective_style() || self.get_box().transform_style == ComputedTransformStyle::Preserve3d || will_change_bits .intersects(WillChangeBits::TRANSFORM | WillChangeBits::PERSPECTIVE)) diff --git a/components/layout/table/construct.rs b/components/layout/table/construct.rs index f20360d3b56..56e11320be4 100644 --- a/components/layout/table/construct.rs +++ b/components/layout/table/construct.rs @@ -1019,15 +1019,16 @@ where DisplayLayoutInternal::TableCell => { // This value will already have filtered out rowspan=0 // in quirks mode, so we don't have to worry about that. - // - // The HTML specification limits the parsed value of `rowspan` to - // 65534 and `colspan` to 1000, so we also enforce the same limits - // when dealing with arbitrary DOM elements (perhaps created via - // script). let (rowspan, colspan) = if info.pseudo_element_type.is_none() { let node = info.node.to_threadsafe(); - let rowspan = node.get_rowspan().unwrap_or(1).min(65534) as usize; - let colspan = node.get_colspan().unwrap_or(1).min(1000) as usize; + let rowspan = node.get_rowspan().unwrap_or(1) as usize; + let colspan = node.get_colspan().unwrap_or(1) as usize; + + // The HTML specification clamps value of `rowspan` to [0, 65534] and + // `colspan` to [1, 1000]. + assert!((1..=1000).contains(&colspan)); + assert!((0..=65534).contains(&rowspan)); + (rowspan, colspan) } else { (1, 1) @@ -1140,21 +1141,19 @@ fn add_column<'dom, Node: NodeExt<'dom>>( is_anonymous: bool, ) -> ArcRefCell<TableTrack> { let span = if column_info.pseudo_element_type.is_none() { - column_info - .node - .to_threadsafe() - .get_span() - .unwrap_or(1) - .min(1000) as usize + column_info.node.to_threadsafe().get_span().unwrap_or(1) } else { 1 }; + // The HTML specification clamps value of `span` for `<col>` to [1, 1000]. + assert!((1..=1000).contains(&span)); + let column = ArcRefCell::new(TableTrack { base: LayoutBoxBase::new(column_info.into(), column_info.style.clone()), group_index, is_anonymous, }); - collection.extend(repeat(column.clone()).take(span)); + collection.extend(repeat(column.clone()).take(span as usize)); column } diff --git a/components/layout/table/layout.rs b/components/layout/table/layout.rs index 0cbe3e9ca76..2efe339837e 100644 --- a/components/layout/table/layout.rs +++ b/components/layout/table/layout.rs @@ -1068,7 +1068,6 @@ impl<'a> TableLayout<'a> { &mut self, layout_context: &LayoutContext, containing_block_for_table: &ContainingBlock, - parent_positioning_context: &mut PositioningContext, ) { self.cells_laid_out = self .table @@ -1076,30 +1075,6 @@ impl<'a> TableLayout<'a> { .par_iter() .enumerate() .map(|(row_index, row_slots)| { - // When building the PositioningContext for this cell, we want it to have the same - // configuration for whatever PositioningContext the contents are ultimately added to. - let collect_for_nearest_positioned_ancestor = parent_positioning_context - .collects_for_nearest_positioned_ancestor() || - self.table.rows.get(row_index).is_some_and(|row| { - let row = row.borrow(); - let row_group_collects_for_nearest_positioned_ancestor = - row.group_index.is_some_and(|group_index| { - self.table.row_groups[group_index] - .borrow() - .base - .style - .establishes_containing_block_for_absolute_descendants( - FragmentFlags::empty(), - ) - }); - row_group_collects_for_nearest_positioned_ancestor || - row.base - .style - .establishes_containing_block_for_absolute_descendants( - FragmentFlags::empty(), - ) - }); - row_slots .par_iter() .enumerate() @@ -1141,10 +1116,7 @@ impl<'a> TableLayout<'a> { style: &cell.base.style, }; - let mut positioning_context = PositioningContext::new_for_subtree( - collect_for_nearest_positioned_ancestor, - ); - + let mut positioning_context = PositioningContext::default(); let layout = cell.contents.layout( layout_context, &mut positioning_context, @@ -1503,7 +1475,6 @@ impl<'a> TableLayout<'a> { layout_context: &LayoutContext, parent_positioning_context: &mut PositioningContext, ) -> BoxFragment { - let mut positioning_context = caption.context.new_positioning_context(); let containing_block = &ContainingBlock { size: ContainingBlockSize { inline: self.table_width + self.pbm.padding_border_sums.inline, @@ -1517,6 +1488,8 @@ impl<'a> TableLayout<'a> { // stretch block size. https://drafts.csswg.org/css-sizing-4/#stretch-fit-sizing let ignore_block_margins_for_stretch = LogicalSides1D::new(false, false); + let mut positioning_context = + PositioningContext::new_for_layout_box_base(&caption.context.base); let mut box_fragment = caption.context.layout_in_flow_block_level( layout_context, positioning_context @@ -1769,11 +1742,7 @@ impl<'a> TableLayout<'a> { ) -> BoxFragment { self.distributed_column_widths = Self::distribute_width_to_columns(self.assignable_width, &self.columns); - self.layout_cells_in_row( - layout_context, - containing_block_for_children, - positioning_context, - ); + self.layout_cells_in_row(layout_context, containing_block_for_children); let table_writing_mode = containing_block_for_children.style.writing_mode; let first_layout_row_heights = self.do_first_row_layout(table_writing_mode); self.compute_table_height_and_final_row_heights( @@ -2325,7 +2294,7 @@ impl<'a> RowFragmentLayout<'a> { Self { row: table_row, rect, - positioning_context: table_row.base.new_positioning_context(), + positioning_context: PositioningContext::new_for_layout_box_base(&table_row.base), containing_block, fragments: Vec::new(), } @@ -2379,11 +2348,11 @@ impl<'a> RowFragmentLayout<'a> { if let Some(mut row_positioning_context) = self.positioning_context.take() { row_positioning_context.layout_collected_children(layout_context, &mut row_fragment); - let positioning_context = row_group_fragment_layout + let parent_positioning_context = row_group_fragment_layout .as_mut() .and_then(|layout| layout.positioning_context.as_mut()) .unwrap_or(table_positioning_context); - positioning_context.append(row_positioning_context); + parent_positioning_context.append(row_positioning_context); } let fragment = Fragment::Box(ArcRefCell::new(row_fragment)); @@ -2410,7 +2379,7 @@ impl RowGroupFragmentLayout { let row_group = row_group.borrow(); ( dimensions.get_row_group_rect(&row_group), - row_group.base.new_positioning_context(), + PositioningContext::new_for_layout_box_base(&row_group.base), ) }; Self { diff --git a/components/layout/taffy/layout.rs b/components/layout/taffy/layout.rs index 3777c902053..a5838c1bd65 100644 --- a/components/layout/taffy/layout.rs +++ b/components/layout/taffy/layout.rs @@ -29,7 +29,7 @@ use crate::geom::{ use crate::layout_box_base::CacheableLayoutResult; use crate::positioned::{AbsolutelyPositionedBox, PositioningContext, PositioningContextLength}; use crate::sizing::{ComputeInlineContentSizes, ContentSizes, InlineContentSizesResult}; -use crate::style_ext::{ComputedValuesExt, LayoutStyle}; +use crate::style_ext::LayoutStyle; use crate::{ConstraintSpace, ContainingBlock, ContainingBlockSize}; const DUMMY_NODE_ID: taffy::NodeId = taffy::NodeId::new(u64::MAX); @@ -250,29 +250,15 @@ impl taffy::LayoutPartialTree for TaffyContainerContext<'_> { }, style, }; - let layout = { - let mut child_positioning_context = independent_context - .new_positioning_context() - .unwrap_or_else(|| { - PositioningContext::new_for_subtree( - self.positioning_context - .collects_for_nearest_positioned_ancestor(), - ) - }); - - let layout = non_replaced.layout_without_caching( - self.layout_context, - &mut child_positioning_context, - &content_box_size_override, - containing_block, - false, /* depends_on_block_constraints */ - ); - // Store layout data on child for later access - child.positioning_context = child_positioning_context; - - layout - }; + child.positioning_context = PositioningContext::default(); + let layout = non_replaced.layout_without_caching( + self.layout_context, + &mut child.positioning_context, + &content_box_size_override, + containing_block, + false, /* depends_on_block_constraints */ + ); child.child_fragments = layout.fragments; self.child_specific_layout_infos[usize::from(node_id)] = @@ -373,8 +359,7 @@ impl ComputeInlineContentSizes for TaffyContainer { let mut grid_context = TaffyContainerContext { layout_context, - positioning_context: - &mut PositioningContext::new_for_containing_block_for_all_descendants(), + positioning_context: &mut PositioningContext::default(), content_box_size_override: containing_block, style, source_child_nodes: &self.children, @@ -540,17 +525,6 @@ impl TaffyContainer { let child_specific_layout_info: Option<SpecificLayoutInfo> = std::mem::take(&mut container_ctx.child_specific_layout_infos[child_id]); - let establishes_containing_block_for_absolute_descendants = - if let TaffyItemBoxInner::InFlowBox(independent_box) = &child.taffy_level_box { - child - .style - .establishes_containing_block_for_absolute_descendants( - independent_box.base_fragment_info().flags, - ) - } else { - false - }; - let fragment = match &mut child.taffy_level_box { TaffyItemBoxInner::InFlowBox(independent_box) => { let mut fragment_info = independent_box.base_fragment_info(); @@ -573,29 +547,21 @@ impl TaffyContainer { }) .with_specific_layout_info(child_specific_layout_info); - if establishes_containing_block_for_absolute_descendants { - child.positioning_context.layout_collected_children( - container_ctx.layout_context, - &mut box_fragment, - ); - } - - let fragment = Fragment::Box(ArcRefCell::new(box_fragment)); - + child.positioning_context.layout_collected_children( + container_ctx.layout_context, + &mut box_fragment, + ); child .positioning_context - .adjust_static_position_of_hoisted_fragments( - &fragment, + .adjust_static_position_of_hoisted_fragments_with_offset( + &box_fragment.content_rect.origin.to_vector(), PositioningContextLength::zero(), ); - let child_positioning_context = std::mem::replace( - &mut child.positioning_context, - PositioningContext::new_for_containing_block_for_all_descendants(), - ); container_ctx .positioning_context - .append(child_positioning_context); - fragment + .append(std::mem::take(&mut child.positioning_context)); + + Fragment::Box(ArcRefCell::new(box_fragment)) }, TaffyItemBoxInner::OutOfFlowAbsolutelyPositionedBox(abs_pos_box) => { fn resolve_alignment(value: AlignFlags, auto: AlignFlags) -> AlignFlags { diff --git a/components/layout/taffy/mod.rs b/components/layout/taffy/mod.rs index 55a678cd89a..b1ff753ea78 100644 --- a/components/layout/taffy/mod.rs +++ b/components/layout/taffy/mod.rs @@ -110,7 +110,7 @@ impl TaffyItemBox { Self { taffy_layout: Default::default(), child_fragments: Vec::new(), - positioning_context: PositioningContext::new_for_containing_block_for_all_descendants(), + positioning_context: PositioningContext::default(), style, taffy_level_box: inner, } @@ -118,8 +118,7 @@ impl TaffyItemBox { pub(crate) fn invalidate_cached_fragment(&mut self) { self.taffy_layout = Default::default(); - self.positioning_context = - PositioningContext::new_for_containing_block_for_all_descendants(); + self.positioning_context = PositioningContext::default(); match self.taffy_level_box { TaffyItemBoxInner::InFlowBox(ref independent_formatting_context) => { independent_formatting_context diff --git a/components/script/canvas_context.rs b/components/script/canvas_context.rs index d49d31997e1..7dfdf48e3f5 100644 --- a/components/script/canvas_context.rs +++ b/components/script/canvas_context.rs @@ -5,16 +5,25 @@ //! Common interfaces for Canvas Contexts use euclid::default::Size2D; -use script_layout_interface::{HTMLCanvasData, HTMLCanvasDataSource}; +use script_bindings::root::Dom; +use script_layout_interface::HTMLCanvasData; use snapshot::Snapshot; +use webrender_api::ImageKey; use crate::dom::bindings::codegen::UnionTypes::HTMLCanvasElementOrOffscreenCanvas; use crate::dom::bindings::inheritance::Castable; use crate::dom::htmlcanvaselement::HTMLCanvasElement; use crate::dom::node::{Node, NodeDamage}; +#[cfg(feature = "webgpu")] +use crate::dom::types::GPUCanvasContext; +use crate::dom::types::{ + CanvasRenderingContext2D, OffscreenCanvas, OffscreenCanvasRenderingContext2D, + WebGL2RenderingContext, WebGLRenderingContext, +}; pub(crate) trait LayoutCanvasRenderingContextHelpers { - fn canvas_data_source(self) -> HTMLCanvasDataSource; + /// `None` is rendered as transparent black (cleared canvas) + fn canvas_data_source(self) -> Option<ImageKey>; } pub(crate) trait LayoutHTMLCanvasElementHelpers { @@ -85,3 +94,180 @@ impl CanvasHelpers for HTMLCanvasElementOrOffscreenCanvas { } } } + +/// Non rooted variant of [`crate::dom::bindings::codegen::Bindings::HTMLCanvasElementBinding::RenderingContext`] +#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)] +#[derive(Clone, JSTraceable, MallocSizeOf)] +pub(crate) enum RenderingContext { + Placeholder(Dom<OffscreenCanvas>), + Context2d(Dom<CanvasRenderingContext2D>), + WebGL(Dom<WebGLRenderingContext>), + WebGL2(Dom<WebGL2RenderingContext>), + #[cfg(feature = "webgpu")] + WebGPU(Dom<GPUCanvasContext>), +} + +impl CanvasContext for RenderingContext { + type ID = (); + + fn context_id(&self) -> Self::ID {} + + fn canvas(&self) -> HTMLCanvasElementOrOffscreenCanvas { + match self { + RenderingContext::Placeholder(context) => (*context.context().unwrap()).canvas(), + RenderingContext::Context2d(context) => context.canvas(), + RenderingContext::WebGL(context) => context.canvas(), + RenderingContext::WebGL2(context) => context.canvas(), + #[cfg(feature = "webgpu")] + RenderingContext::WebGPU(context) => context.canvas(), + } + } + + fn resize(&self) { + match self { + RenderingContext::Placeholder(context) => (*context.context().unwrap()).resize(), + RenderingContext::Context2d(context) => context.resize(), + RenderingContext::WebGL(context) => context.resize(), + RenderingContext::WebGL2(context) => context.resize(), + #[cfg(feature = "webgpu")] + RenderingContext::WebGPU(context) => context.resize(), + } + } + + fn get_image_data(&self) -> Option<Snapshot> { + match self { + RenderingContext::Placeholder(context) => { + (*context.context().unwrap()).get_image_data() + }, + RenderingContext::Context2d(context) => context.get_image_data(), + RenderingContext::WebGL(context) => context.get_image_data(), + RenderingContext::WebGL2(context) => context.get_image_data(), + #[cfg(feature = "webgpu")] + RenderingContext::WebGPU(context) => context.get_image_data(), + } + } + + fn origin_is_clean(&self) -> bool { + match self { + RenderingContext::Placeholder(context) => { + (*context.context().unwrap()).origin_is_clean() + }, + RenderingContext::Context2d(context) => context.origin_is_clean(), + RenderingContext::WebGL(context) => context.origin_is_clean(), + RenderingContext::WebGL2(context) => context.origin_is_clean(), + #[cfg(feature = "webgpu")] + RenderingContext::WebGPU(context) => context.origin_is_clean(), + } + } + + fn size(&self) -> Size2D<u64> { + match self { + RenderingContext::Placeholder(context) => (*context.context().unwrap()).size(), + RenderingContext::Context2d(context) => context.size(), + RenderingContext::WebGL(context) => context.size(), + RenderingContext::WebGL2(context) => context.size(), + #[cfg(feature = "webgpu")] + RenderingContext::WebGPU(context) => context.size(), + } + } + + fn mark_as_dirty(&self) { + match self { + RenderingContext::Placeholder(context) => (*context.context().unwrap()).mark_as_dirty(), + RenderingContext::Context2d(context) => context.mark_as_dirty(), + RenderingContext::WebGL(context) => context.mark_as_dirty(), + RenderingContext::WebGL2(context) => context.mark_as_dirty(), + #[cfg(feature = "webgpu")] + RenderingContext::WebGPU(context) => context.mark_as_dirty(), + } + } + + fn update_rendering(&self) { + match self { + RenderingContext::Placeholder(context) => { + (*context.context().unwrap()).update_rendering() + }, + RenderingContext::Context2d(context) => context.update_rendering(), + RenderingContext::WebGL(context) => context.update_rendering(), + RenderingContext::WebGL2(context) => context.update_rendering(), + #[cfg(feature = "webgpu")] + RenderingContext::WebGPU(context) => context.update_rendering(), + } + } + + fn onscreen(&self) -> bool { + match self { + RenderingContext::Placeholder(context) => (*context.context().unwrap()).onscreen(), + RenderingContext::Context2d(context) => context.onscreen(), + RenderingContext::WebGL(context) => context.onscreen(), + RenderingContext::WebGL2(context) => context.onscreen(), + #[cfg(feature = "webgpu")] + RenderingContext::WebGPU(context) => context.onscreen(), + } + } +} + +/// Non rooted variant of [`crate::dom::bindings::codegen::Bindings::OffscreenCanvasBinding::OffscreenRenderingContext`] +#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)] +#[derive(Clone, JSTraceable, MallocSizeOf)] +pub(crate) enum OffscreenRenderingContext { + Context2d(Dom<OffscreenCanvasRenderingContext2D>), + //WebGL(Dom<WebGLRenderingContext>), + //WebGL2(Dom<WebGL2RenderingContext>), + //#[cfg(feature = "webgpu")] + //WebGPU(Dom<GPUCanvasContext>), +} + +impl CanvasContext for OffscreenRenderingContext { + type ID = (); + + fn context_id(&self) -> Self::ID {} + + fn canvas(&self) -> HTMLCanvasElementOrOffscreenCanvas { + match self { + OffscreenRenderingContext::Context2d(context) => context.canvas(), + } + } + + fn resize(&self) { + match self { + OffscreenRenderingContext::Context2d(context) => context.resize(), + } + } + + fn get_image_data(&self) -> Option<Snapshot> { + match self { + OffscreenRenderingContext::Context2d(context) => context.get_image_data(), + } + } + + fn origin_is_clean(&self) -> bool { + match self { + OffscreenRenderingContext::Context2d(context) => context.origin_is_clean(), + } + } + + fn size(&self) -> Size2D<u64> { + match self { + OffscreenRenderingContext::Context2d(context) => context.size(), + } + } + + fn mark_as_dirty(&self) { + match self { + OffscreenRenderingContext::Context2d(context) => context.mark_as_dirty(), + } + } + + fn update_rendering(&self) { + match self { + OffscreenRenderingContext::Context2d(context) => context.update_rendering(), + } + } + + fn onscreen(&self) -> bool { + match self { + OffscreenRenderingContext::Context2d(context) => context.onscreen(), + } + } +} diff --git a/components/script/canvas_state.rs b/components/script/canvas_state.rs index e9892818e92..dabe6a5728b 100644 --- a/components/script/canvas_state.rs +++ b/components/script/canvas_state.rs @@ -36,6 +36,7 @@ use style_traits::{CssWriter, ParsingMode}; use url::Url; use webrender_api::ImageKey; +use crate::canvas_context::{OffscreenRenderingContext, RenderingContext}; use crate::dom::bindings::cell::DomRefCell; use crate::dom::bindings::codegen::Bindings::CanvasRenderingContext2DBinding::{ CanvasDirection, CanvasFillRule, CanvasImageSource, CanvasLineCap, CanvasLineJoin, @@ -52,10 +53,10 @@ use crate::dom::canvaspattern::CanvasPattern; use crate::dom::dommatrix::DOMMatrix; use crate::dom::element::{Element, cors_setting_for_element}; use crate::dom::globalscope::GlobalScope; -use crate::dom::htmlcanvaselement::{CanvasContext, HTMLCanvasElement}; +use crate::dom::htmlcanvaselement::HTMLCanvasElement; use crate::dom::imagedata::ImageData; use crate::dom::node::{Node, NodeTraits}; -use crate::dom::offscreencanvas::{OffscreenCanvas, OffscreenCanvasContext}; +use crate::dom::offscreencanvas::OffscreenCanvas; use crate::dom::paintworkletglobalscope::PaintWorkletGlobalScope; use crate::dom::textmetrics::TextMetrics; use crate::script_runtime::CanGc; @@ -522,7 +523,7 @@ impl CanvasState { if let Some(context) = canvas.context() { match *context { - OffscreenCanvasContext::OffscreenContext2d(ref context) => { + OffscreenRenderingContext::Context2d(ref context) => { context.send_canvas_2d_msg(Canvas2dMsg::DrawImageInOther( self.get_canvas_id(), image_size, @@ -577,7 +578,7 @@ impl CanvasState { if let Some(context) = canvas.context() { match *context { - CanvasContext::Context2d(ref context) => { + RenderingContext::Context2d(ref context) => { context.send_canvas_2d_msg(Canvas2dMsg::DrawImageInOther( self.get_canvas_id(), image_size, @@ -586,12 +587,12 @@ impl CanvasState { smoothing_enabled, )); }, - CanvasContext::Placeholder(ref context) => { + RenderingContext::Placeholder(ref context) => { let Some(context) = context.context() else { return Err(Error::InvalidState); }; match *context { - OffscreenCanvasContext::OffscreenContext2d(ref context) => context + OffscreenRenderingContext::Context2d(ref context) => context .send_canvas_2d_msg(Canvas2dMsg::DrawImageInOther( self.get_canvas_id(), image_size, diff --git a/components/script/dom/canvasrenderingcontext2d.rs b/components/script/dom/canvasrenderingcontext2d.rs index 38bd38ad511..046d478e49d 100644 --- a/components/script/dom/canvasrenderingcontext2d.rs +++ b/components/script/dom/canvasrenderingcontext2d.rs @@ -7,9 +7,9 @@ use dom_struct::dom_struct; use euclid::default::Size2D; use profile_traits::ipc; use script_bindings::inheritance::Castable; -use script_layout_interface::HTMLCanvasDataSource; use servo_url::ServoUrl; use snapshot::Snapshot; +use webrender_api::ImageKey; use crate::canvas_context::{CanvasContext, CanvasHelpers, LayoutCanvasRenderingContextHelpers}; use crate::canvas_state::CanvasState; @@ -98,13 +98,13 @@ impl CanvasRenderingContext2D { } impl LayoutCanvasRenderingContextHelpers for LayoutDom<'_, CanvasRenderingContext2D> { - fn canvas_data_source(self) -> HTMLCanvasDataSource { + fn canvas_data_source(self) -> Option<ImageKey> { let canvas_state = &self.unsafe_get().canvas_state; if canvas_state.is_paintable() { - HTMLCanvasDataSource::Image(canvas_state.image_key()) + Some(canvas_state.image_key()) } else { - HTMLCanvasDataSource::Empty + None } } } diff --git a/components/script/dom/element.rs b/components/script/dom/element.rs index 2831fc3d8f0..c040078f707 100644 --- a/components/script/dom/element.rs +++ b/components/script/dom/element.rs @@ -3025,28 +3025,8 @@ impl ElementMethods<crate::DomTypeHolder> for Element { DomRoot::from_ref(self.upcast()) }; - // Step 3. Unsafely set HTML given target, this, and compliantHTML. - // Let newChildren be the result of the HTML fragment parsing algorithm. - let new_children = ServoParser::parse_html_fragment(self, html, true, can_gc); - - let context_document = { - if let Some(template) = self.downcast::<HTMLTemplateElement>() { - template.Content(can_gc).upcast::<Node>().owner_doc() - } else { - self.owner_document() - } - }; - - // Let fragment be a new DocumentFragment whose node document is contextElement's node document. - let frag = DocumentFragment::new(&context_document, can_gc); - - // For each node in newChildren, append node to fragment. - for child in new_children { - frag.upcast::<Node>().AppendChild(&child, can_gc).unwrap(); - } - - // Replace all with fragment within target. - Node::replace_all(Some(frag.upcast()), &target, can_gc); + // Step 3. Unsafely set HTML given target, this, and compliantHTML + Node::unsafely_set_html(&target, self, html, can_gc); } /// <https://html.spec.whatwg.org/multipage/#dom-element-gethtml> diff --git a/components/script/dom/gpucanvascontext.rs b/components/script/dom/gpucanvascontext.rs index 5304d0f5d3b..f47e1dfddd1 100644 --- a/components/script/dom/gpucanvascontext.rs +++ b/components/script/dom/gpucanvascontext.rs @@ -3,7 +3,7 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ use dom_struct::dom_struct; -use script_layout_interface::HTMLCanvasDataSource; +use webrender_api::ImageKey; use crate::dom::bindings::codegen::Bindings::GPUCanvasContextBinding::GPUCanvasContextMethods; use crate::dom::bindings::codegen::UnionTypes; @@ -31,7 +31,7 @@ impl GPUCanvasContextMethods<crate::DomTypeHolder> for GPUCanvasContext { } impl LayoutCanvasRenderingContextHelpers for LayoutDom<'_, GPUCanvasContext> { - fn canvas_data_source(self) -> HTMLCanvasDataSource { + fn canvas_data_source(self) -> Option<ImageKey> { unimplemented!() } } diff --git a/components/script/dom/htmlcanvaselement.rs b/components/script/dom/htmlcanvaselement.rs index bb27d28cea8..56e008839ba 100644 --- a/components/script/dom/htmlcanvaselement.rs +++ b/components/script/dom/htmlcanvaselement.rs @@ -21,20 +21,19 @@ use image::{ColorType, ImageEncoder}; use ipc_channel::ipc::{self as ipcchan}; use js::error::throw_type_error; use js::rust::{HandleObject, HandleValue}; -use script_layout_interface::{HTMLCanvasData, HTMLCanvasDataSource}; +use script_layout_interface::HTMLCanvasData; use servo_media::streams::MediaStreamType; use servo_media::streams::registry::MediaStreamId; use snapshot::Snapshot; use style::attr::AttrValue; -use crate::canvas_context::CanvasContext as _; pub(crate) use crate::canvas_context::*; use crate::conversions::Convert; use crate::dom::attr::Attr; use crate::dom::bindings::callback::ExceptionHandling; use crate::dom::bindings::cell::{DomRefCell, Ref, ref_filter_map}; use crate::dom::bindings::codegen::Bindings::HTMLCanvasElementBinding::{ - BlobCallback, HTMLCanvasElementMethods, RenderingContext, + BlobCallback, HTMLCanvasElementMethods, RenderingContext as RootedRenderingContext, }; use crate::dom::bindings::codegen::Bindings::MediaStreamBinding::MediaStreamMethods; use crate::dom::bindings::codegen::Bindings::WebGLRenderingContextBinding::WebGLContextAttributes; @@ -104,21 +103,10 @@ impl EncodedImageType { } } -#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)] -#[derive(Clone, JSTraceable, MallocSizeOf)] -pub(crate) enum CanvasContext { - Placeholder(Dom<OffscreenCanvas>), - Context2d(Dom<CanvasRenderingContext2D>), - WebGL(Dom<WebGLRenderingContext>), - WebGL2(Dom<WebGL2RenderingContext>), - #[cfg(feature = "webgpu")] - WebGPU(Dom<GPUCanvasContext>), -} - #[dom_struct] pub(crate) struct HTMLCanvasElement { htmlelement: HTMLElement, - context: DomRefCell<Option<CanvasContext>>, + context: DomRefCell<Option<RenderingContext>>, // This id and hashmap are used to keep track of ongoing toBlob() calls. callback_id: Cell<u32>, #[ignore_malloc_size_of = "not implemented for webidl callbacks"] @@ -159,14 +147,7 @@ impl HTMLCanvasElement { fn recreate_contexts_after_resize(&self) { if let Some(ref context) = *self.context.borrow() { - match *context { - CanvasContext::Context2d(ref context) => context.resize(), - CanvasContext::WebGL(ref context) => context.resize(), - CanvasContext::WebGL2(ref context) => context.resize(), - #[cfg(feature = "webgpu")] - CanvasContext::WebGPU(ref context) => context.resize(), - CanvasContext::Placeholder(ref context) => context.resize(self.get_size().cast()), - } + context.resize() } } @@ -176,23 +157,14 @@ impl HTMLCanvasElement { pub(crate) fn origin_is_clean(&self) -> bool { match *self.context.borrow() { - Some(CanvasContext::Context2d(ref context)) => context.origin_is_clean(), + Some(ref context) => context.origin_is_clean(), _ => true, } } pub(crate) fn mark_as_dirty(&self) { if let Some(ref context) = *self.context.borrow() { - match *context { - CanvasContext::Context2d(ref context) => context.mark_as_dirty(), - CanvasContext::WebGL(ref context) => context.mark_as_dirty(), - CanvasContext::WebGL2(ref context) => context.mark_as_dirty(), - #[cfg(feature = "webgpu")] - CanvasContext::WebGPU(ref context) => context.mark_as_dirty(), - CanvasContext::Placeholder(ref _context) => { - // TODO: Should this be marked as dirty? - }, - } + context.mark_as_dirty() } } @@ -222,12 +194,14 @@ impl LayoutHTMLCanvasElementHelpers for LayoutDom<'_, HTMLCanvasElement> { fn data(self) -> HTMLCanvasData { let source = unsafe { match self.unsafe_get().context.borrow_for_layout().as_ref() { - Some(CanvasContext::Context2d(context)) => context.to_layout().canvas_data_source(), - Some(CanvasContext::WebGL(context)) => context.to_layout().canvas_data_source(), - Some(CanvasContext::WebGL2(context)) => context.to_layout().canvas_data_source(), + Some(RenderingContext::Context2d(context)) => { + context.to_layout().canvas_data_source() + }, + Some(RenderingContext::WebGL(context)) => context.to_layout().canvas_data_source(), + Some(RenderingContext::WebGL2(context)) => context.to_layout().canvas_data_source(), #[cfg(feature = "webgpu")] - Some(CanvasContext::WebGPU(context)) => context.to_layout().canvas_data_source(), - Some(CanvasContext::Placeholder(_)) | None => HTMLCanvasDataSource::Empty, + Some(RenderingContext::WebGPU(context)) => context.to_layout().canvas_data_source(), + Some(RenderingContext::Placeholder(_)) | None => None, } }; @@ -246,14 +220,14 @@ impl LayoutHTMLCanvasElementHelpers for LayoutDom<'_, HTMLCanvasElement> { } impl HTMLCanvasElement { - pub(crate) fn context(&self) -> Option<Ref<CanvasContext>> { + pub(crate) fn context(&self) -> Option<Ref<RenderingContext>> { ref_filter_map(self.context.borrow(), |ctx| ctx.as_ref()) } fn get_or_init_2d_context(&self, can_gc: CanGc) -> Option<DomRoot<CanvasRenderingContext2D>> { if let Some(ctx) = self.context() { return match *ctx { - CanvasContext::Context2d(ref ctx) => Some(DomRoot::from_ref(ctx)), + RenderingContext::Context2d(ref ctx) => Some(DomRoot::from_ref(ctx)), _ => None, }; } @@ -261,7 +235,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); - *self.context.borrow_mut() = Some(CanvasContext::Context2d(Dom::from_ref(&*context))); + *self.context.borrow_mut() = Some(RenderingContext::Context2d(Dom::from_ref(&*context))); Some(context) } @@ -273,7 +247,7 @@ impl HTMLCanvasElement { ) -> Option<DomRoot<WebGLRenderingContext>> { if let Some(ctx) = self.context() { return match *ctx { - CanvasContext::WebGL(ref ctx) => Some(DomRoot::from_ref(ctx)), + RenderingContext::WebGL(ref ctx) => Some(DomRoot::from_ref(ctx)), _ => None, }; } @@ -289,7 +263,7 @@ impl HTMLCanvasElement { attrs, can_gc, )?; - *self.context.borrow_mut() = Some(CanvasContext::WebGL(Dom::from_ref(&*context))); + *self.context.borrow_mut() = Some(RenderingContext::WebGL(Dom::from_ref(&*context))); Some(context) } @@ -305,7 +279,7 @@ impl HTMLCanvasElement { } if let Some(ctx) = self.context() { return match *ctx { - CanvasContext::WebGL2(ref ctx) => Some(DomRoot::from_ref(ctx)), + RenderingContext::WebGL2(ref ctx) => Some(DomRoot::from_ref(ctx)), _ => None, }; } @@ -314,7 +288,7 @@ impl HTMLCanvasElement { let attrs = Self::get_gl_attributes(cx, options)?; let canvas = HTMLCanvasElementOrOffscreenCanvas::HTMLCanvasElement(DomRoot::from_ref(self)); let context = WebGL2RenderingContext::new(&window, &canvas, size, attrs, can_gc)?; - *self.context.borrow_mut() = Some(CanvasContext::WebGL2(Dom::from_ref(&*context))); + *self.context.borrow_mut() = Some(RenderingContext::WebGL2(Dom::from_ref(&*context))); Some(context) } @@ -327,7 +301,7 @@ impl HTMLCanvasElement { fn get_or_init_webgpu_context(&self, can_gc: CanGc) -> Option<DomRoot<GPUCanvasContext>> { if let Some(ctx) = self.context() { return match *ctx { - CanvasContext::WebGPU(ref ctx) => Some(DomRoot::from_ref(ctx)), + RenderingContext::WebGPU(ref ctx) => Some(DomRoot::from_ref(ctx)), _ => None, }; } @@ -341,7 +315,8 @@ impl HTMLCanvasElement { .expect("Failed to get WebGPU channel") .map(|channel| { let context = GPUCanvasContext::new(&global_scope, self, channel, can_gc); - *self.context.borrow_mut() = Some(CanvasContext::WebGPU(Dom::from_ref(&*context))); + *self.context.borrow_mut() = + Some(RenderingContext::WebGPU(Dom::from_ref(&*context))); context }) } @@ -349,8 +324,8 @@ impl HTMLCanvasElement { /// Gets the base WebGLRenderingContext for WebGL or WebGL 2, if exists. pub(crate) fn get_base_webgl_context(&self) -> Option<DomRoot<WebGLRenderingContext>> { match *self.context.borrow() { - Some(CanvasContext::WebGL(ref context)) => Some(DomRoot::from_ref(context)), - Some(CanvasContext::WebGL2(ref context)) => Some(context.base_context()), + Some(RenderingContext::WebGL(ref context)) => Some(DomRoot::from_ref(context)), + Some(RenderingContext::WebGL2(ref context)) => Some(context.base_context()), _ => None, } } @@ -378,12 +353,7 @@ impl HTMLCanvasElement { pub(crate) fn get_image_data(&self) -> Option<Snapshot> { match self.context.borrow().as_ref() { - Some(CanvasContext::Context2d(context)) => context.get_image_data(), - Some(CanvasContext::WebGL(context)) => context.get_image_data(), - Some(CanvasContext::WebGL2(context)) => context.get_image_data(), - #[cfg(feature = "webgpu")] - Some(CanvasContext::WebGPU(context)) => context.get_image_data(), - Some(CanvasContext::Placeholder(context)) => context.get_image_data(), + Some(context) => context.get_image_data(), None => { let size = self.get_size(); if size.width == 0 || size.height == 0 { @@ -466,7 +436,7 @@ impl HTMLCanvasElementMethods<crate::DomTypeHolder> for HTMLCanvasElement { // is set to placeholder, the user agent must throw an "InvalidStateError" DOMException and leave the // attribute's value unchanged. fn SetWidth(&self, value: u32, can_gc: CanGc) -> Fallible<()> { - if let Some(CanvasContext::Placeholder(_)) = *self.context.borrow() { + if let Some(RenderingContext::Placeholder(_)) = *self.context.borrow() { return Err(Error::InvalidState); } @@ -485,7 +455,7 @@ impl HTMLCanvasElementMethods<crate::DomTypeHolder> for HTMLCanvasElement { // https://html.spec.whatwg.org/multipage/#dom-canvas-height fn SetHeight(&self, value: u32, can_gc: CanGc) -> Fallible<()> { - if let Some(CanvasContext::Placeholder(_)) = *self.context.borrow() { + if let Some(RenderingContext::Placeholder(_)) = *self.context.borrow() { return Err(Error::InvalidState); } @@ -506,26 +476,26 @@ impl HTMLCanvasElementMethods<crate::DomTypeHolder> for HTMLCanvasElement { id: DOMString, options: HandleValue, can_gc: CanGc, - ) -> Fallible<Option<RenderingContext>> { + ) -> Fallible<Option<RootedRenderingContext>> { // Always throw an InvalidState exception when the canvas is in Placeholder mode (See table in the spec). - if let Some(CanvasContext::Placeholder(_)) = *self.context.borrow() { + if let Some(RenderingContext::Placeholder(_)) = *self.context.borrow() { return Err(Error::InvalidState); } Ok(match &*id { "2d" => self .get_or_init_2d_context(can_gc) - .map(RenderingContext::CanvasRenderingContext2D), + .map(RootedRenderingContext::CanvasRenderingContext2D), "webgl" | "experimental-webgl" => self .get_or_init_webgl_context(cx, options, can_gc) - .map(RenderingContext::WebGLRenderingContext), + .map(RootedRenderingContext::WebGLRenderingContext), "webgl2" | "experimental-webgl2" => self .get_or_init_webgl2_context(cx, options, can_gc) - .map(RenderingContext::WebGL2RenderingContext), + .map(RootedRenderingContext::WebGL2RenderingContext), #[cfg(feature = "webgpu")] "webgpu" => self .get_or_init_webgpu_context(can_gc) - .map(RenderingContext::GPUCanvasContext), + .map(RootedRenderingContext::GPUCanvasContext), _ => None, }) } @@ -672,7 +642,8 @@ impl HTMLCanvasElementMethods<crate::DomTypeHolder> for HTMLCanvasElement { can_gc, ); // Step 4. Set this canvas element's context mode to placeholder. - *self.context.borrow_mut() = Some(CanvasContext::Placeholder(offscreen_canvas.as_traced())); + *self.context.borrow_mut() = + Some(RenderingContext::Placeholder(offscreen_canvas.as_traced())); // Step 5. Return offscreenCanvas. Ok(offscreen_canvas) diff --git a/components/script/dom/htmltablecellelement.rs b/components/script/dom/htmltablecellelement.rs index 4f312e928c4..8b553923230 100644 --- a/components/script/dom/htmltablecellelement.rs +++ b/components/script/dom/htmltablecellelement.rs @@ -70,13 +70,17 @@ impl HTMLTableCellElementMethods<crate::DomTypeHolder> for HTMLTableCellElement make_uint_getter!(ColSpan, "colspan", DEFAULT_COLSPAN); // https://html.spec.whatwg.org/multipage/#dom-tdth-colspan - make_uint_setter!(SetColSpan, "colspan", DEFAULT_COLSPAN); + // > The colSpan IDL attribute must reflect the colspan content attribute. It is clamped to + // > the range [1, 1000], and its default value is 1. + make_clamped_uint_setter!(SetColSpan, "colspan", 1, 1000, 1); // https://html.spec.whatwg.org/multipage/#dom-tdth-rowspan make_uint_getter!(RowSpan, "rowspan", DEFAULT_ROWSPAN); // https://html.spec.whatwg.org/multipage/#dom-tdth-rowspan - make_uint_setter!(SetRowSpan, "rowspan", DEFAULT_ROWSPAN); + // > The rowSpan IDL attribute must reflect the rowspan content attribute. It is clamped to + // > the range [0, 65534], and its default value is 1. + make_clamped_uint_setter!(SetRowSpan, "rowspan", 0, 65534, 1); // https://html.spec.whatwg.org/multipage/#dom-tdth-bgcolor make_getter!(BgColor, "bgcolor"); @@ -174,23 +178,26 @@ impl VirtualMethods for HTMLTableCellElement { match *local_name { local_name!("colspan") => { let mut attr = AttrValue::from_u32(value.into(), DEFAULT_COLSPAN); - if let AttrValue::UInt(_, ref mut val) = attr { - if *val == 0 { - *val = 1; - } + if let AttrValue::UInt(_, ref mut value) = attr { + // From <https://html.spec.whatwg.org/multipage/#dom-tdth-colspan>: + // > The colSpan IDL attribute must reflect the colspan content attribute. It is clamped to + // > the range [1, 1000], and its default value is 1. + *value = (*value).clamp(1, 1000); } attr }, local_name!("rowspan") => { let mut attr = AttrValue::from_u32(value.into(), DEFAULT_ROWSPAN); - if let AttrValue::UInt(_, ref mut val) = attr { - if *val == 0 { - let node = self.upcast::<Node>(); - let doc = node.owner_doc(); - // rowspan = 0 is not supported in quirks mode - if doc.quirks_mode() != QuirksMode::NoQuirks { - *val = 1; - } + if let AttrValue::UInt(_, ref mut value) = attr { + // From <https://html.spec.whatwg.org/multipage/#dom-tdth-rowspan>: + // > The rowSpan IDL attribute must reflect the rowspan content attribute. It is clamped to + // > the range [0, 65534], and its default value is 1. + // Note that rowspan = 0 is not supported in quirks mode. + let document = self.upcast::<Node>().owner_doc(); + if document.quirks_mode() != QuirksMode::NoQuirks { + *value = (*value).clamp(1, 65534); + } else { + *value = (*value).clamp(0, 65534); } } attr diff --git a/components/script/dom/htmltablecolelement.rs b/components/script/dom/htmltablecolelement.rs index c7ad4afd944..9e8eecf1147 100644 --- a/components/script/dom/htmltablecolelement.rs +++ b/components/script/dom/htmltablecolelement.rs @@ -20,8 +20,6 @@ use crate::dom::node::Node; use crate::dom::virtualmethods::VirtualMethods; use crate::script_runtime::CanGc; -const DEFAULT_SPAN: u32 = 1; - #[dom_struct] pub(crate) struct HTMLTableColElement { htmlelement: HTMLElement, @@ -62,9 +60,11 @@ impl HTMLTableColElement { impl HTMLTableColElementMethods<crate::DomTypeHolder> for HTMLTableColElement { // <https://html.spec.whatwg.org/multipage/#attr-col-span> - make_uint_getter!(Span, "span", DEFAULT_SPAN); + make_uint_getter!(Span, "span", 1); // <https://html.spec.whatwg.org/multipage/#attr-col-span> - make_uint_setter!(SetSpan, "span", DEFAULT_SPAN); + // > The span IDL attribute must reflect the content attribute of the same name. It is clamped + // > to the range [1, 1000], and its default value is 1. + make_clamped_uint_setter!(SetSpan, "span", 1, 1000, 1); } pub(crate) trait HTMLTableColElementLayoutHelpers<'dom> { @@ -96,11 +96,12 @@ impl VirtualMethods for HTMLTableColElement { fn parse_plain_attribute(&self, local_name: &LocalName, value: DOMString) -> AttrValue { match *local_name { local_name!("span") => { - let mut attr = AttrValue::from_u32(value.into(), DEFAULT_SPAN); + let mut attr = AttrValue::from_u32(value.into(), 1); if let AttrValue::UInt(_, ref mut val) = attr { - if *val == 0 { - *val = 1; - } + // From <https://html.spec.whatwg.org/multipage/#attr-col-span>: + // > The span IDL attribute must reflect the content attribute of the same name. + // > It is clamped to the range [1, 1000], and its default value is 1. + *val = (*val).clamp(1, 1000); } attr }, diff --git a/components/script/dom/macros.rs b/components/script/dom/macros.rs index c2f5ba37c21..997341984c6 100644 --- a/components/script/dom/macros.rs +++ b/components/script/dom/macros.rs @@ -318,6 +318,26 @@ macro_rules! make_uint_setter( ); #[macro_export] +macro_rules! make_clamped_uint_setter( + ($attr:ident, $htmlname:tt, $min:expr, $max:expr, $default:expr) => ( + fn $attr(&self, value: u32) { + use $crate::dom::bindings::inheritance::Castable; + use $crate::dom::element::Element; + use $crate::dom::values::UNSIGNED_LONG_MAX; + use $crate::script_runtime::CanGc; + let value = if value > UNSIGNED_LONG_MAX { + $default + } else { + value.clamp($min, $max) + }; + + let element = self.upcast::<Element>(); + element.set_uint_attribute(&html5ever::local_name!($htmlname), value, CanGc::note()) + } + ); +); + +#[macro_export] macro_rules! make_limited_uint_setter( ($attr:ident, $htmlname:tt, $default:expr) => ( fn $attr(&self, value: u32) -> $crate::dom::bindings::error::ErrorResult { diff --git a/components/script/dom/node.rs b/components/script/dom/node.rs index 1117eff6d3c..e9d36a01426 100644 --- a/components/script/dom/node.rs +++ b/components/script/dom/node.rs @@ -110,7 +110,7 @@ use crate::dom::pointerevent::{PointerEvent, PointerId}; use crate::dom::processinginstruction::ProcessingInstruction; use crate::dom::range::WeakRangeVec; use crate::dom::raredata::NodeRareData; -use crate::dom::servoparser::serialize_html_fragment; +use crate::dom::servoparser::{ServoParser, serialize_html_fragment}; use crate::dom::shadowroot::{IsUserAgentWidget, LayoutShadowRootHelpers, ShadowRoot}; use crate::dom::stylesheetlist::StyleSheetListOwner; use crate::dom::svgsvgelement::{LayoutSVGSVGElementHelpers, SVGSVGElement}; @@ -316,6 +316,34 @@ impl Node { } } + /// Implements the "unsafely set HTML" algorithm as specified in: + /// <https://html.spec.whatwg.org/multipage/#concept-unsafely-set-html> + pub fn unsafely_set_html( + target: &Node, + context_element: &Element, + html: DOMString, + can_gc: CanGc, + ) { + // Step 1. Let newChildren be the result of the HTML fragment parsing algorithm. + let new_children = ServoParser::parse_html_fragment(context_element, html, true, can_gc); + + // Step 2. Let fragment be a new DocumentFragment whose node document is contextElement's node document. + + let context_document = context_element.owner_document(); + let fragment = DocumentFragment::new(&context_document, can_gc); + + // Step 3. For each node in newChildren, append node to fragment. + for child in new_children { + fragment + .upcast::<Node>() + .AppendChild(&child, can_gc) + .unwrap(); + } + + // Step 4. Replace all with fragment within target. + Node::replace_all(Some(fragment.upcast()), target, can_gc); + } + pub(crate) fn clean_up_style_and_layout_data(&self) { self.owner_doc().cancel_animations_for_node(self); self.style_data.borrow_mut().take(); @@ -1283,6 +1311,21 @@ impl Node { is_shadow_host, shadow_root_mode, display, + doctype_name: self + .downcast::<DocumentType>() + .map(DocumentType::name) + .cloned() + .map(String::from), + doctype_public_identifier: self + .downcast::<DocumentType>() + .map(DocumentType::public_id) + .cloned() + .map(String::from), + doctype_system_identifier: self + .downcast::<DocumentType>() + .map(DocumentType::system_id) + .cloned() + .map(String::from), } } diff --git a/components/script/dom/nodelist.rs b/components/script/dom/nodelist.rs index b349f16a986..1ec2dc3f78b 100644 --- a/components/script/dom/nodelist.rs +++ b/components/script/dom/nodelist.rs @@ -175,7 +175,6 @@ impl NodeList { #[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)] pub(crate) struct ChildrenList { node: Dom<Node>, - #[ignore_malloc_size_of = "Defined in rust-mozjs"] last_visited: MutNullableDom<Node>, last_index: Cell<u32>, } diff --git a/components/script/dom/offscreencanvas.rs b/components/script/dom/offscreencanvas.rs index aabe5955e12..9947d35f4e0 100644 --- a/components/script/dom/offscreencanvas.rs +++ b/components/script/dom/offscreencanvas.rs @@ -9,9 +9,10 @@ use euclid::default::Size2D; use js::rust::{HandleObject, HandleValue}; use snapshot::Snapshot; +use crate::canvas_context::{CanvasContext, OffscreenRenderingContext}; use crate::dom::bindings::cell::{DomRefCell, Ref, ref_filter_map}; use crate::dom::bindings::codegen::Bindings::OffscreenCanvasBinding::{ - OffscreenCanvasMethods, OffscreenRenderingContext, + OffscreenCanvasMethods, OffscreenRenderingContext as RootedOffscreenRenderingContext, }; use crate::dom::bindings::error::{Error, Fallible}; use crate::dom::bindings::reflector::{DomGlobal, reflect_dom_object_with_proto}; @@ -23,20 +24,12 @@ use crate::dom::htmlcanvaselement::HTMLCanvasElement; use crate::dom::offscreencanvasrenderingcontext2d::OffscreenCanvasRenderingContext2D; use crate::script_runtime::{CanGc, JSContext}; -#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)] -#[derive(Clone, JSTraceable, MallocSizeOf)] -pub(crate) enum OffscreenCanvasContext { - OffscreenContext2d(Dom<OffscreenCanvasRenderingContext2D>), - //WebGL(Dom<WebGLRenderingContext>), - //WebGL2(Dom<WebGL2RenderingContext>), -} - #[dom_struct] pub(crate) struct OffscreenCanvas { eventtarget: EventTarget, width: Cell<u64>, height: Cell<u64>, - context: DomRefCell<Option<OffscreenCanvasContext>>, + context: DomRefCell<Option<OffscreenRenderingContext>>, placeholder: Option<Dom<HTMLCanvasElement>>, } @@ -77,20 +70,18 @@ impl OffscreenCanvas { pub(crate) fn origin_is_clean(&self) -> bool { match *self.context.borrow() { - Some(OffscreenCanvasContext::OffscreenContext2d(ref context)) => { - context.origin_is_clean() - }, + Some(ref context) => context.origin_is_clean(), _ => true, } } - pub(crate) fn context(&self) -> Option<Ref<OffscreenCanvasContext>> { + pub(crate) fn context(&self) -> Option<Ref<OffscreenRenderingContext>> { ref_filter_map(self.context.borrow(), |ctx| ctx.as_ref()) } pub(crate) fn get_image_data(&self) -> Option<Snapshot> { match self.context.borrow().as_ref() { - Some(OffscreenCanvasContext::OffscreenContext2d(context)) => context.get_image_data(), + Some(context) => context.get_image_data(), None => { let size = self.get_size(); if size.width == 0 || size.height == 0 { @@ -108,13 +99,13 @@ impl OffscreenCanvas { ) -> Option<DomRoot<OffscreenCanvasRenderingContext2D>> { if let Some(ctx) = self.context() { return match *ctx { - OffscreenCanvasContext::OffscreenContext2d(ref ctx) => Some(DomRoot::from_ref(ctx)), + OffscreenRenderingContext::Context2d(ref ctx) => Some(DomRoot::from_ref(ctx)), }; } let context = OffscreenCanvasRenderingContext2D::new(&self.global(), self, can_gc); - *self.context.borrow_mut() = Some(OffscreenCanvasContext::OffscreenContext2d( - Dom::from_ref(&*context), - )); + *self.context.borrow_mut() = Some(OffscreenRenderingContext::Context2d(Dom::from_ref( + &*context, + ))); Some(context) } @@ -125,19 +116,6 @@ impl OffscreenCanvas { pub(crate) fn placeholder(&self) -> Option<&HTMLCanvasElement> { self.placeholder.as_deref() } - - pub(crate) fn resize(&self, size: Size2D<u64>) { - self.width.set(size.width); - self.height.set(size.height); - - if let Some(canvas_context) = self.context() { - match &*canvas_context { - OffscreenCanvasContext::OffscreenContext2d(rendering_context) => { - rendering_context.set_canvas_bitmap_dimensions(self.get_size()); - }, - } - } - } } impl OffscreenCanvasMethods<crate::DomTypeHolder> for OffscreenCanvas { @@ -160,11 +138,11 @@ impl OffscreenCanvasMethods<crate::DomTypeHolder> for OffscreenCanvas { id: DOMString, _options: HandleValue, can_gc: CanGc, - ) -> Fallible<Option<OffscreenRenderingContext>> { + ) -> Fallible<Option<RootedOffscreenRenderingContext>> { match &*id { "2d" => Ok(self .get_or_init_2d_context(can_gc) - .map(OffscreenRenderingContext::OffscreenCanvasRenderingContext2D)), + .map(RootedOffscreenRenderingContext::OffscreenCanvasRenderingContext2D)), /*"webgl" | "experimental-webgl" => self .get_or_init_webgl_context(cx, options) .map(OffscreenRenderingContext::WebGLRenderingContext), @@ -187,11 +165,7 @@ impl OffscreenCanvasMethods<crate::DomTypeHolder> for OffscreenCanvas { self.width.set(value); if let Some(canvas_context) = self.context() { - match &*canvas_context { - OffscreenCanvasContext::OffscreenContext2d(rendering_context) => { - rendering_context.set_canvas_bitmap_dimensions(self.get_size()); - }, - } + canvas_context.resize(); } if let Some(canvas) = &self.placeholder { @@ -209,11 +183,7 @@ impl OffscreenCanvasMethods<crate::DomTypeHolder> for OffscreenCanvas { self.height.set(value); if let Some(canvas_context) = self.context() { - match &*canvas_context { - OffscreenCanvasContext::OffscreenContext2d(rendering_context) => { - rendering_context.set_canvas_bitmap_dimensions(self.get_size()); - }, - } + canvas_context.resize(); } if let Some(canvas) = &self.placeholder { diff --git a/components/script/dom/offscreencanvasrenderingcontext2d.rs b/components/script/dom/offscreencanvasrenderingcontext2d.rs index b2d0f3201ca..d7ca0e9dc4d 100644 --- a/components/script/dom/offscreencanvasrenderingcontext2d.rs +++ b/components/script/dom/offscreencanvasrenderingcontext2d.rs @@ -3,11 +3,10 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ use crate::dom::bindings::codegen::GenericBindings::CanvasRenderingContext2DBinding::CanvasRenderingContext2D_Binding::CanvasRenderingContext2DMethods; -use crate::canvas_context::CanvasContext as _; +use crate::canvas_context::CanvasContext; use crate::dom::bindings::codegen::UnionTypes::HTMLCanvasElementOrOffscreenCanvas; use canvas_traits::canvas::Canvas2dMsg; use dom_struct::dom_struct; -use euclid::default::Size2D; use snapshot::Snapshot; use crate::dom::bindings::codegen::Bindings::CanvasRenderingContext2DBinding::{ @@ -64,21 +63,33 @@ impl OffscreenCanvasRenderingContext2D { reflect_dom_object(boxed, global, can_gc) } - pub(crate) fn set_canvas_bitmap_dimensions(&self, size: Size2D<u64>) { - self.context.set_canvas_bitmap_dimensions(size.cast()); - } - pub(crate) fn send_canvas_2d_msg(&self, msg: Canvas2dMsg) { self.context.send_canvas_2d_msg(msg) } +} - pub(crate) fn origin_is_clean(&self) -> bool { - self.context.origin_is_clean() +impl CanvasContext for OffscreenCanvasRenderingContext2D { + type ID = <CanvasRenderingContext2D as CanvasContext>::ID; + + fn context_id(&self) -> Self::ID { + self.context.context_id() + } + + fn canvas(&self) -> HTMLCanvasElementOrOffscreenCanvas { + self.context.canvas() + } + + fn resize(&self) { + self.context.resize() } - pub(crate) fn get_image_data(&self) -> Option<Snapshot> { + fn get_image_data(&self) -> Option<Snapshot> { self.context.get_image_data() } + + fn origin_is_clean(&self) -> bool { + self.context.origin_is_clean() + } } impl OffscreenCanvasRenderingContext2DMethods<crate::DomTypeHolder> diff --git a/components/script/dom/shadowroot.rs b/components/script/dom/shadowroot.rs index 72b074ed6f4..14d9c24b10e 100644 --- a/components/script/dom/shadowroot.rs +++ b/components/script/dom/shadowroot.rs @@ -453,6 +453,15 @@ impl ShadowRootMethods<crate::DomTypeHolder> for ShadowRoot { self.slot_assignment_mode } + /// <https://html.spec.whatwg.org/multipage/#dom-shadowroot-sethtmlunsafe> + fn SetHTMLUnsafe(&self, html: DOMString, can_gc: CanGc) { + // Step 2. Unsafely set HTMl given this, this's shadow host, and complaintHTML + let target = self.upcast::<Node>(); + let context_element = self.Host(); + + Node::unsafely_set_html(target, &context_element, html, can_gc); + } + // https://dom.spec.whatwg.org/#dom-shadowroot-onslotchange event_handler!(onslotchange, GetOnslotchange, SetOnslotchange); } diff --git a/components/script/dom/webgl2renderingcontext.rs b/components/script/dom/webgl2renderingcontext.rs index 416454d8719..5e538b53b5f 100644 --- a/components/script/dom/webgl2renderingcontext.rs +++ b/components/script/dom/webgl2renderingcontext.rs @@ -22,10 +22,10 @@ use js::jsval::{BooleanValue, DoubleValue, Int32Value, NullValue, ObjectValue, U use js::rust::{CustomAutoRooterGuard, HandleObject, MutableHandleValue}; use js::typedarray::{ArrayBufferView, CreateWith, Float32, Int32Array, Uint32, Uint32Array}; use script_bindings::interfaces::WebGL2RenderingContextHelpers; -use script_layout_interface::HTMLCanvasDataSource; use servo_config::pref; use snapshot::Snapshot; use url::Host; +use webrender_api::ImageKey; use crate::canvas_context::CanvasContext; use crate::dom::bindings::codegen::Bindings::WebGL2RenderingContextBinding::{ @@ -4702,7 +4702,7 @@ impl WebGL2RenderingContextMethods<crate::DomTypeHolder> for WebGL2RenderingCont impl LayoutCanvasRenderingContextHelpers for LayoutDom<'_, WebGL2RenderingContext> { #[allow(unsafe_code)] - fn canvas_data_source(self) -> HTMLCanvasDataSource { + fn canvas_data_source(self) -> Option<ImageKey> { let this = self.unsafe_get(); unsafe { (*this.base.to_layout().unsafe_get()).layout_handle() } } diff --git a/components/script/dom/webglrenderingcontext.rs b/components/script/dom/webglrenderingcontext.rs index 9996a3cf504..98170f9655b 100644 --- a/components/script/dom/webglrenderingcontext.rs +++ b/components/script/dom/webglrenderingcontext.rs @@ -31,7 +31,6 @@ use js::typedarray::{ }; use net_traits::image_cache::ImageResponse; use pixels::{self, PixelFormat}; -use script_layout_interface::HTMLCanvasDataSource; use serde::{Deserialize, Serialize}; use servo_config::pref; use snapshot::Snapshot; @@ -875,9 +874,8 @@ impl WebGLRenderingContext { receiver.recv().unwrap() } - pub(crate) fn layout_handle(&self) -> HTMLCanvasDataSource { - let image_key = self.webrender_image; - HTMLCanvasDataSource::WebGL(image_key) + pub(crate) fn layout_handle(&self) -> Option<ImageKey> { + Some(self.webrender_image) } // https://www.khronos.org/registry/webgl/extensions/ANGLE_instanced_arrays/ @@ -4829,7 +4827,7 @@ impl WebGLRenderingContextMethods<crate::DomTypeHolder> for WebGLRenderingContex } impl LayoutCanvasRenderingContextHelpers for LayoutDom<'_, WebGLRenderingContext> { - fn canvas_data_source(self) -> HTMLCanvasDataSource { + fn canvas_data_source(self) -> Option<ImageKey> { (*self.unsafe_get()).layout_handle() } } diff --git a/components/script/dom/webgpu/gpucanvascontext.rs b/components/script/dom/webgpu/gpucanvascontext.rs index c81f96f651f..359b1b14003 100644 --- a/components/script/dom/webgpu/gpucanvascontext.rs +++ b/components/script/dom/webgpu/gpucanvascontext.rs @@ -8,7 +8,6 @@ use std::cell::RefCell; use arrayvec::ArrayVec; use dom_struct::dom_struct; use ipc_channel::ipc::{self}; -use script_layout_interface::HTMLCanvasDataSource; use snapshot::Snapshot; use webgpu_traits::{ ContextConfiguration, PRESENTATION_BUFFER_COUNT, WebGPU, WebGPUContextId, WebGPURequest, @@ -227,11 +226,11 @@ impl GPUCanvasContext { // Internal helper methods impl GPUCanvasContext { - fn layout_handle(&self) -> HTMLCanvasDataSource { + fn layout_handle(&self) -> Option<ImageKey> { if self.drawing_buffer.borrow().cleared { - HTMLCanvasDataSource::Empty + None } else { - HTMLCanvasDataSource::WebGPU(self.webrender_image) + Some(self.webrender_image) } } @@ -301,7 +300,7 @@ impl CanvasContext for GPUCanvasContext { } impl LayoutCanvasRenderingContextHelpers for LayoutDom<'_, GPUCanvasContext> { - fn canvas_data_source(self) -> HTMLCanvasDataSource { + fn canvas_data_source(self) -> Option<ImageKey> { (*self.unsafe_get()).layout_handle() } } diff --git a/components/script/dom/window.rs b/components/script/dom/window.rs index a685bbb25f2..90782e217b7 100644 --- a/components/script/dom/window.rs +++ b/components/script/dom/window.rs @@ -1246,7 +1246,7 @@ impl WindowMethods<crate::DomTypeHolder> for Window { let rv = jsval_to_webdriver(cx, &self.globalscope, val, realm, can_gc); let opt_chan = self.webdriver_script_chan.borrow_mut().take(); if let Some(chan) = opt_chan { - chan.send(rv).unwrap(); + let _ = chan.send(rv); } } @@ -1255,9 +1255,9 @@ impl WindowMethods<crate::DomTypeHolder> for Window { let opt_chan = self.webdriver_script_chan.borrow_mut().take(); if let Some(chan) = opt_chan { if let Ok(rv) = rv { - chan.send(Err(WebDriverJSError::JSException(rv))).unwrap(); + let _ = chan.send(Err(WebDriverJSError::JSException(rv))); } else { - chan.send(rv).unwrap(); + let _ = chan.send(rv); } } } @@ -1265,7 +1265,7 @@ impl WindowMethods<crate::DomTypeHolder> for Window { fn WebdriverTimeout(&self) { let opt_chan = self.webdriver_script_chan.borrow_mut().take(); if let Some(chan) = opt_chan { - chan.send(Err(WebDriverJSError::Timeout)).unwrap(); + let _ = chan.send(Err(WebDriverJSError::Timeout)); } } diff --git a/components/script/script_module.rs b/components/script/script_module.rs index c7697adeea6..0aa35a2eda8 100644 --- a/components/script/script_module.rs +++ b/components/script/script_module.rs @@ -1369,7 +1369,7 @@ pub(crate) unsafe extern "C" fn host_import_module_dynamically( true } -#[derive(Clone, JSTraceable, MallocSizeOf)] +#[derive(Clone, Debug, JSTraceable, MallocSizeOf)] /// <https://html.spec.whatwg.org/multipage/#script-fetch-options> pub(crate) struct ScriptFetchOptions { #[no_trace] @@ -1763,7 +1763,8 @@ fn fetch_single_module_script( .mode(mode) .insecure_requests_policy(global.insecure_requests_policy()) .has_trustworthy_ancestor_origin(global.has_trustworthy_ancestor_origin()) - .policy_container(global.policy_container().to_owned()); + .policy_container(global.policy_container().to_owned()) + .cryptographic_nonce_metadata(options.cryptographic_nonce.clone()); let context = Arc::new(Mutex::new(ModuleContext { owner, diff --git a/components/script_bindings/codegen/Bindings.conf b/components/script_bindings/codegen/Bindings.conf index 4ab0b21cabe..6396e3ced0c 100644 --- a/components/script_bindings/codegen/Bindings.conf +++ b/components/script_bindings/codegen/Bindings.conf @@ -579,7 +579,7 @@ DOMInterfaces = { }, 'ShadowRoot': { - 'canGc': ['ElementFromPoint', 'ElementsFromPoint', 'SetInnerHTML', 'GetHTML', 'InnerHTML'], + 'canGc': ['SetHTMLUnsafe', 'ElementFromPoint', 'ElementsFromPoint', 'SetInnerHTML', 'GetHTML', 'InnerHTML'], }, 'StaticRange': { diff --git a/components/script_bindings/webidls/ShadowRoot.webidl b/components/script_bindings/webidls/ShadowRoot.webidl index 6e3f2032294..cb0926afc10 100644 --- a/components/script_bindings/webidls/ShadowRoot.webidl +++ b/components/script_bindings/webidls/ShadowRoot.webidl @@ -25,7 +25,7 @@ ShadowRoot includes DocumentOrShadowRoot; // https://html.spec.whatwg.org/multipage/#dom-parsing-and-serialization partial interface ShadowRoot { - // [CEReactions] undefined setHTMLUnsafe((TrustedHTML or DOMString) html); + [CEReactions] undefined setHTMLUnsafe(DOMString html); DOMString getHTML(optional GetHTMLOptions options = {}); // [CEReactions] attribute (TrustedHTML or [LegacyNullToEmptyString] DOMString) innerHTML; diff --git a/components/servo/lib.rs b/components/servo/lib.rs index 6f39e773e4e..366685e1123 100644 --- a/components/servo/lib.rs +++ b/components/servo/lib.rs @@ -120,6 +120,7 @@ pub use {bluetooth, bluetooth_traits}; use crate::proxies::ConstellationProxy; use crate::responders::ServoErrorChannel; pub use crate::servo_delegate::{ServoDelegate, ServoError}; +use crate::webrender_api::FrameReadyParams; pub use crate::webview::{WebView, WebViewBuilder}; pub use crate::webview_delegate::{ AllowOrDenyRequest, AuthenticationRequest, FormControl, NavigationRequest, PermissionRequest, @@ -233,14 +234,13 @@ impl webrender_api::RenderNotifier for RenderNotifier { fn new_frame_ready( &self, document_id: DocumentId, - _scrolled: bool, - composite_needed: bool, - _frame_publish_id: FramePublishId, + _: FramePublishId, + frame_ready_params: &FrameReadyParams, ) { self.compositor_proxy .send(CompositorMsg::NewWebRenderFrameReady( document_id, - composite_needed, + frame_ready_params.render, )); } } diff --git a/components/shared/devtools/lib.rs b/components/shared/devtools/lib.rs index 0cf99d22658..c07f4529073 100644 --- a/components/shared/devtools/lib.rs +++ b/components/shared/devtools/lib.rs @@ -144,6 +144,15 @@ pub struct NodeInfo { pub shadow_root_mode: Option<ShadowRootMode>, pub is_shadow_host: bool, pub display: Option<String>, + + /// The `DOCTYPE` name if this is a `DocumentType` node, `None` otherwise + pub doctype_name: Option<String>, + + /// The `DOCTYPE` public identifier if this is a `DocumentType` node , `None` otherwise + pub doctype_public_identifier: Option<String>, + + /// The `DOCTYPE` system identifier if this is a `DocumentType` node, `None` otherwise + pub doctype_system_identifier: Option<String>, } pub struct StartedTimelineMarker { diff --git a/components/shared/script_layout/lib.rs b/components/shared/script_layout/lib.rs index a40b8c403c1..66baccd5147 100644 --- a/components/shared/script_layout/lib.rs +++ b/components/shared/script_layout/lib.rs @@ -117,16 +117,8 @@ pub enum LayoutElementType { SVGSVGElement, } -pub enum HTMLCanvasDataSource { - WebGL(ImageKey), - Image(ImageKey), - WebGPU(ImageKey), - /// transparent black - Empty, -} - pub struct HTMLCanvasData { - pub source: HTMLCanvasDataSource, + pub source: Option<ImageKey>, pub width: u32, pub height: u32, } diff --git a/components/webdriver_server/actions.rs b/components/webdriver_server/actions.rs index f69a09a2577..fbede5b5887 100644 --- a/components/webdriver_server/actions.rs +++ b/components/webdriver_server/actions.rs @@ -76,7 +76,7 @@ fn compute_tick_duration(tick_actions: &ActionSequence) -> u64 { } }, ActionsType::Key { actions: _ } => (), - ActionsType::Wheel { .. } => todo!("Not implemented."), + ActionsType::Wheel { .. } => log::error!("not implemented"), } duration } @@ -176,7 +176,10 @@ impl Handler { } } }, - ActionsType::Wheel { .. } => todo!("Not implemented."), + ActionsType::Wheel { .. } => { + log::error!("not yet implemented"); + return Err(ErrorStatus::UnsupportedOperation); + }, } Ok(()) diff --git a/components/webdriver_server/capabilities.rs b/components/webdriver_server/capabilities.rs index 477a3bfd34c..32596f5275a 100644 --- a/components/webdriver_server/capabilities.rs +++ b/components/webdriver_server/capabilities.rs @@ -4,7 +4,7 @@ use serde_json::{Map, Value}; use webdriver::capabilities::{BrowserCapabilities, Capabilities}; -use webdriver::error::{WebDriverError, WebDriverResult}; +use webdriver::error::{ErrorStatus, WebDriverError, WebDriverResult}; pub struct ServoCapabilities { pub browser_name: String, @@ -79,42 +79,42 @@ impl BrowserCapabilities for ServoCapabilities { &mut self, _: &serde_json::Map<std::string::String, Value>, ) -> Result<bool, WebDriverError> { - todo!() + Err(WebDriverError::new(ErrorStatus::UnsupportedOperation, "")) } fn webauthn_virtual_authenticators( &mut self, _: &serde_json::Map<std::string::String, Value>, ) -> Result<bool, WebDriverError> { - todo!() + Err(WebDriverError::new(ErrorStatus::UnsupportedOperation, "")) } fn webauthn_extension_uvm( &mut self, _: &serde_json::Map<std::string::String, Value>, ) -> Result<bool, WebDriverError> { - todo!() + Err(WebDriverError::new(ErrorStatus::UnsupportedOperation, "")) } fn webauthn_extension_prf( &mut self, _: &serde_json::Map<std::string::String, Value>, ) -> Result<bool, WebDriverError> { - todo!() + Err(WebDriverError::new(ErrorStatus::UnsupportedOperation, "")) } fn webauthn_extension_large_blob( &mut self, _: &serde_json::Map<std::string::String, Value>, ) -> Result<bool, WebDriverError> { - todo!() + Err(WebDriverError::new(ErrorStatus::UnsupportedOperation, "")) } fn webauthn_extension_cred_blob( &mut self, _: &serde_json::Map<std::string::String, Value>, ) -> Result<bool, WebDriverError> { - todo!() + Err(WebDriverError::new(ErrorStatus::UnsupportedOperation, "")) } } diff --git a/components/webdriver_server/lib.rs b/components/webdriver_server/lib.rs index ce83a8f3cc1..d003ebf8adb 100644 --- a/components/webdriver_server/lib.rs +++ b/components/webdriver_server/lib.rs @@ -29,7 +29,7 @@ use embedder_traits::{ use euclid::{Rect, Size2D}; use http::method::Method; use image::{DynamicImage, ImageFormat, RgbaImage}; -use ipc_channel::ipc::{self, IpcSender}; +use ipc_channel::ipc::{self, IpcReceiver, IpcSender}; use ipc_channel::router::ROUTER; use keyboard_types::webdriver::send_keys; use log::{debug, info}; @@ -678,7 +678,7 @@ impl Handler { let (sender, receiver) = ipc::channel().unwrap(); self.top_level_script_command(WebDriverScriptCommand::GetUrl(sender))?; - let url = receiver.recv().unwrap(); + let url = wait_for_script_response(receiver)?; Ok(WebDriverResponse::Generic(ValueResponse( serde_json::to_value(url.as_str())?, @@ -694,7 +694,7 @@ impl Handler { .send(EmbedderToConstellationMessage::WebDriverCommand(cmd_msg)) .unwrap(); - let window_size = receiver.recv().unwrap(); + let window_size = wait_for_script_response(receiver)?; let window_size_response = WindowRectResponse { x: 0, y: 0, @@ -738,7 +738,7 @@ impl Handler { .unwrap(); }); - let window_size = receiver.recv().unwrap(); + let window_size = wait_for_script_response(receiver)?; let window_size_response = WindowRectResponse { x: 0, y: 0, @@ -756,7 +756,7 @@ impl Handler { sender, ))?; - match receiver.recv().unwrap() { + match wait_for_script_response(receiver)? { Ok(is_enabled) => Ok(WebDriverResponse::Generic(ValueResponse( serde_json::to_value(is_enabled)?, ))), @@ -772,7 +772,7 @@ impl Handler { sender, ))?; - match receiver.recv().unwrap() { + match wait_for_script_response(receiver)? { Ok(is_selected) => Ok(WebDriverResponse::Generic(ValueResponse( serde_json::to_value(is_selected)?, ))), @@ -812,7 +812,7 @@ impl Handler { self.top_level_script_command(WebDriverScriptCommand::GetTitle(sender))?; - let value = receiver.recv().unwrap(); + let value = wait_for_script_response(receiver)?; Ok(WebDriverResponse::Generic(ValueResponse( serde_json::to_value(value)?, ))) @@ -874,7 +874,7 @@ impl Handler { }, } - match receiver.recv().unwrap() { + match wait_for_script_response(receiver)? { Ok(value) => { let value_resp = serde_json::to_value( value.map(|x| serde_json::to_value(WebElement(x)).unwrap()), @@ -1005,7 +1005,7 @@ impl Handler { let cmd = WebDriverScriptCommand::GetBrowsingContextId(frame_id, sender); self.browsing_context_script_command(cmd)?; - match receiver.recv().unwrap() { + match wait_for_script_response(receiver)? { Ok(browsing_context_id) => { self.session_mut()?.browsing_context_id = browsing_context_id; Ok(WebDriverResponse::Void) @@ -1047,7 +1047,7 @@ impl Handler { }, } - match receiver.recv().unwrap() { + match wait_for_script_response(receiver)? { Ok(value) => { let resp_value: Vec<Value> = value .into_iter() @@ -1103,7 +1103,7 @@ impl Handler { }, } - match receiver.recv().unwrap() { + match wait_for_script_response(receiver)? { Ok(value) => { let value_resp = serde_json::to_value( value.map(|x| serde_json::to_value(WebElement(x)).unwrap()), @@ -1156,7 +1156,7 @@ impl Handler { }, } - match receiver.recv().unwrap() { + match wait_for_script_response(receiver)? { Ok(value) => { let resp_value: Vec<Value> = value .into_iter() @@ -1175,7 +1175,7 @@ impl Handler { let (sender, receiver) = ipc::channel().unwrap(); let cmd = WebDriverScriptCommand::GetElementRect(element.to_string(), sender); self.browsing_context_script_command(cmd)?; - match receiver.recv().unwrap() { + match wait_for_script_response(receiver)? { Ok(rect) => { let response = ElementRectResponse { x: rect.origin.x, @@ -1193,7 +1193,7 @@ impl Handler { let (sender, receiver) = ipc::channel().unwrap(); let cmd = WebDriverScriptCommand::GetElementText(element.to_string(), sender); self.browsing_context_script_command(cmd)?; - match receiver.recv().unwrap() { + match wait_for_script_response(receiver)? { Ok(value) => Ok(WebDriverResponse::Generic(ValueResponse( serde_json::to_value(value)?, ))), @@ -1205,9 +1205,7 @@ impl Handler { let (sender, receiver) = ipc::channel().unwrap(); let cmd = WebDriverScriptCommand::GetActiveElement(sender); self.browsing_context_script_command(cmd)?; - let value = receiver - .recv() - .unwrap() + let value = wait_for_script_response(receiver)? .map(|x| serde_json::to_value(WebElement(x)).unwrap()); Ok(WebDriverResponse::Generic(ValueResponse( serde_json::to_value(value)?, @@ -1218,7 +1216,7 @@ impl Handler { let (sender, receiver) = ipc::channel().unwrap(); let cmd = WebDriverScriptCommand::GetComputedRole(element.to_string(), sender); self.browsing_context_script_command(cmd)?; - match receiver.recv().unwrap() { + match wait_for_script_response(receiver)? { Ok(value) => Ok(WebDriverResponse::Generic(ValueResponse( serde_json::to_value(value)?, ))), @@ -1230,7 +1228,7 @@ impl Handler { let (sender, receiver) = ipc::channel().unwrap(); let cmd = WebDriverScriptCommand::GetElementTagName(element.to_string(), sender); self.browsing_context_script_command(cmd)?; - match receiver.recv().unwrap() { + match wait_for_script_response(receiver)? { Ok(value) => Ok(WebDriverResponse::Generic(ValueResponse( serde_json::to_value(value)?, ))), @@ -1250,7 +1248,7 @@ impl Handler { sender, ); self.browsing_context_script_command(cmd)?; - match receiver.recv().unwrap() { + match wait_for_script_response(receiver)? { Ok(value) => Ok(WebDriverResponse::Generic(ValueResponse( serde_json::to_value(value)?, ))), @@ -1272,7 +1270,7 @@ impl Handler { ); self.browsing_context_script_command(cmd)?; - match receiver.recv().unwrap() { + match wait_for_script_response(receiver)? { Ok(value) => Ok(WebDriverResponse::Generic(ValueResponse( serde_json::to_value(SendableWebDriverJSValue(value))?, ))), @@ -1289,7 +1287,7 @@ impl Handler { let cmd = WebDriverScriptCommand::GetElementCSS(element.to_string(), name.to_owned(), sender); self.browsing_context_script_command(cmd)?; - match receiver.recv().unwrap() { + match wait_for_script_response(receiver)? { Ok(value) => Ok(WebDriverResponse::Generic(ValueResponse( serde_json::to_value(value)?, ))), @@ -1301,7 +1299,7 @@ impl Handler { let (sender, receiver) = ipc::channel().unwrap(); let cmd = WebDriverScriptCommand::GetCookies(sender); self.browsing_context_script_command(cmd)?; - let cookies = receiver.recv().unwrap(); + let cookies = wait_for_script_response(receiver)?; let response = cookies .into_iter() .map(|cookie| cookie_msg_to_cookie(cookie.into_inner())) @@ -1313,12 +1311,14 @@ impl Handler { let (sender, receiver) = ipc::channel().unwrap(); let cmd = WebDriverScriptCommand::GetCookie(name, sender); self.browsing_context_script_command(cmd)?; - let cookies = receiver.recv().unwrap(); - let response = cookies + let cookies = wait_for_script_response(receiver)?; + let Some(response) = cookies .into_iter() .map(|cookie| cookie_msg_to_cookie(cookie.into_inner())) .next() - .unwrap(); + else { + return Err(WebDriverError::new(ErrorStatus::NoSuchCookie, "")); + }; Ok(WebDriverResponse::Cookie(CookieResponse(response))) } @@ -1342,7 +1342,7 @@ impl Handler { let cmd = WebDriverScriptCommand::AddCookie(cookie_builder.build(), sender); self.browsing_context_script_command(cmd)?; - match receiver.recv().unwrap() { + match wait_for_script_response(receiver)? { Ok(_) => Ok(WebDriverResponse::Void), Err(response) => match response { WebDriverCookieError::InvalidDomain => Err(WebDriverError::new( @@ -1361,7 +1361,7 @@ impl Handler { let (sender, receiver) = ipc::channel().unwrap(); let cmd = WebDriverScriptCommand::DeleteCookie(name, sender); self.browsing_context_script_command(cmd)?; - match receiver.recv().unwrap() { + match wait_for_script_response(receiver)? { Ok(_) => Ok(WebDriverResponse::Void), Err(error) => Err(WebDriverError::new(error, "")), } @@ -1371,7 +1371,7 @@ impl Handler { let (sender, receiver) = ipc::channel().unwrap(); let cmd = WebDriverScriptCommand::DeleteCookies(sender); self.browsing_context_script_command(cmd)?; - match receiver.recv().unwrap() { + match wait_for_script_response(receiver)? { Ok(_) => Ok(WebDriverResponse::Void), Err(error) => Err(WebDriverError::new(error, "")), } @@ -1426,7 +1426,7 @@ impl Handler { let cmd = WebDriverScriptCommand::GetPageSource(sender); self.browsing_context_script_command(cmd)?; - match receiver.recv().unwrap() { + match wait_for_script_response(receiver)? { Ok(source) => Ok(WebDriverResponse::Generic(ValueResponse( serde_json::to_value(source)?, ))), @@ -1487,9 +1487,7 @@ impl Handler { let (sender, receiver) = ipc::channel().unwrap(); let command = WebDriverScriptCommand::ExecuteScript(script, sender); self.browsing_context_script_command(command)?; - let result = receiver - .recv() - .unwrap_or(Err(WebDriverJSError::BrowsingContextNotFound)); + let result = wait_for_script_response(receiver)?; self.postprocess_js_result(result) } @@ -1533,9 +1531,7 @@ impl Handler { let (sender, receiver) = ipc::channel().unwrap(); let command = WebDriverScriptCommand::ExecuteAsyncScript(script, sender); self.browsing_context_script_command(command)?; - let result = receiver - .recv() - .unwrap_or(Err(WebDriverJSError::BrowsingContextNotFound)); + let result = wait_for_script_response(receiver)?; self.postprocess_js_result(result) } @@ -1589,10 +1585,7 @@ impl Handler { .unwrap(); // TODO: distinguish the not found and not focusable cases - receiver - .recv() - .unwrap() - .map_err(|error| WebDriverError::new(error, ""))?; + wait_for_script_response(receiver)?.map_err(|error| WebDriverError::new(error, ""))?; let input_events = send_keys(&keys.text); @@ -1615,7 +1608,7 @@ impl Handler { let command = WebDriverScriptCommand::ElementClick(element.to_string(), sender); self.browsing_context_script_command(command)?; - match receiver.recv().unwrap() { + match wait_for_script_response(receiver)? { Ok(element_id) => match element_id { Some(element_id) => { let id = Uuid::new_v4().to_string(); @@ -1688,7 +1681,7 @@ impl Handler { .send(EmbedderToConstellationMessage::WebDriverCommand(cmd_msg)) .unwrap(); - if let Some(x) = receiver.recv().unwrap() { + if let Some(x) = wait_for_script_response(receiver)? { img = Some(x); break; }; @@ -1739,7 +1732,7 @@ impl Handler { let command = WebDriverScriptCommand::GetBoundingClientRect(element.to_string(), sender); self.browsing_context_script_command(command)?; - match receiver.recv().unwrap() { + match wait_for_script_response(receiver)? { Ok(rect) => { let encoded = self.take_screenshot(Some(Rect::from_untyped(&rect)))?; @@ -1944,3 +1937,12 @@ fn webdriver_value_to_js_argument(v: &Value) -> String { }, } } + +fn wait_for_script_response<T>(receiver: IpcReceiver<T>) -> Result<T, WebDriverError> +where + T: for<'de> Deserialize<'de> + Serialize, +{ + receiver + .recv() + .map_err(|_| WebDriverError::new(ErrorStatus::NoSuchWindow, "")) +} |