diff options
Diffstat (limited to 'components')
57 files changed, 2569 insertions, 1293 deletions
diff --git a/components/canvas/Cargo.toml b/components/canvas/Cargo.toml index 7e7b00efe11..6084fc6e434 100644 --- a/components/canvas/Cargo.toml +++ b/components/canvas/Cargo.toml @@ -11,24 +11,15 @@ rust-version.workspace = true name = "canvas" path = "lib.rs" -[features] -webgl_backtrace = ["canvas_traits/webgl_backtrace"] -webxr = ["dep:webxr", "dep:webxr-api"] - [dependencies] app_units = { workspace = true } -bitflags = { workspace = true } -byteorder = { workspace = true } canvas_traits = { workspace = true } compositing_traits = { workspace = true } crossbeam-channel = { workspace = true } cssparser = { workspace = true } euclid = { workspace = true } -fnv = { workspace = true } font-kit = "0.14" fonts = { path = "../fonts" } -glow = { workspace = true } -half = "2" ipc-channel = { workspace = true } log = { workspace = true } lyon_geom = "1.0.4" @@ -40,9 +31,5 @@ raqote = "0.8.5" servo_arc = { workspace = true } snapshot = { workspace = true } stylo = { workspace = true } -surfman = { workspace = true } unicode-script = { workspace = true } -webrender = { workspace = true } webrender_api = { workspace = true } -webxr = { path = "../webxr", features = ["ipc"], optional = true } -webxr-api = { workspace = true, features = ["ipc"], optional = true } diff --git a/components/canvas/backend.rs b/components/canvas/backend.rs new file mode 100644 index 00000000000..f94636079a6 --- /dev/null +++ b/components/canvas/backend.rs @@ -0,0 +1,242 @@ +/* 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 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 snapshot(&self) -> B::SourceSurface; + 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 snapshot_data(&self) -> &[u8]; +} + +/// 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..0856583cea3 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 { @@ -614,10 +429,10 @@ impl<'a> CanvasData<'a> { SerializableImageData::Raw(IpcSharedMemory::from_bytes(draw_target.snapshot_data())); 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, @@ -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,11 +1286,11 @@ 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(), &Point2D::new(shadow_src_rect.origin.x, shadow_src_rect.origin.y), @@ -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>>, @@ -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 d2c62c1d8b6..91ab58b0e8b 100644 --- a/components/canvas/lib.rs +++ b/components/canvas/lib.rs @@ -4,14 +4,8 @@ #![deny(unsafe_code)] +mod backend; mod raqote_backend; -pub use webgl_mode::WebGLComm; - pub mod canvas_data; pub mod canvas_paint_thread; -mod webgl_limits; -mod webgl_mode; -pub mod webgl_thread; -#[cfg(feature = "webxr")] -mod webxr; diff --git a/components/canvas/raqote_backend.rs b/components/canvas/raqote_backend.rs index 12137e41f41..344d42dec6c 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,84 @@ 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 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 +233,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 +362,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 +374,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 +447,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 +478,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 +498,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 +547,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 +557,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 +568,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 +589,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 snapshot(&self) -> <RaqoteBackend as Backend>::SourceSurface { + self.snapshot_data().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 +627,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,9 +649,9 @@ 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)] @@ -709,7 +670,7 @@ impl Filter { } } -struct PathBuilder(Option<raqote::PathBuilder>); +pub(crate) struct PathBuilder(Option<raqote::PathBuilder>); impl PathBuilder { fn new() -> PathBuilder { @@ -717,7 +678,7 @@ impl PathBuilder { } } -impl GenericPathBuilder for PathBuilder { +impl GenericPathBuilder<RaqoteBackend> for PathBuilder { fn arc( &mut self, origin: Point2D<f32>, @@ -726,7 +687,8 @@ impl GenericPathBuilder for PathBuilder { end_angle: f32, anticlockwise: bool, ) { - self.ellipse( + <PathBuilder as GenericPathBuilder<RaqoteBackend>>::ellipse( + self, origin, radius, radius, @@ -736,6 +698,7 @@ impl GenericPathBuilder for PathBuilder { anticlockwise, ); } + fn bezier_curve_to( &mut self, control_point1: &Point2D<f32>, @@ -751,66 +714,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 +747,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 +771,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 +884,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 +953,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 2175028a81b..ad89c435717 100644 --- a/components/constellation/constellation.rs +++ b/components/constellation/constellation.rs @@ -115,9 +115,10 @@ use constellation_traits::{ AuxiliaryWebViewCreationRequest, AuxiliaryWebViewCreationResponse, BroadcastMsg, DocumentState, EmbedderToConstellationMessage, IFrameLoadInfo, IFrameLoadInfoWithData, IFrameSandboxState, IFrameSizeMsg, Job, LoadData, LoadOrigin, LogEntry, MessagePortMsg, NavigationHistoryBehavior, - PaintMetricEvent, PortMessageTask, SWManagerMsg, SWManagerSenders, ScriptToConstellationChan, - ScriptToConstellationMessage, ScrollState, ServiceWorkerManagerFactory, ServiceWorkerMsg, - StructuredSerializedData, TraversalDirection, WindowSizeType, + PaintMetricEvent, PortMessageTask, PortTransferInfo, SWManagerMsg, SWManagerSenders, + ScriptToConstellationChan, ScriptToConstellationMessage, ScrollState, + ServiceWorkerManagerFactory, ServiceWorkerMsg, StructuredSerializedData, TraversalDirection, + WindowSizeType, }; use crossbeam_channel::{Receiver, Select, Sender, unbounded}; use devtools_traits::{ @@ -127,10 +128,10 @@ use devtools_traits::{ use embedder_traits::resources::{self, Resource}; use embedder_traits::user_content_manager::UserContentManager; use embedder_traits::{ - AnimationState, CompositorHitTestResult, Cursor, EmbedderMsg, EmbedderProxy, ImeEvent, - InputEvent, MediaSessionActionType, MediaSessionEvent, MediaSessionPlaybackState, MouseButton, - MouseButtonAction, MouseButtonEvent, Theme, ViewportDetails, WebDriverCommandMsg, - WebDriverLoadStatus, + AnimationState, CompositorHitTestResult, Cursor, EmbedderMsg, EmbedderProxy, + FocusSequenceNumber, ImeEvent, InputEvent, MediaSessionActionType, MediaSessionEvent, + MediaSessionPlaybackState, MouseButton, MouseButtonAction, MouseButtonEvent, Theme, + ViewportDetails, WebDriverCommandMsg, WebDriverLoadStatus, }; use euclid::Size2D; use euclid::default::Size2D as UntypedSize2D; @@ -202,9 +203,6 @@ enum TransferState { /// While a completion failed, another global requested to complete the transfer. /// We are still buffering messages, and awaiting the return of the buffer from the global who failed. CompletionRequested(MessagePortRouterId, VecDeque<PortMessageTask>), - /// The entangled port has been removed while the port was in-transfer, - /// the current port should be removed as well once it is managed again. - EntangledRemoved, } #[derive(Debug)] @@ -1043,6 +1041,44 @@ where } } + /// Enumerate the specified browsing context's ancestor pipelines up to + /// the top-level pipeline. + fn ancestor_pipelines_of_browsing_context_iter( + &self, + browsing_context_id: BrowsingContextId, + ) -> impl Iterator<Item = &Pipeline> + '_ { + let mut state: Option<PipelineId> = self + .browsing_contexts + .get(&browsing_context_id) + .and_then(|browsing_context| browsing_context.parent_pipeline_id); + std::iter::from_fn(move || { + if let Some(pipeline_id) = state { + let pipeline = self.pipelines.get(&pipeline_id)?; + let browsing_context = self.browsing_contexts.get(&pipeline.browsing_context_id)?; + state = browsing_context.parent_pipeline_id; + Some(pipeline) + } else { + None + } + }) + } + + /// Enumerate the specified browsing context's ancestor-or-self pipelines up + /// to the top-level pipeline. + fn ancestor_or_self_pipelines_of_browsing_context_iter( + &self, + browsing_context_id: BrowsingContextId, + ) -> impl Iterator<Item = &Pipeline> + '_ { + let this_pipeline = self + .browsing_contexts + .get(&browsing_context_id) + .map(|browsing_context| browsing_context.pipeline_id) + .and_then(|pipeline_id| self.pipelines.get(&pipeline_id)); + this_pipeline + .into_iter() + .chain(self.ancestor_pipelines_of_browsing_context_iter(browsing_context_id)) + } + /// Create a new browsing context and update the internal bookkeeping. #[allow(clippy::too_many_arguments)] fn new_browsing_context( @@ -1486,12 +1522,12 @@ where ScriptToConstellationMessage::NewMessagePort(router_id, port_id) => { self.handle_new_messageport(router_id, port_id); }, - ScriptToConstellationMessage::RemoveMessagePort(port_id) => { - self.handle_remove_messageport(port_id); - }, ScriptToConstellationMessage::EntanglePorts(port1, port2) => { self.handle_entangle_messageports(port1, port2); }, + ScriptToConstellationMessage::DisentanglePorts(port1, port2) => { + self.handle_disentangle_messageports(port1, port2); + }, ScriptToConstellationMessage::NewBroadcastChannelRouter( router_id, response_sender, @@ -1621,8 +1657,15 @@ where data, ); }, - ScriptToConstellationMessage::Focus => { - self.handle_focus_msg(source_pipeline_id); + ScriptToConstellationMessage::Focus(focused_child_browsing_context_id, sequence) => { + self.handle_focus_msg( + source_pipeline_id, + focused_child_browsing_context_id, + sequence, + ); + }, + ScriptToConstellationMessage::FocusRemoteDocument(focused_browsing_context_id) => { + self.handle_focus_remote_document_msg(focused_browsing_context_id); }, ScriptToConstellationMessage::SetThrottledComplete(throttled) => { self.handle_set_throttled_complete(source_pipeline_id, throttled); @@ -2072,17 +2115,6 @@ where Entry::Occupied(entry) => entry, }; match entry.get().state { - TransferState::EntangledRemoved => { - // If the entangled port has been removed while this one was in-transfer, - // remove it now. - if let Some(ipc_sender) = self.message_port_routers.get(&router_id) { - let _ = ipc_sender.send(MessagePortMsg::RemoveMessagePort(port_id)); - } else { - warn!("No message-port sender for {:?}", router_id); - } - entry.remove_entry(); - continue; - }, TransferState::CompletionInProgress(expected_router_id) => { // Here, the transfer was normally completed. @@ -2106,9 +2138,9 @@ where fn handle_message_port_transfer_failed( &mut self, - ports: HashMap<MessagePortId, VecDeque<PortMessageTask>>, + ports: HashMap<MessagePortId, PortTransferInfo>, ) { - for (port_id, mut previous_buffer) in ports.into_iter() { + for (port_id, mut transfer_info) in ports.into_iter() { let entry = match self.message_ports.remove(&port_id) { None => { warn!( @@ -2120,11 +2152,6 @@ where Some(entry) => entry, }; let new_info = match entry.state { - TransferState::EntangledRemoved => { - // If the entangled port has been removed while this one was in-transfer, - // just drop it. - continue; - }, TransferState::CompletionFailed(mut current_buffer) => { // The transfer failed, // and now the global has returned us the buffer we previously sent. @@ -2132,7 +2159,7 @@ where // Tasks in the previous buffer are older, // hence need to be added to the front of the current one. - while let Some(task) = previous_buffer.pop_back() { + while let Some(task) = transfer_info.port_message_queue.pop_back() { current_buffer.push_front(task); } // Update the state to transfer-in-progress. @@ -2151,7 +2178,7 @@ where // Tasks in the previous buffer are older, // hence need to be added to the front of the current one. - while let Some(task) = previous_buffer.pop_back() { + while let Some(task) = transfer_info.port_message_queue.pop_back() { current_buffer.push_front(task); } // Forward the buffered message-queue to complete the current transfer. @@ -2159,7 +2186,10 @@ where if ipc_sender .send(MessagePortMsg::CompletePendingTransfer( port_id, - current_buffer, + PortTransferInfo { + port_message_queue: current_buffer, + disentangled: entry.entangled_with.is_none(), + }, )) .is_err() { @@ -2206,18 +2236,14 @@ where Some(entry) => entry, }; let new_info = match entry.state { - TransferState::EntangledRemoved => { - // If the entangled port has been removed while this one was in-transfer, - // remove it now. - if let Some(ipc_sender) = self.message_port_routers.get(&router_id) { - let _ = ipc_sender.send(MessagePortMsg::RemoveMessagePort(port_id)); - } else { - warn!("No message-port sender for {:?}", router_id); - } - continue; - }, TransferState::TransferInProgress(buffer) => { - response.insert(port_id, buffer); + response.insert( + port_id, + PortTransferInfo { + port_message_queue: buffer, + disentangled: entry.entangled_with.is_none(), + }, + ); // If the port was in transfer, and a global is requesting completion, // we note the start of the completion. @@ -2296,10 +2322,6 @@ where TransferState::TransferInProgress(queue) => queue.push_back(task), TransferState::CompletionFailed(queue) => queue.push_back(task), TransferState::CompletionRequested(_, queue) => queue.push_back(task), - TransferState::EntangledRemoved => warn!( - "Messageport received a message, but entangled has alread been removed {:?}", - port_id - ), } } @@ -2365,59 +2387,6 @@ where feature = "tracing", tracing::instrument(skip_all, fields(servo_profiling = true), level = "trace") )] - fn handle_remove_messageport(&mut self, port_id: MessagePortId) { - let entangled = match self.message_ports.remove(&port_id) { - Some(info) => info.entangled_with, - None => { - return warn!( - "Constellation asked to remove unknown messageport {:?}", - port_id - ); - }, - }; - let entangled_id = match entangled { - Some(id) => id, - None => return, - }; - let info = match self.message_ports.get_mut(&entangled_id) { - Some(info) => info, - None => { - return warn!( - "Constellation asked to remove unknown entangled messageport {:?}", - entangled_id - ); - }, - }; - let router_id = match info.state { - TransferState::EntangledRemoved => { - return warn!( - "Constellation asked to remove entangled messageport by a port that was already removed {:?}", - port_id - ); - }, - TransferState::TransferInProgress(_) | - TransferState::CompletionInProgress(_) | - TransferState::CompletionFailed(_) | - TransferState::CompletionRequested(_, _) => { - // Note: since the port is in-transer, we don't have a router to send it a message - // to let it know that its entangled port has been removed. - // Hence we mark it so that it will be messaged and removed once the transfer completes. - info.state = TransferState::EntangledRemoved; - return; - }, - TransferState::Managed(router_id) => router_id, - }; - if let Some(sender) = self.message_port_routers.get(&router_id) { - let _ = sender.send(MessagePortMsg::RemoveMessagePort(entangled_id)); - } else { - warn!("No message-port sender for {:?}", router_id); - } - } - - #[cfg_attr( - feature = "tracing", - tracing::instrument(skip_all, fields(servo_profiling = true), level = "trace") - )] fn handle_entangle_messageports(&mut self, port1: MessagePortId, port2: MessagePortId) { if let Some(info) = self.message_ports.get_mut(&port1) { info.entangled_with = Some(port2); @@ -2437,6 +2406,57 @@ where } } + #[cfg_attr( + feature = "tracing", + tracing::instrument(skip_all, fields(servo_profiling = true), level = "trace") + )] + /// <https://html.spec.whatwg.org/multipage/#disentangle> + fn handle_disentangle_messageports( + &mut self, + port1: MessagePortId, + port2: Option<MessagePortId>, + ) { + // Disentangle initiatorPort and otherPort, + // so that they are no longer entangled or associated with each other. + // Note: If `port2` is some, then this is the first message + // and `port1` is the initiatorPort, `port2` is the otherPort. + // We can immediately remove the initiator. + let _ = self.message_ports.remove(&port1); + + // Note: the none case is when otherPort sent this message + // in response to completing its own local disentanglement. + let Some(port2) = port2 else { + return; + }; + + // Start disentanglement of the other port. + if let Some(info) = self.message_ports.get_mut(&port2) { + info.entangled_with = None; + match &mut info.state { + TransferState::Managed(router_id) | + TransferState::CompletionInProgress(router_id) => { + // We try to disentangle the other port now, + // and if it has been transfered out by the time the message is received, + // it will be ignored, + // and disentanglement will be completed as part of the transfer. + if let Some(ipc_sender) = self.message_port_routers.get(router_id) { + let _ = ipc_sender.send(MessagePortMsg::CompleteDisentanglement(port2)); + } else { + warn!("No message-port sender for {:?}", router_id); + } + }, + _ => { + // Note: the port is in transfer, disentanglement will complete along with it. + }, + } + } else { + warn!( + "Constellation asked to disentangle unknown messageport: {:?}", + port2 + ); + } + } + /// <https://w3c.github.io/ServiceWorker/#schedule-job-algorithm> /// and /// <https://w3c.github.io/ServiceWorker/#dfn-job-queue> @@ -4070,6 +4090,7 @@ where } new_pipeline.set_throttled(false); + self.notify_focus_state(new_pipeline_id); } self.update_activity(old_pipeline_id); @@ -4275,66 +4296,231 @@ where feature = "tracing", tracing::instrument(skip_all, fields(servo_profiling = true), level = "trace") )] - fn handle_focus_msg(&mut self, pipeline_id: PipelineId) { - let (browsing_context_id, webview_id) = match self.pipelines.get(&pipeline_id) { - Some(pipeline) => (pipeline.browsing_context_id, pipeline.webview_id), + fn handle_focus_msg( + &mut self, + pipeline_id: PipelineId, + focused_child_browsing_context_id: Option<BrowsingContextId>, + sequence: FocusSequenceNumber, + ) { + let (browsing_context_id, webview_id) = match self.pipelines.get_mut(&pipeline_id) { + Some(pipeline) => { + pipeline.focus_sequence = sequence; + (pipeline.browsing_context_id, pipeline.webview_id) + }, None => return warn!("{}: Focus parent after closure", pipeline_id), }; + // Ignore if the pipeline isn't fully active. + if self.get_activity(pipeline_id) != DocumentActivity::FullyActive { + debug!( + "Ignoring the focus request because pipeline {} is not \ + fully active", + pipeline_id + ); + return; + } + // Focus the top-level browsing context. self.webviews.focus(webview_id); self.embedder_proxy .send(EmbedderMsg::WebViewFocused(webview_id)); - // Update the webview’s focused browsing context. - match self.webviews.get_mut(webview_id) { - Some(webview) => { - webview.focused_browsing_context_id = browsing_context_id; - }, - None => { - return warn!( - "{}: Browsing context for focus msg does not exist", - webview_id - ); - }, + // If a container with a non-null nested browsing context is focused, + // the nested browsing context's active document becomes the focused + // area of the top-level browsing context instead. + let focused_browsing_context_id = + focused_child_browsing_context_id.unwrap_or(browsing_context_id); + + // Send focus messages to the affected pipelines, except + // `pipeline_id`, which has already its local focus state + // updated. + self.focus_browsing_context(Some(pipeline_id), focused_browsing_context_id); + } + + fn handle_focus_remote_document_msg(&mut self, focused_browsing_context_id: BrowsingContextId) { + let pipeline_id = match self.browsing_contexts.get(&focused_browsing_context_id) { + Some(browsing_context) => browsing_context.pipeline_id, + None => return warn!("Browsing context {} not found", focused_browsing_context_id), }; - // Focus parent iframes recursively - self.focus_parent_pipeline(browsing_context_id); + // Ignore if its active document isn't fully active. + if self.get_activity(pipeline_id) != DocumentActivity::FullyActive { + debug!( + "Ignoring the remote focus request because pipeline {} of \ + browsing context {} is not fully active", + pipeline_id, focused_browsing_context_id, + ); + return; + } + + self.focus_browsing_context(None, focused_browsing_context_id); } + /// Perform [the focusing steps][1] for the active document of + /// `focused_browsing_context_id`. + /// + /// If `initiator_pipeline_id` is specified, this method avoids sending + /// a message to `initiator_pipeline_id`, assuming its local focus state has + /// already been updated. This is necessary for performing the focusing + /// steps for an object that is not the document itself but something that + /// belongs to the document. + /// + /// [1]: https://html.spec.whatwg.org/multipage/#focusing-steps #[cfg_attr( feature = "tracing", tracing::instrument(skip_all, fields(servo_profiling = true), level = "trace") )] - fn focus_parent_pipeline(&mut self, browsing_context_id: BrowsingContextId) { - let parent_pipeline_id = match self.browsing_contexts.get(&browsing_context_id) { - Some(ctx) => ctx.parent_pipeline_id, - None => { - return warn!("{}: Focus parent after closure", browsing_context_id); - }, + fn focus_browsing_context( + &mut self, + initiator_pipeline_id: Option<PipelineId>, + focused_browsing_context_id: BrowsingContextId, + ) { + let webview_id = match self.browsing_contexts.get(&focused_browsing_context_id) { + Some(browsing_context) => browsing_context.top_level_id, + None => return warn!("Browsing context {} not found", focused_browsing_context_id), }; - let parent_pipeline_id = match parent_pipeline_id { - Some(parent_id) => parent_id, + + // Update the webview’s focused browsing context. + let old_focused_browsing_context_id = match self.webviews.get_mut(webview_id) { + Some(browser) => replace( + &mut browser.focused_browsing_context_id, + focused_browsing_context_id, + ), None => { - return debug!("{}: Focus has no parent", browsing_context_id); + return warn!( + "{}: Browsing context for focus msg does not exist", + webview_id + ); }, }; - // Send a message to the parent of the provided browsing context (if it - // exists) telling it to mark the iframe element as focused. - let msg = ScriptThreadMessage::FocusIFrame(parent_pipeline_id, browsing_context_id); - let (result, parent_browsing_context_id) = match self.pipelines.get(&parent_pipeline_id) { - Some(pipeline) => { - let result = pipeline.event_loop.send(msg); - (result, pipeline.browsing_context_id) + // The following part is similar to [the focus update steps][1] except + // that only `Document`s in the given focus chains are considered. It's + // ultimately up to the script threads to fire focus events at the + // affected objects. + // + // [1]: https://html.spec.whatwg.org/multipage/#focus-update-steps + let mut old_focus_chain_pipelines: Vec<&Pipeline> = self + .ancestor_or_self_pipelines_of_browsing_context_iter(old_focused_browsing_context_id) + .collect(); + let mut new_focus_chain_pipelines: Vec<&Pipeline> = self + .ancestor_or_self_pipelines_of_browsing_context_iter(focused_browsing_context_id) + .collect(); + + debug!( + "old_focus_chain_pipelines = {:?}", + old_focus_chain_pipelines + .iter() + .map(|p| p.id.to_string()) + .collect::<Vec<_>>() + ); + debug!( + "new_focus_chain_pipelines = {:?}", + new_focus_chain_pipelines + .iter() + .map(|p| p.id.to_string()) + .collect::<Vec<_>>() + ); + + // At least the last entries should match. Otherwise something is wrong, + // and we don't want to proceed and crash the top-level pipeline by + // sending an impossible `Unfocus` message to it. + match ( + &old_focus_chain_pipelines[..], + &new_focus_chain_pipelines[..], + ) { + ([.., p1], [.., p2]) if p1.id == p2.id => {}, + _ => { + warn!("Aborting the focus operation - focus chain sanity check failed"); + return; }, - None => return warn!("{}: Focus after closure", parent_pipeline_id), - }; - if let Err(e) = result { - self.handle_send_error(parent_pipeline_id, e); } - self.focus_parent_pipeline(parent_browsing_context_id); + + // > If the last entry in `old chain` and the last entry in `new chain` + // > are the same, pop the last entry from `old chain` and the last + // > entry from `new chain` and redo this step. + let mut first_common_pipeline_in_chain = None; + while let ([.., p1], [.., p2]) = ( + &old_focus_chain_pipelines[..], + &new_focus_chain_pipelines[..], + ) { + if p1.id != p2.id { + break; + } + old_focus_chain_pipelines.pop(); + first_common_pipeline_in_chain = new_focus_chain_pipelines.pop(); + } + + let mut send_errors = Vec::new(); + + // > For each entry `entry` in `old chain`, in order, run these + // > substeps: [...] + for &pipeline in old_focus_chain_pipelines.iter() { + if Some(pipeline.id) != initiator_pipeline_id { + let msg = ScriptThreadMessage::Unfocus(pipeline.id, pipeline.focus_sequence); + trace!("Sending {:?} to {}", msg, pipeline.id); + if let Err(e) = pipeline.event_loop.send(msg) { + send_errors.push((pipeline.id, e)); + } + } else { + trace!( + "Not notifying {} - it's the initiator of this focus operation", + pipeline.id + ); + } + } + + // > For each entry entry in `new chain`, in reverse order, run these + // > substeps: [...] + let mut child_browsing_context_id = None; + for &pipeline in new_focus_chain_pipelines.iter().rev() { + // Don't send a message to the browsing context that initiated this + // focus operation. It already knows that it has gotten focus. + if Some(pipeline.id) != initiator_pipeline_id { + let msg = if let Some(child_browsing_context_id) = child_browsing_context_id { + // Focus the container element of `child_browsing_context_id`. + ScriptThreadMessage::FocusIFrame( + pipeline.id, + child_browsing_context_id, + pipeline.focus_sequence, + ) + } else { + // Focus the document. + ScriptThreadMessage::FocusDocument(pipeline.id, pipeline.focus_sequence) + }; + trace!("Sending {:?} to {}", msg, pipeline.id); + if let Err(e) = pipeline.event_loop.send(msg) { + send_errors.push((pipeline.id, e)); + } + } else { + trace!( + "Not notifying {} - it's the initiator of this focus operation", + pipeline.id + ); + } + child_browsing_context_id = Some(pipeline.browsing_context_id); + } + + if let (Some(pipeline), Some(child_browsing_context_id)) = + (first_common_pipeline_in_chain, child_browsing_context_id) + { + if Some(pipeline.id) != initiator_pipeline_id { + // Focus the container element of `child_browsing_context_id`. + let msg = ScriptThreadMessage::FocusIFrame( + pipeline.id, + child_browsing_context_id, + pipeline.focus_sequence, + ); + trace!("Sending {:?} to {}", msg, pipeline.id); + if let Err(e) = pipeline.event_loop.send(msg) { + send_errors.push((pipeline.id, e)); + } + } + } + + for (pipeline_id, e) in send_errors { + self.handle_send_error(pipeline_id, e); + } } #[cfg_attr( @@ -4929,10 +5115,42 @@ where self.trim_history(top_level_id); } + self.notify_focus_state(change.new_pipeline_id); + self.notify_history_changed(change.webview_id); self.update_webview_in_compositor(change.webview_id); } + /// Update the focus state of the specified pipeline that recently became + /// active (thus doesn't have a focused container element) and may have + /// out-dated information. + fn notify_focus_state(&mut self, pipeline_id: PipelineId) { + let pipeline = match self.pipelines.get(&pipeline_id) { + Some(pipeline) => pipeline, + None => return warn!("Pipeline {} is closed", pipeline_id), + }; + + let is_focused = match self.webviews.get(pipeline.webview_id) { + Some(webview) => webview.focused_browsing_context_id == pipeline.browsing_context_id, + None => { + return warn!( + "Pipeline {}'s top-level browsing context {} is closed", + pipeline_id, pipeline.webview_id + ); + }, + }; + + // If the browsing context is focused, focus the document + let msg = if is_focused { + ScriptThreadMessage::FocusDocument(pipeline_id, pipeline.focus_sequence) + } else { + ScriptThreadMessage::Unfocus(pipeline_id, pipeline.focus_sequence) + }; + if let Err(e) = pipeline.event_loop.send(msg) { + self.handle_send_error(pipeline_id, e); + } + } + #[cfg_attr( feature = "tracing", tracing::instrument(skip_all, fields(servo_profiling = true), level = "trace") @@ -5382,7 +5600,29 @@ where None => { warn!("{parent_pipeline_id}: Child closed after parent"); }, - Some(parent_pipeline) => parent_pipeline.remove_child(browsing_context_id), + Some(parent_pipeline) => { + parent_pipeline.remove_child(browsing_context_id); + + // If `browsing_context_id` has focus, focus the parent + // browsing context + if let Some(webview) = self.webviews.get_mut(browsing_context.top_level_id) { + if webview.focused_browsing_context_id == browsing_context_id { + trace!( + "About-to-be-closed browsing context {} is currently focused, so \ + focusing its parent {}", + browsing_context_id, parent_pipeline.browsing_context_id + ); + webview.focused_browsing_context_id = + parent_pipeline.browsing_context_id; + } + } else { + warn!( + "Browsing context {} contains a reference to \ + a non-existent top-level browsing context {}", + browsing_context_id, browsing_context.top_level_id + ); + } + }, }; } debug!("{}: Closed", browsing_context_id); diff --git a/components/constellation/pipeline.rs b/components/constellation/pipeline.rs index 2e139578ffe..556ef9bd60f 100644 --- a/components/constellation/pipeline.rs +++ b/components/constellation/pipeline.rs @@ -25,7 +25,7 @@ use constellation_traits::{LoadData, SWManagerMsg, ScriptToConstellationChan}; use crossbeam_channel::{Sender, unbounded}; use devtools_traits::{DevtoolsControlMsg, ScriptToDevtoolsControlMsg}; use embedder_traits::user_content_manager::UserContentManager; -use embedder_traits::{AnimationState, ViewportDetails}; +use embedder_traits::{AnimationState, FocusSequenceNumber, ViewportDetails}; use fonts::{SystemFontServiceProxy, SystemFontServiceProxySender}; use ipc_channel::Error; use ipc_channel::ipc::{self, IpcReceiver, IpcSender}; @@ -102,6 +102,8 @@ pub struct Pipeline { /// The last compositor [`Epoch`] that was laid out in this pipeline if "exit after load" is /// enabled. pub layout_epoch: Epoch, + + pub focus_sequence: FocusSequenceNumber, } /// Initial setup data needed to construct a pipeline. @@ -370,6 +372,7 @@ impl Pipeline { completely_loaded: false, title: String::new(), layout_epoch: Epoch(0), + focus_sequence: FocusSequenceNumber::default(), }; pipeline.set_throttled(throttled); diff --git a/components/constellation/tracing.rs b/components/constellation/tracing.rs index a939bbafc48..eff7f755c6b 100644 --- a/components/constellation/tracing.rs +++ b/components/constellation/tracing.rs @@ -123,8 +123,8 @@ mod from_script { Self::RemoveMessagePortRouter(..) => target!("RemoveMessagePortRouter"), Self::RerouteMessagePort(..) => target!("RerouteMessagePort"), Self::MessagePortShipped(..) => target!("MessagePortShipped"), - Self::RemoveMessagePort(..) => target!("RemoveMessagePort"), Self::EntanglePorts(..) => target!("EntanglePorts"), + Self::DisentanglePorts(..) => target!("DisentanglePorts"), Self::NewBroadcastChannelRouter(..) => target!("NewBroadcastChannelRouter"), Self::RemoveBroadcastChannelRouter(..) => target!("RemoveBroadcastChannelRouter"), Self::NewBroadcastChannelNameInRouter(..) => { @@ -138,7 +138,8 @@ mod from_script { Self::BroadcastStorageEvent(..) => target!("BroadcastStorageEvent"), Self::ChangeRunningAnimationsState(..) => target!("ChangeRunningAnimationsState"), Self::CreateCanvasPaintThread(..) => target!("CreateCanvasPaintThread"), - Self::Focus => target!("Focus"), + Self::Focus(..) => target!("Focus"), + Self::FocusRemoteDocument(..) => target!("FocusRemoteDocument"), Self::GetTopForBrowsingContext(..) => target!("GetTopForBrowsingContext"), Self::GetBrowsingContextInfo(..) => target!("GetBrowsingContextInfo"), Self::GetChildBrowsingContextId(..) => target!("GetChildBrowsingContextId"), 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/net/async_runtime.rs b/components/net/async_runtime.rs index c99068b1076..909bdef8fb0 100644 --- a/components/net/async_runtime.rs +++ b/components/net/async_runtime.rs @@ -2,31 +2,27 @@ * 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::cmp::Ord; +use std::sync::LazyLock; use std::sync::atomic::{AtomicUsize, Ordering}; -use std::sync::{LazyLock, Mutex}; use std::thread; use tokio::runtime::{Builder, Runtime}; -pub static HANDLE: LazyLock<Mutex<Option<Runtime>>> = LazyLock::new(|| { - Mutex::new(Some( - Builder::new_multi_thread() - .thread_name_fn(|| { - static ATOMIC_ID: AtomicUsize = AtomicUsize::new(0); - let id = ATOMIC_ID.fetch_add(1, Ordering::Relaxed); - format!("tokio-runtime-{}", id) - }) - .worker_threads( - thread::available_parallelism() - .map(|i| i.get()) - .unwrap_or(servo_config::pref!(threadpools_fallback_worker_num) as usize) - .min( - servo_config::pref!(threadpools_async_runtime_workers_max).max(1) as usize, - ), - ) - .enable_io() - .enable_time() - .build() - .unwrap(), - )) +pub static HANDLE: LazyLock<Runtime> = LazyLock::new(|| { + Builder::new_multi_thread() + .thread_name_fn(|| { + static ATOMIC_ID: AtomicUsize = AtomicUsize::new(0); + let id = ATOMIC_ID.fetch_add(1, Ordering::Relaxed); + format!("tokio-runtime-{}", id) + }) + .worker_threads( + thread::available_parallelism() + .map(|i| i.get()) + .unwrap_or(servo_config::pref!(threadpools_fallback_worker_num) as usize) + .min(servo_config::pref!(threadpools_async_runtime_workers_max).max(1) as usize), + ) + .enable_io() + .enable_time() + .build() + .expect("Unable to build tokio-runtime runtime") }); diff --git a/components/net/connector.rs b/components/net/connector.rs index 12d0638d84d..e02ff8971e3 100644 --- a/components/net/connector.rs +++ b/components/net/connector.rs @@ -165,7 +165,7 @@ where F: Future<Output = ()> + 'static + std::marker::Send, { fn execute(&self, fut: F) { - HANDLE.lock().unwrap().as_ref().unwrap().spawn(fut); + HANDLE.spawn(fut); } } diff --git a/components/net/http_loader.rs b/components/net/http_loader.rs index 35624bb8645..e0867b8d07f 100644 --- a/components/net/http_loader.rs +++ b/components/net/http_loader.rs @@ -493,7 +493,7 @@ impl BodySink { match self { BodySink::Chunked(sender) => { let sender = sender.clone(); - HANDLE.lock().unwrap().as_mut().unwrap().spawn(async move { + HANDLE.spawn(async move { let _ = sender.send(Ok(Frame::data(bytes.into()))).await; }); }, @@ -2016,7 +2016,7 @@ async fn http_network_fetch( let url1 = request.url(); let url2 = url1.clone(); - HANDLE.lock().unwrap().as_ref().unwrap().spawn( + HANDLE.spawn( res.into_body() .map_err(|e| { warn!("Error streaming response body: {:?}", e); diff --git a/components/net/resource_thread.rs b/components/net/resource_thread.rs index b6f885f29b7..5d1ede28c32 100644 --- a/components/net/resource_thread.rs +++ b/components/net/resource_thread.rs @@ -771,7 +771,7 @@ impl CoreResourceManager { _ => (FileTokenCheck::NotRequired, None), }; - HANDLE.lock().unwrap().as_ref().unwrap().spawn(async move { + HANDLE.spawn(async move { // XXXManishearth: Check origin against pipeline id (also ensure that the mode is allowed) // todo load context / mimesniff in fetch // todo referrer policy? diff --git a/components/net/websocket_loader.rs b/components/net/websocket_loader.rs index 95f66558482..128436ac47c 100644 --- a/components/net/websocket_loader.rs +++ b/components/net/websocket_loader.rs @@ -418,24 +418,21 @@ fn connect( tls_config.alpn_protocols = vec!["http/1.1".to_string().into()]; let resource_event_sender2 = resource_event_sender.clone(); - match HANDLE.lock().unwrap().as_mut() { - Some(handle) => handle.spawn( - start_websocket( - http_state, - req_url.clone(), - resource_event_sender, - protocols, - client, - tls_config, - dom_action_receiver, - ) - .map_err(move |e| { - warn!("Failed to establish a WebSocket connection: {:?}", e); - let _ = resource_event_sender2.send(WebSocketNetworkEvent::Fail); - }), - ), - None => return Err("No runtime available".to_string()), - }; + HANDLE.spawn( + start_websocket( + http_state, + req_url.clone(), + resource_event_sender, + protocols, + client, + tls_config, + dom_action_receiver, + ) + .map_err(move |e| { + warn!("Failed to establish a WebSocket connection: {:?}", e); + let _ = resource_event_sender2.send(WebSocketNetworkEvent::Fail); + }), + ); Ok(()) } diff --git a/components/script/canvas_context.rs b/components/script/canvas_context.rs index d49d31997e1..d85877c0f41 100644 --- a/components/script/canvas_context.rs +++ b/components/script/canvas_context.rs @@ -5,6 +5,7 @@ //! Common interfaces for Canvas Contexts use euclid::default::Size2D; +use script_bindings::root::Dom; use script_layout_interface::{HTMLCanvasData, HTMLCanvasDataSource}; use snapshot::Snapshot; @@ -12,6 +13,10 @@ use crate::dom::bindings::codegen::UnionTypes::HTMLCanvasElementOrOffscreenCanva use crate::dom::bindings::inheritance::Castable; use crate::dom::htmlcanvaselement::HTMLCanvasElement; use crate::dom::node::{Node, NodeDamage}; +use crate::dom::types::{ + CanvasRenderingContext2D, GPUCanvasContext, OffscreenCanvas, OffscreenCanvasRenderingContext2D, + WebGL2RenderingContext, WebGLRenderingContext, +}; pub(crate) trait LayoutCanvasRenderingContextHelpers { fn canvas_data_source(self) -> HTMLCanvasDataSource; @@ -85,3 +90,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/dissimilaroriginwindow.rs b/components/script/dom/dissimilaroriginwindow.rs index b7fbe0855fe..70c384db822 100644 --- a/components/script/dom/dissimilaroriginwindow.rs +++ b/components/script/dom/dissimilaroriginwindow.rs @@ -181,12 +181,13 @@ impl DissimilarOriginWindowMethods<crate::DomTypeHolder> for DissimilarOriginWin // https://html.spec.whatwg.org/multipage/#dom-window-blur fn Blur(&self) { - // TODO: Implement x-origin blur + // > User agents are encouraged to ignore calls to this `blur()` method + // > entirely. } - // https://html.spec.whatwg.org/multipage/#dom-focus + // https://html.spec.whatwg.org/multipage/#dom-window-focus fn Focus(&self) { - // TODO: Implement x-origin focus + self.window_proxy().focus(); } // https://html.spec.whatwg.org/multipage/#dom-location diff --git a/components/script/dom/document.rs b/components/script/dom/document.rs index ec2ad98c464..2baab15e1b8 100644 --- a/components/script/dom/document.rs +++ b/components/script/dom/document.rs @@ -30,8 +30,8 @@ use devtools_traits::ScriptToDevtoolsControlMsg; use dom_struct::dom_struct; use embedder_traits::{ AllowOrDeny, AnimationState, CompositorHitTestResult, ContextMenuResult, EditingActionEvent, - EmbedderMsg, ImeEvent, InputEvent, LoadStatus, MouseButton, MouseButtonAction, - MouseButtonEvent, TouchEvent, TouchEventType, TouchId, WheelEvent, + EmbedderMsg, FocusSequenceNumber, ImeEvent, InputEvent, LoadStatus, MouseButton, + MouseButtonAction, MouseButtonEvent, TouchEvent, TouchEventType, TouchId, WheelEvent, }; use encoding_rs::{Encoding, UTF_8}; use euclid::default::{Point2D, Rect, Size2D}; @@ -270,12 +270,11 @@ pub(crate) enum IsHTMLDocument { #[derive(JSTraceable, MallocSizeOf)] #[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)] -enum FocusTransaction { - /// No focus operation is in effect. - NotInTransaction, - /// A focus operation is in effect. - /// Contains the element that has most recently requested focus for itself. - InTransaction(Option<Dom<Element>>), +struct FocusTransaction { + /// The focused element of this document. + element: Option<Dom<Element>>, + /// See [`Document::has_focus`]. + has_focus: bool, } /// Information about a declarative refresh @@ -341,9 +340,16 @@ pub(crate) struct Document { /// Whether the DOMContentLoaded event has already been dispatched. domcontentloaded_dispatched: Cell<bool>, /// The state of this document's focus transaction. - focus_transaction: DomRefCell<FocusTransaction>, + focus_transaction: DomRefCell<Option<FocusTransaction>>, /// The element that currently has the document focus context. focused: MutNullableDom<Element>, + /// The last sequence number sent to the constellation. + #[no_trace] + focus_sequence: Cell<FocusSequenceNumber>, + /// Indicates whether the container is included in the top-level browsing + /// context's focus chain (not considering system focus). Permanently `true` + /// for a top-level document. + has_focus: Cell<bool>, /// The script element that is currently executing. current_script: MutNullableDom<HTMLScriptElement>, /// <https://html.spec.whatwg.org/multipage/#pending-parsing-blocking-script> @@ -1120,124 +1126,318 @@ impl Document { self.focused.get() } + /// Get the last sequence number sent to the constellation. + /// + /// Received focus-related messages with sequence numbers less than the one + /// returned by this method must be discarded. + pub fn get_focus_sequence(&self) -> FocusSequenceNumber { + self.focus_sequence.get() + } + + /// Generate the next sequence number for focus-related messages. + fn increment_fetch_focus_sequence(&self) -> FocusSequenceNumber { + self.focus_sequence.set(FocusSequenceNumber( + self.focus_sequence + .get() + .0 + .checked_add(1) + .expect("too many focus messages have been sent"), + )); + self.focus_sequence.get() + } + /// Initiate a new round of checking for elements requesting focus. The last element to call /// `request_focus` before `commit_focus_transaction` is called will receive focus. fn begin_focus_transaction(&self) { - *self.focus_transaction.borrow_mut() = FocusTransaction::InTransaction(Default::default()); + // Initialize it with the current state + *self.focus_transaction.borrow_mut() = Some(FocusTransaction { + element: self.focused.get().as_deref().map(Dom::from_ref), + has_focus: self.has_focus.get(), + }); } /// <https://html.spec.whatwg.org/multipage/#focus-fixup-rule> pub(crate) fn perform_focus_fixup_rule(&self, not_focusable: &Element, can_gc: CanGc) { + // Return if `not_focusable` is not the designated focused area of the + // `Document`. if Some(not_focusable) != self.focused.get().as_deref() { return; } - self.request_focus( - self.GetBody().as_ref().map(|e| e.upcast()), - FocusType::Element, - can_gc, - ) + + let implicit_transaction = self.focus_transaction.borrow().is_none(); + + if implicit_transaction { + self.begin_focus_transaction(); + } + + // Designate the viewport as the new focused area of the `Document`, but + // do not run the focusing steps. + { + let mut focus_transaction = self.focus_transaction.borrow_mut(); + focus_transaction.as_mut().unwrap().element = None; + } + + if implicit_transaction { + self.commit_focus_transaction(FocusInitiator::Local, can_gc); + } } - /// Request that the given element receive focus once the current transaction is complete. - /// If None is passed, then whatever element is currently focused will no longer be focused - /// once the transaction is complete. + /// Request that the given element receive focus once the current + /// transaction is complete. `None` specifies to focus the document. + /// + /// If there's no ongoing transaction, this method automatically starts and + /// commits an implicit transaction. pub(crate) fn request_focus( &self, elem: Option<&Element>, - focus_type: FocusType, + focus_initiator: FocusInitiator, can_gc: CanGc, ) { - let implicit_transaction = matches!( - *self.focus_transaction.borrow(), - FocusTransaction::NotInTransaction - ); + // If an element is specified, and it's non-focusable, ignore the + // request. + if elem.is_some_and(|e| !e.is_focusable_area()) { + return; + } + + let implicit_transaction = self.focus_transaction.borrow().is_none(); + if implicit_transaction { self.begin_focus_transaction(); } - if elem.is_none_or(|e| e.is_focusable_area()) { - *self.focus_transaction.borrow_mut() = - FocusTransaction::InTransaction(elem.map(Dom::from_ref)); + + { + let mut focus_transaction = self.focus_transaction.borrow_mut(); + let focus_transaction = focus_transaction.as_mut().unwrap(); + focus_transaction.element = elem.map(Dom::from_ref); + focus_transaction.has_focus = true; } + if implicit_transaction { - self.commit_focus_transaction(focus_type, can_gc); + self.commit_focus_transaction(focus_initiator, can_gc); + } + } + + /// Update the local focus state accordingly after being notified that the + /// document's container is removed from the top-level browsing context's + /// focus chain (not considering system focus). + pub(crate) fn handle_container_unfocus(&self, can_gc: CanGc) { + assert!( + self.window().parent_info().is_some(), + "top-level document cannot be unfocused", + ); + + // Since this method is called from an event loop, there mustn't be + // an in-progress focus transaction + assert!( + self.focus_transaction.borrow().is_none(), + "there mustn't be an in-progress focus transaction at this point" + ); + + // Start an implicit focus transaction + self.begin_focus_transaction(); + + // Update the transaction + { + let mut focus_transaction = self.focus_transaction.borrow_mut(); + focus_transaction.as_mut().unwrap().has_focus = false; } + + // Commit the implicit focus transaction + self.commit_focus_transaction(FocusInitiator::Remote, can_gc); } /// Reassign the focus context to the element that last requested focus during this - /// transaction, or none if no elements requested it. - fn commit_focus_transaction(&self, focus_type: FocusType, can_gc: CanGc) { - let possibly_focused = match *self.focus_transaction.borrow() { - FocusTransaction::NotInTransaction => unreachable!(), - FocusTransaction::InTransaction(ref elem) => { - elem.as_ref().map(|e| DomRoot::from_ref(&**e)) - }, + /// transaction, or the document if no elements requested it. + fn commit_focus_transaction(&self, focus_initiator: FocusInitiator, can_gc: CanGc) { + let (mut new_focused, new_focus_state) = { + let focus_transaction = self.focus_transaction.borrow(); + let focus_transaction = focus_transaction + .as_ref() + .expect("no focus transaction in progress"); + ( + focus_transaction + .element + .as_ref() + .map(|e| DomRoot::from_ref(&**e)), + focus_transaction.has_focus, + ) }; - *self.focus_transaction.borrow_mut() = FocusTransaction::NotInTransaction; - if self.focused == possibly_focused.as_deref() { - return; - } - if let Some(ref elem) = self.focused.get() { - let node = elem.upcast::<Node>(); - elem.set_focus_state(false); - // FIXME: pass appropriate relatedTarget - if node.is_connected() { - self.fire_focus_event(FocusEventType::Blur, node, None, can_gc); + *self.focus_transaction.borrow_mut() = None; + + if !new_focus_state { + // In many browsers, a document forgets its focused area when the + // document is removed from the top-level BC's focus chain + if new_focused.take().is_some() { + trace!( + "Forgetting the document's focused area because the \ + document's container was removed from the top-level BC's \ + focus chain" + ); } + } + + let old_focused = self.focused.get(); + let old_focus_state = self.has_focus.get(); + + debug!( + "Committing focus transaction: {:?} → {:?}", + (&old_focused, old_focus_state), + (&new_focused, new_focus_state), + ); - // Notify the embedder to hide the input method. - if elem.input_method_type().is_some() { - self.send_to_embedder(EmbedderMsg::HideIME(self.webview_id())); + // `*_focused_filtered` indicates the local element (if any) included in + // the top-level BC's focus chain. + let old_focused_filtered = old_focused.as_ref().filter(|_| old_focus_state); + let new_focused_filtered = new_focused.as_ref().filter(|_| new_focus_state); + + let trace_focus_chain = |name, element, doc| { + trace!( + "{} local focus chain: {}", + name, + match (element, doc) { + (Some(e), _) => format!("[{:?}, document]", e), + (None, true) => "[document]".to_owned(), + (None, false) => "[]".to_owned(), + } + ); + }; + + trace_focus_chain("Old", old_focused_filtered, old_focus_state); + trace_focus_chain("New", new_focused_filtered, new_focus_state); + + if old_focused_filtered != new_focused_filtered { + if let Some(elem) = &old_focused_filtered { + let node = elem.upcast::<Node>(); + elem.set_focus_state(false); + // FIXME: pass appropriate relatedTarget + if node.is_connected() { + self.fire_focus_event(FocusEventType::Blur, node.upcast(), None, can_gc); + } + + // Notify the embedder to hide the input method. + if elem.input_method_type().is_some() { + self.send_to_embedder(EmbedderMsg::HideIME(self.webview_id())); + } } } - self.focused.set(possibly_focused.as_deref()); + if old_focus_state != new_focus_state && !new_focus_state { + self.fire_focus_event(FocusEventType::Blur, self.global().upcast(), None, can_gc); + } - if let Some(ref elem) = self.focused.get() { - elem.set_focus_state(true); - let node = elem.upcast::<Node>(); - // FIXME: pass appropriate relatedTarget - self.fire_focus_event(FocusEventType::Focus, node, None, can_gc); - // Update the focus state for all elements in the focus chain. - // https://html.spec.whatwg.org/multipage/#focus-chain - if focus_type == FocusType::Element { - self.window() - .send_to_constellation(ScriptToConstellationMessage::Focus); + self.focused.set(new_focused.as_deref()); + self.has_focus.set(new_focus_state); + + if old_focus_state != new_focus_state && new_focus_state { + self.fire_focus_event(FocusEventType::Focus, self.global().upcast(), None, can_gc); + } + + if old_focused_filtered != new_focused_filtered { + if let Some(elem) = &new_focused_filtered { + elem.set_focus_state(true); + let node = elem.upcast::<Node>(); + // FIXME: pass appropriate relatedTarget + self.fire_focus_event(FocusEventType::Focus, node.upcast(), None, can_gc); + + // Notify the embedder to display an input method. + if let Some(kind) = elem.input_method_type() { + let rect = elem.upcast::<Node>().bounding_content_box_or_zero(can_gc); + let rect = Rect::new( + Point2D::new(rect.origin.x.to_px(), rect.origin.y.to_px()), + Size2D::new(rect.size.width.to_px(), rect.size.height.to_px()), + ); + let (text, multiline) = if let Some(input) = elem.downcast::<HTMLInputElement>() + { + ( + Some(( + (input.Value()).to_string(), + input.GetSelectionEnd().unwrap_or(0) as i32, + )), + false, + ) + } else if let Some(textarea) = elem.downcast::<HTMLTextAreaElement>() { + ( + Some(( + (textarea.Value()).to_string(), + textarea.GetSelectionEnd().unwrap_or(0) as i32, + )), + true, + ) + } else { + (None, false) + }; + self.send_to_embedder(EmbedderMsg::ShowIME( + self.webview_id(), + kind, + text, + multiline, + DeviceIntRect::from_untyped(&rect.to_box2d()), + )); + } } + } + + if focus_initiator != FocusInitiator::Local { + return; + } - // Notify the embedder to display an input method. - if let Some(kind) = elem.input_method_type() { - let rect = elem.upcast::<Node>().bounding_content_box_or_zero(can_gc); - let rect = Rect::new( - Point2D::new(rect.origin.x.to_px(), rect.origin.y.to_px()), - Size2D::new(rect.size.width.to_px(), rect.size.height.to_px()), + // We are the initiator of the focus operation, so we must broadcast + // the change we intend to make. + match (old_focus_state, new_focus_state) { + (_, true) => { + // Advertise the change in the focus chain. + // <https://html.spec.whatwg.org/multipage/#focus-chain> + // <https://html.spec.whatwg.org/multipage/#focusing-steps> + // + // If the top-level BC doesn't have system focus, this won't + // have an immediate effect, but it will when we gain system + // focus again. Therefore we still have to send `ScriptMsg:: + // Focus`. + // + // When a container with a non-null nested browsing context is + // focused, its active document becomes the focused area of the + // top-level browsing context instead. Therefore we need to let + // the constellation know if such a container is focused. + // + // > The focusing steps for an object `new focus target` [...] + // > + // > 3. If `new focus target` is a browsing context container + // > with non-null nested browsing context, then set + // > `new focus target` to the nested browsing context's + // > active document. + let child_browsing_context_id = new_focused + .as_ref() + .and_then(|elem| elem.downcast::<HTMLIFrameElement>()) + .and_then(|iframe| iframe.browsing_context_id()); + + let sequence = self.increment_fetch_focus_sequence(); + + debug!( + "Advertising the focus request to the constellation \ + with sequence number {} and child BC ID {}", + sequence, + child_browsing_context_id + .as_ref() + .map(|id| id as &dyn std::fmt::Display) + .unwrap_or(&"(none)"), ); - let (text, multiline) = if let Some(input) = elem.downcast::<HTMLInputElement>() { - ( - Some(( - input.Value().to_string(), - input.GetSelectionEnd().unwrap_or(0) as i32, - )), - false, - ) - } else if let Some(textarea) = elem.downcast::<HTMLTextAreaElement>() { - ( - Some(( - textarea.Value().to_string(), - textarea.GetSelectionEnd().unwrap_or(0) as i32, - )), - true, - ) - } else { - (None, false) - }; - self.send_to_embedder(EmbedderMsg::ShowIME( - self.webview_id(), - kind, - text, - multiline, - DeviceIntRect::from_untyped(&rect.to_box2d()), - )); - } + + self.window() + .send_to_constellation(ScriptToConstellationMessage::Focus( + child_browsing_context_id, + sequence, + )); + }, + (false, false) => { + // Our `Document` doesn't have focus, and we intend to keep it + // this way. + }, + (true, false) => { + unreachable!( + "Can't lose the document's focus without specifying \ + another one to focus" + ); + }, } } @@ -1352,7 +1552,10 @@ impl Document { } self.begin_focus_transaction(); - self.request_focus(Some(&*el), FocusType::Element, can_gc); + // Try to focus `el`. If it's not focusable, focus the document + // instead. + self.request_focus(None, FocusInitiator::Local, can_gc); + self.request_focus(Some(&*el), FocusInitiator::Local, can_gc); } let dom_event = DomRoot::upcast::<Event>(MouseEvent::for_platform_mouse_event( @@ -1390,7 +1593,9 @@ impl Document { } if let MouseButtonAction::Click = event.action { - self.commit_focus_transaction(FocusType::Element, can_gc); + if self.focus_transaction.borrow().is_some() { + self.commit_focus_transaction(FocusInitiator::Local, can_gc); + } self.maybe_fire_dblclick( hit_test_result.point_in_viewport, node, @@ -2217,7 +2422,7 @@ impl Document { ImeEvent::Dismissed => { self.request_focus( self.GetBody().as_ref().map(|e| e.upcast()), - FocusType::Element, + FocusInitiator::Local, can_gc, ); return; @@ -3196,7 +3401,7 @@ impl Document { fn fire_focus_event( &self, focus_event_type: FocusEventType, - node: &Node, + event_target: &EventTarget, related_target: Option<&EventTarget>, can_gc: CanGc, ) { @@ -3216,8 +3421,7 @@ impl Document { ); let event = event.upcast::<Event>(); event.set_trusted(true); - let target = node.upcast(); - event.fire(target, can_gc); + event.fire(event_target, can_gc); } /// <https://html.spec.whatwg.org/multipage/#cookie-averse-document-object> @@ -3797,6 +4001,8 @@ impl Document { .and_then(|charset| Encoding::for_label(charset.as_bytes())) .unwrap_or(UTF_8); + let has_focus = window.parent_info().is_none(); + let has_browsing_context = has_browsing_context == HasBrowsingContext::Yes; Document { @@ -3844,8 +4050,10 @@ impl Document { stylesheet_list: MutNullableDom::new(None), ready_state: Cell::new(ready_state), domcontentloaded_dispatched: Cell::new(domcontentloaded_dispatched), - focus_transaction: DomRefCell::new(FocusTransaction::NotInTransaction), + focus_transaction: DomRefCell::new(None), focused: Default::default(), + focus_sequence: Cell::new(FocusSequenceNumber::default()), + has_focus: Cell::new(has_focus), current_script: Default::default(), pending_parsing_blocking_script: Default::default(), script_blocking_stylesheets_count: Cell::new(0u32), @@ -4991,12 +5199,34 @@ impl DocumentMethods<crate::DomTypeHolder> for Document { // https://html.spec.whatwg.org/multipage/#dom-document-hasfocus fn HasFocus(&self) -> bool { - // Step 1-2. - if self.window().parent_info().is_none() && self.is_fully_active() { - return true; + // <https://html.spec.whatwg.org/multipage/#has-focus-steps> + // + // > The has focus steps, given a `Document` object `target`, are as + // > follows: + // > + // > 1. If `target`'s browsing context's top-level browsing context does + // > not have system focus, then return false. + + // > 2. Let `candidate` be `target`'s browsing context's top-level + // > browsing context's active document. + // > + // > 3. While true: + // > + // > 3.1. If `candidate` is target, then return true. + // > + // > 3.2. If the focused area of `candidate` is a browsing context + // > container with a non-null nested browsing context, then set + // > `candidate` to the active document of that browsing context + // > container's nested browsing context. + // > + // > 3.3. Otherwise, return false. + if self.window().parent_info().is_none() { + // 2 → 3 → (3.1 || ⋯ → 3.3) + self.is_fully_active() + } else { + // 2 → 3 → 3.2 → (⋯ → 3.1 || ⋯ → 3.3) + self.is_fully_active() && self.has_focus.get() } - // TODO Step 3. - false } // https://html.spec.whatwg.org/multipage/#dom-document-domain @@ -6399,6 +6629,17 @@ pub(crate) enum FocusType { Parent, // Focusing a parent element (an iframe) } +/// Specifies the initiator of a focus operation. +#[derive(Clone, Copy, PartialEq)] +pub enum FocusInitiator { + /// The operation is initiated by this document and to be broadcasted + /// through the constellation. + Local, + /// The operation is initiated somewhere else, and we are updating our + /// internal state accordingly. + Remote, +} + /// Focus events pub(crate) enum FocusEventType { Focus, // Element gained focus. Doesn't bubble. diff --git a/components/script/dom/element.rs b/components/script/dom/element.rs index 3a8ac8f0cd8..2831fc3d8f0 100644 --- a/components/script/dom/element.rs +++ b/components/script/dom/element.rs @@ -327,7 +327,21 @@ impl Element { ) } - impl_rare_data!(ElementRareData); + fn rare_data(&self) -> Ref<Option<Box<ElementRareData>>> { + self.rare_data.borrow() + } + + fn rare_data_mut(&self) -> RefMut<Option<Box<ElementRareData>>> { + self.rare_data.borrow_mut() + } + + fn ensure_rare_data(&self) -> RefMut<Box<ElementRareData>> { + let mut rare_data = self.rare_data.borrow_mut(); + if rare_data.is_none() { + *rare_data = Some(Default::default()); + } + RefMut::map(rare_data, |rare_data| rare_data.as_mut().unwrap()) + } pub(crate) fn restyle(&self, damage: NodeDamage) { let doc = self.node.owner_doc(); diff --git a/components/script/dom/globalscope.rs b/components/script/dom/globalscope.rs index b3345b90fc0..efa9a9a97ab 100644 --- a/components/script/dom/globalscope.rs +++ b/components/script/dom/globalscope.rs @@ -457,8 +457,9 @@ pub(crate) struct ManagedMessagePort { /// and only add them, and ask the constellation to complete the transfer, /// in a subsequent task if the port hasn't been re-transfered. pending: bool, - /// Has the port been closed? If closed, it can be dropped and later GC'ed. - closed: bool, + /// Whether the port has been closed by script in this global, + /// so it can be removed. + explicitly_closed: bool, /// Note: it may seem strange to use a pair of options, versus for example an enum. /// But it looks like tranform streams will require both of those in their transfer. /// This will be resolved when we reach that point of the implementation. @@ -546,12 +547,17 @@ impl MessageListener { let mut succeeded = vec![]; let mut failed = HashMap::new(); - for (id, buffer) in ports.into_iter() { + for (id, info) in ports.into_iter() { if global.is_managing_port(&id) { succeeded.push(id); - global.complete_port_transfer(id, buffer); + global.complete_port_transfer( + id, + info.port_message_queue, + info.disentangled, + CanGc::note() + ); } else { - failed.insert(id, buffer); + failed.insert(id, info); } } let _ = global.script_to_constellation_chan().send( @@ -560,13 +566,21 @@ impl MessageListener { }) ); }, - MessagePortMsg::CompletePendingTransfer(port_id, buffer) => { + MessagePortMsg::CompletePendingTransfer(port_id, info) => { let context = self.context.clone(); self.task_source.queue(task!(complete_pending: move || { let global = context.root(); - global.complete_port_transfer(port_id, buffer); + global.complete_port_transfer(port_id, info.port_message_queue, info.disentangled, CanGc::note()); })); }, + MessagePortMsg::CompleteDisentanglement(port_id) => { + let context = self.context.clone(); + self.task_source + .queue(task!(try_complete_disentanglement: move || { + let global = context.root(); + global.try_complete_disentanglement(port_id, CanGc::note()); + })); + }, MessagePortMsg::NewTask(port_id, task) => { let context = self.context.clone(); self.task_source.queue(task!(process_new_task: move || { @@ -574,14 +588,6 @@ impl MessageListener { global.route_task_to_port(port_id, task, CanGc::note()); })); }, - MessagePortMsg::RemoveMessagePort(port_id) => { - let context = self.context.clone(); - self.task_source - .queue(task!(process_remove_message_port: move || { - let global = context.root(); - global.note_entangled_port_removed(&port_id); - })); - }, } } } @@ -871,7 +877,13 @@ impl GlobalScope { } /// Complete the transfer of a message-port. - fn complete_port_transfer(&self, port_id: MessagePortId, tasks: VecDeque<PortMessageTask>) { + fn complete_port_transfer( + &self, + port_id: MessagePortId, + tasks: VecDeque<PortMessageTask>, + disentangled: bool, + can_gc: CanGc, + ) { let should_start = if let MessagePortState::Managed(_id, message_ports) = &mut *self.message_port_state.borrow_mut() { @@ -885,6 +897,10 @@ impl GlobalScope { } if let Some(port_impl) = managed_port.port_impl.as_mut() { port_impl.complete_transfer(tasks); + if disentangled { + port_impl.disentangle(); + managed_port.dom_port.disentangle(); + } port_impl.enabled() } else { panic!("managed-port has no port-impl."); @@ -895,7 +911,45 @@ impl GlobalScope { panic!("complete_port_transfer called for an unknown port."); }; if should_start { - self.start_message_port(&port_id); + self.start_message_port(&port_id, can_gc); + } + } + + /// The closing of `otherPort`, if it is in a different global. + /// <https://html.spec.whatwg.org/multipage/#disentangle> + fn try_complete_disentanglement(&self, port_id: MessagePortId, can_gc: CanGc) { + let dom_port = if let MessagePortState::Managed(_id, message_ports) = + &mut *self.message_port_state.borrow_mut() + { + let dom_port = if let Some(managed_port) = message_ports.get_mut(&port_id) { + if managed_port.pending { + unreachable!("CompleteDisentanglement msg received for a pending port."); + } + let port_impl = managed_port + .port_impl + .as_mut() + .expect("managed-port has no port-impl."); + port_impl.disentangle(); + managed_port.dom_port.as_rooted() + } else { + // Note: this, and the other return below, + // can happen if the port has already been transferred out of this global, + // in which case the disentanglement will complete along with the transfer. + return; + }; + dom_port + } else { + return; + }; + + // Fire an event named close at otherPort. + dom_port.upcast().fire_event(atom!("close"), can_gc); + + let res = self.script_to_constellation_chan().send( + ScriptToConstellationMessage::DisentanglePorts(port_id, None), + ); + if res.is_err() { + warn!("Sending DisentanglePorts failed"); } } @@ -951,8 +1005,64 @@ impl GlobalScope { } /// <https://html.spec.whatwg.org/multipage/#disentangle> - pub(crate) fn disentangle_port(&self, _port: &MessagePort) { - // TODO: #36465 + pub(crate) fn disentangle_port(&self, port: &MessagePort, can_gc: CanGc) { + let initiator_port = port.message_port_id(); + // Let otherPort be the MessagePort which initiatorPort was entangled with. + let Some(other_port) = port.disentangle() else { + // Assert: otherPort exists. + // Note: ignoring the assert, + // because the streams spec seems to disentangle ports that are disentangled already. + return; + }; + + // Disentangle initiatorPort and otherPort, so that they are no longer entangled or associated with each other. + // Note: this is done in part here, and in part at the constellation(if otherPort is in another global). + let dom_port = if let MessagePortState::Managed(_id, message_ports) = + &mut *self.message_port_state.borrow_mut() + { + let mut dom_port = None; + for port_id in &[initiator_port, &other_port] { + match message_ports.get_mut(port_id) { + None => { + continue; + }, + Some(managed_port) => { + let port_impl = managed_port + .port_impl + .as_mut() + .expect("managed-port has no port-impl."); + managed_port.dom_port.disentangle(); + port_impl.disentangle(); + + if **port_id == other_port { + dom_port = Some(managed_port.dom_port.as_rooted()) + } + }, + } + } + dom_port + } else { + panic!("disentangle_port called on a global not managing any ports."); + }; + + // Fire an event named close at `otherPort`. + // Note: done here if the port is managed by the same global as `initialPort`. + if let Some(dom_port) = dom_port { + dom_port.upcast().fire_event(atom!("close"), can_gc); + } + + let chan = self.script_to_constellation_chan().clone(); + let initiator_port = *initiator_port; + self.task_manager() + .port_message_queue() + .queue(task!(post_message: move || { + // Note: we do this in a task to ensure it doesn't affect messages that are still to be routed, + // see the task queueing in `post_messageport_msg`. + let res = chan.send(ScriptToConstellationMessage::DisentanglePorts(initiator_port, Some(other_port))); + if res.is_err() { + warn!("Sending DisentanglePorts failed"); + } + })); } /// <https://html.spec.whatwg.org/multipage/#entangle> @@ -984,18 +1094,6 @@ impl GlobalScope { .send(ScriptToConstellationMessage::EntanglePorts(port1, port2)); } - /// Note that the entangled port of `port_id` has been removed in another global. - pub(crate) fn note_entangled_port_removed(&self, port_id: &MessagePortId) { - // Note: currently this is a no-op, - // as we only use the `close` method to manage the local lifecyle of a port. - // This could be used as part of lifecyle management to determine a port can be GC'ed. - // See https://github.com/servo/servo/issues/25772 - warn!( - "Entangled port of {:?} has been removed in another global", - port_id - ); - } - /// Handle the transfer of a port in the current task. pub(crate) fn mark_port_as_transferred(&self, port_id: &MessagePortId) -> MessagePortImpl { if let MessagePortState::Managed(_id, message_ports) = @@ -1021,20 +1119,21 @@ impl GlobalScope { } /// <https://html.spec.whatwg.org/multipage/#dom-messageport-start> - pub(crate) fn start_message_port(&self, port_id: &MessagePortId) { - let message_buffer = if let MessagePortState::Managed(_id, message_ports) = + pub(crate) fn start_message_port(&self, port_id: &MessagePortId, can_gc: CanGc) { + let (message_buffer, dom_port) = if let MessagePortState::Managed(_id, message_ports) = &mut *self.message_port_state.borrow_mut() { - match message_ports.get_mut(port_id) { + let (message_buffer, dom_port) = match message_ports.get_mut(port_id) { None => panic!("start_message_port called on a unknown port."), Some(managed_port) => { if let Some(port_impl) = managed_port.port_impl.as_mut() { - port_impl.start() + (port_impl.start(), managed_port.dom_port.as_rooted()) } else { panic!("managed-port has no port-impl."); } }, - } + }; + (message_buffer, dom_port) } else { return warn!("start_message_port called on a global not managing any ports."); }; @@ -1042,6 +1141,18 @@ impl GlobalScope { for task in message_buffer { self.route_task_to_port(*port_id, task, CanGc::note()); } + if dom_port.disentangled() { + // <https://html.spec.whatwg.org/multipage/#disentangle> + // Fire an event named close at otherPort. + dom_port.upcast().fire_event(atom!("close"), can_gc); + + let res = self.script_to_constellation_chan().send( + ScriptToConstellationMessage::DisentanglePorts(*port_id, None), + ); + if res.is_err() { + warn!("Sending DisentanglePorts failed"); + } + } } } @@ -1055,7 +1166,7 @@ impl GlobalScope { Some(managed_port) => { if let Some(port_impl) = managed_port.port_impl.as_mut() { port_impl.close(); - managed_port.closed = true; + managed_port.explicitly_closed = true; } else { panic!("managed-port has no port-impl."); } @@ -1436,12 +1547,7 @@ impl GlobalScope { let to_be_removed: Vec<MessagePortId> = message_ports .iter() .filter_map(|(id, managed_port)| { - if managed_port.closed { - // Let the constellation know to drop this port and the one it is entangled with, - // and to forward this message to the script-process where the entangled is found. - let _ = self - .script_to_constellation_chan() - .send(ScriptToConstellationMessage::RemoveMessagePort(*id)); + if managed_port.explicitly_closed { Some(*id) } else { None @@ -1451,6 +1557,9 @@ impl GlobalScope { for id in to_be_removed { message_ports.remove(&id); } + // Note: ports are only removed throught explicit closure by script in this global. + // TODO: #25772 + // TODO: remove ports when we can be sure their port message queue is empty(via the constellation). message_ports.is_empty() } else { false @@ -1581,7 +1690,7 @@ impl GlobalScope { port_impl: Some(port_impl), dom_port: Dom::from_ref(dom_port), pending: true, - closed: false, + explicitly_closed: false, cross_realm_transform_readable: None, cross_realm_transform_writable: None, }, @@ -1605,7 +1714,7 @@ impl GlobalScope { port_impl: Some(port_impl), dom_port: Dom::from_ref(dom_port), pending: false, - closed: false, + explicitly_closed: false, cross_realm_transform_readable: None, cross_realm_transform_writable: None, }, diff --git a/components/script/dom/htmlcanvaselement.rs b/components/script/dom/htmlcanvaselement.rs index bb27d28cea8..cc6df183f42 100644 --- a/components/script/dom/htmlcanvaselement.rs +++ b/components/script/dom/htmlcanvaselement.rs @@ -27,14 +27,13 @@ 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 => HTMLCanvasDataSource::Empty, } }; @@ -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/htmldetailselement.rs b/components/script/dom/htmldetailselement.rs index a3e2a05af32..1d48b8e7a97 100644 --- a/components/script/dom/htmldetailselement.rs +++ b/components/script/dom/htmldetailselement.rs @@ -178,8 +178,6 @@ impl HTMLDetailsElement { } } shadow_tree.descendants.Assign(slottable_children); - - self.upcast::<Node>().dirty(NodeDamage::OtherNodeDamage); } fn update_shadow_tree_styles(&self, can_gc: CanGc) { @@ -214,8 +212,6 @@ impl HTMLDetailsElement { .implicit_summary .upcast::<Element>() .set_string_attribute(&local_name!("style"), implicit_summary_style.into(), can_gc); - - self.upcast::<Node>().dirty(NodeDamage::OtherNodeDamage); } } diff --git a/components/script/dom/htmlelement.rs b/components/script/dom/htmlelement.rs index 9505d5182c7..e7efbde9b1d 100644 --- a/components/script/dom/htmlelement.rs +++ b/components/script/dom/htmlelement.rs @@ -32,7 +32,7 @@ use crate::dom::bindings::str::DOMString; use crate::dom::characterdata::CharacterData; use crate::dom::cssstyledeclaration::{CSSModificationAccess, CSSStyleDeclaration, CSSStyleOwner}; use crate::dom::customelementregistry::CallbackReaction; -use crate::dom::document::{Document, FocusType}; +use crate::dom::document::{Document, FocusInitiator}; use crate::dom::documentfragment::DocumentFragment; use crate::dom::domstringmap::DOMStringMap; use crate::dom::element::{AttributeMutation, Element}; @@ -415,18 +415,19 @@ impl HTMLElementMethods<crate::DomTypeHolder> for HTMLElement { // TODO: Mark the element as locked for focus and run the focusing steps. // https://html.spec.whatwg.org/multipage/#focusing-steps let document = self.owner_document(); - document.request_focus(Some(self.upcast()), FocusType::Element, can_gc); + document.request_focus(Some(self.upcast()), FocusInitiator::Local, can_gc); } // https://html.spec.whatwg.org/multipage/#dom-blur fn Blur(&self, can_gc: CanGc) { - // TODO: Run the unfocusing steps. + // TODO: Run the unfocusing steps. Focus the top-level document, not + // the current document. if !self.as_element().focus_state() { return; } // https://html.spec.whatwg.org/multipage/#unfocusing-steps let document = self.owner_document(); - document.request_focus(None, FocusType::Element, can_gc); + document.request_focus(None, FocusInitiator::Local, can_gc); } // https://drafts.csswg.org/cssom-view/#dom-htmlelement-offsetparent diff --git a/components/script/dom/macros.rs b/components/script/dom/macros.rs index b3f222af0da..c2f5ba37c21 100644 --- a/components/script/dom/macros.rs +++ b/components/script/dom/macros.rs @@ -719,26 +719,3 @@ macro_rules! handle_potential_webgl_error { handle_potential_webgl_error!($context, $call, ()) }; } - -macro_rules! impl_rare_data ( - ($type:ty) => ( - fn rare_data(&self) -> Ref<Option<Box<$type>>> { - self.rare_data.borrow() - } - - #[allow(dead_code)] - fn rare_data_mut(&self) -> RefMut<Option<Box<$type>>> { - self.rare_data.borrow_mut() - } - - fn ensure_rare_data(&self) -> RefMut<Box<$type>> { - let mut rare_data = self.rare_data.borrow_mut(); - if rare_data.is_none() { - *rare_data = Some(Default::default()); - } - RefMut::map(rare_data, |rare_data| { - rare_data.as_mut().unwrap() - }) - } - ); -); diff --git a/components/script/dom/messageport.rs b/components/script/dom/messageport.rs index 85d94c1aa7a..d70d3139b96 100644 --- a/components/script/dom/messageport.rs +++ b/components/script/dom/messageport.rs @@ -83,6 +83,20 @@ impl MessagePort { *self.entangled_port.borrow_mut() = Some(other_id); } + /// <https://html.spec.whatwg.org/multipage/#disentangle> + pub(crate) fn disentangle(&self) -> Option<MessagePortId> { + // Disentangle initiatorPort and otherPort, so that they are no longer entangled or associated with each other. + // Note: called from `disentangle_port` in the global, where the rest happens. + self.entangled_port.borrow_mut().take() + } + + /// Has the port been disentangled? + /// Used when starting the port to fire the `close` event, + /// to cover the case of a disentanglement while in transfer. + pub(crate) fn disentangled(&self) -> bool { + self.entangled_port.borrow().is_none() + } + pub(crate) fn message_port_id(&self) -> &MessagePortId { &self.message_port_id } @@ -314,20 +328,28 @@ impl MessagePortMethods<crate::DomTypeHolder> for MessagePort { } /// <https://html.spec.whatwg.org/multipage/#dom-messageport-start> - fn Start(&self) { + fn Start(&self, can_gc: CanGc) { if self.detached.get() { return; } - self.global().start_message_port(self.message_port_id()); + self.global() + .start_message_port(self.message_port_id(), can_gc); } /// <https://html.spec.whatwg.org/multipage/#dom-messageport-close> - fn Close(&self) { + fn Close(&self, can_gc: CanGc) { if self.detached.get() { return; } + + // Set this's [[Detached]] internal slot value to true. self.detached.set(true); - self.global().close_message_port(self.message_port_id()); + + let global = self.global(); + global.close_message_port(self.message_port_id()); + + // If this is entangled, disentangle it. + global.disentangle_port(self, can_gc); } /// <https://html.spec.whatwg.org/multipage/#handler-messageport-onmessage> @@ -340,15 +362,19 @@ impl MessagePortMethods<crate::DomTypeHolder> for MessagePort { } /// <https://html.spec.whatwg.org/multipage/#handler-messageport-onmessage> - fn SetOnmessage(&self, listener: Option<Rc<EventHandlerNonNull>>) { + fn SetOnmessage(&self, listener: Option<Rc<EventHandlerNonNull>>, can_gc: CanGc) { if self.detached.get() { return; } self.set_onmessage(listener); // Note: we cannot use the event_handler macro, due to the need to start the port. - self.global().start_message_port(self.message_port_id()); + self.global() + .start_message_port(self.message_port_id(), can_gc); } // <https://html.spec.whatwg.org/multipage/#handler-messageport-onmessageerror> event_handler!(messageerror, GetOnmessageerror, SetOnmessageerror); + + // <https://html.spec.whatwg.org/multipage/#handler-messageport-onclose> + event_handler!(close, GetOnclose, SetOnclose); } diff --git a/components/script/dom/node.rs b/components/script/dom/node.rs index b56126076da..2caec47de25 100644 --- a/components/script/dom/node.rs +++ b/components/script/dom/node.rs @@ -564,7 +564,17 @@ impl Iterator for QuerySelectorIterator { } impl Node { - impl_rare_data!(NodeRareData); + fn rare_data(&self) -> Ref<Option<Box<NodeRareData>>> { + self.rare_data.borrow() + } + + fn ensure_rare_data(&self) -> RefMut<Box<NodeRareData>> { + let mut rare_data = self.rare_data.borrow_mut(); + if rare_data.is_none() { + *rare_data = Some(Default::default()); + } + RefMut::map(rare_data, |rare_data| rare_data.as_mut().unwrap()) + } /// Returns true if this node is before `other` in the same connected DOM /// tree. @@ -1007,24 +1017,25 @@ impl Node { /// <https://dom.spec.whatwg.org/#dom-childnode-replacewith> pub(crate) fn replace_with(&self, nodes: Vec<NodeOrString>, can_gc: CanGc) -> ErrorResult { - // Step 1. - let parent = if let Some(parent) = self.GetParentNode() { - parent - } else { - // Step 2. + // Step 1. Let parent be this’s parent. + let Some(parent) = self.GetParentNode() else { + // Step 2. If parent is null, then return. return Ok(()); }; - // Step 3. + + // Step 3. Let viableNextSibling be this’s first following sibling not in nodes; otherwise null. let viable_next_sibling = first_node_not_in(self.following_siblings(), &nodes); - // Step 4. + + // Step 4. Let node be the result of converting nodes into a node, given nodes and this’s node document. let node = self .owner_doc() .node_from_nodes_and_strings(nodes, can_gc)?; + if self.parent_node == Some(&*parent) { - // Step 5. + // Step 5. If this’s parent is parent, replace this with node within parent. parent.ReplaceChild(&node, self, can_gc)?; } else { - // Step 6. + // Step 6. Otherwise, pre-insert node into parent before viableNextSibling. Node::pre_insert(&node, &parent, viable_next_sibling.as_deref(), can_gc)?; } Ok(()) @@ -1272,6 +1283,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), } } @@ -3172,24 +3198,29 @@ impl NodeMethods<crate::DomTypeHolder> for Node { /// <https://dom.spec.whatwg.org/#concept-node-replace> fn ReplaceChild(&self, node: &Node, child: &Node, can_gc: CanGc) -> Fallible<DomRoot<Node>> { - // Step 1. + // Step 1. If parent is not a Document, DocumentFragment, or Element node, + // then throw a "HierarchyRequestError" DOMException. match self.type_id() { NodeTypeId::Document(_) | NodeTypeId::DocumentFragment(_) | NodeTypeId::Element(..) => { }, _ => return Err(Error::HierarchyRequest), } - // Step 2. + // Step 2. If node is a host-including inclusive ancestor of parent, + // then throw a "HierarchyRequestError" DOMException. if node.is_inclusive_ancestor_of(self) { return Err(Error::HierarchyRequest); } - // Step 3. + // Step 3. If child’s parent is not parent, then throw a "NotFoundError" DOMException. if !self.is_parent_of(child) { return Err(Error::NotFound); } - // Step 4-5. + // Step 4. If node is not a DocumentFragment, DocumentType, Element, or CharacterData node, + // then throw a "HierarchyRequestError" DOMException. + // Step 5. If either node is a Text node and parent is a document, + // or node is a doctype and parent is not a document, then throw a "HierarchyRequestError" DOMException. match node.type_id() { NodeTypeId::CharacterData(CharacterDataTypeId::Text(_)) if self.is::<Document>() => { return Err(Error::HierarchyRequest); @@ -3201,7 +3232,8 @@ impl NodeMethods<crate::DomTypeHolder> for Node { _ => (), } - // Step 6. + // Step 6. If parent is a document, and any of the statements below, switched on the interface node implements, + // are true, then throw a "HierarchyRequestError" DOMException. if self.is::<Document>() { match node.type_id() { // Step 6.1 @@ -3255,7 +3287,8 @@ impl NodeMethods<crate::DomTypeHolder> for Node { } } - // Step 7-8. + // Step 7. Let referenceChild be child’s next sibling. + // Step 8. If referenceChild is node, then set referenceChild to node’s next sibling. let child_next_sibling = child.GetNextSibling(); let node_next_sibling = node.GetNextSibling(); let reference_child = if child_next_sibling.as_deref() == Some(node) { @@ -3264,7 +3297,7 @@ impl NodeMethods<crate::DomTypeHolder> for Node { child_next_sibling.as_deref() }; - // Step 9. + // Step 9. Let previousSibling be child’s previous sibling. let previous_sibling = child.GetPreviousSibling(); // NOTE: All existing browsers assume that adoption is performed here, which does not follow the DOM spec. @@ -3285,7 +3318,7 @@ impl NodeMethods<crate::DomTypeHolder> for Node { None }; - // Step 12. + // Step 12. Let nodes be node’s children if node is a DocumentFragment node; otherwise « node ». rooted_vec!(let mut nodes); let nodes = if node.type_id() == NodeTypeId::DocumentFragment(DocumentFragmentTypeId::DocumentFragment) || @@ -3297,7 +3330,7 @@ impl NodeMethods<crate::DomTypeHolder> for Node { from_ref(&node) }; - // Step 13. + // Step 13. Insert node into parent before referenceChild with the suppress observers flag set. Node::insert( node, self, @@ -3306,13 +3339,15 @@ impl NodeMethods<crate::DomTypeHolder> for Node { can_gc, ); - // Step 14. vtable_for(self).children_changed(&ChildrenMutation::replace( previous_sibling.as_deref(), &removed_child, nodes, reference_child, )); + + // Step 14. Queue a tree mutation record for parent with nodes, removedNodes, + // previousSibling, and referenceChild. let removed = removed_child.map(|r| [r]); let mutation = LazyCell::new(|| Mutation::ChildList { added: Some(nodes), @@ -3323,7 +3358,7 @@ impl NodeMethods<crate::DomTypeHolder> for Node { MutationObserver::queue_a_mutation_record(self, mutation); - // Step 15. + // Step 15. Return child. Ok(DomRoot::from_ref(child)) } 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/readablestream.rs b/components/script/dom/readablestream.rs index 51393ab33ae..4982bfa32e3 100644 --- a/components/script/dom/readablestream.rs +++ b/components/script/dom/readablestream.rs @@ -1825,7 +1825,7 @@ impl ReadableStream { global.note_cross_realm_transform_readable(&cross_realm_transform_readable, port_id); // Enable port’s port message queue. - port.Start(); + port.Start(can_gc); // Perform ! SetUpReadableStreamDefaultController controller @@ -2093,7 +2093,7 @@ impl CrossRealmTransformReadable { self.controller.close(can_gc); // Disentangle port. - global.disentangle_port(port); + global.disentangle_port(port, can_gc); } // Otherwise, if type is "error", @@ -2102,7 +2102,7 @@ impl CrossRealmTransformReadable { self.controller.error(value.handle(), can_gc); // Disentangle port. - global.disentangle_port(port); + global.disentangle_port(port, can_gc); } } @@ -2129,7 +2129,7 @@ impl CrossRealmTransformReadable { self.controller.error(rooted_error.handle(), can_gc); // Disentangle port. - global.disentangle_port(port); + global.disentangle_port(port, can_gc); } } diff --git a/components/script/dom/underlyingsourcecontainer.rs b/components/script/dom/underlyingsourcecontainer.rs index 541a831693a..4acb58bafef 100644 --- a/components/script/dom/underlyingsourcecontainer.rs +++ b/components/script/dom/underlyingsourcecontainer.rs @@ -151,7 +151,7 @@ impl UnderlyingSourceContainer { let result = port.pack_and_post_message_handling_error("error", reason, can_gc); // Disentangle port. - self.global().disentangle_port(port); + self.global().disentangle_port(port, can_gc); let promise = Promise::new(&self.global(), can_gc); diff --git a/components/script/dom/window.rs b/components/script/dom/window.rs index e210476a5df..a685bbb25f2 100644 --- a/components/script/dom/window.rs +++ b/components/script/dom/window.rs @@ -787,6 +787,32 @@ impl WindowMethods<crate::DomTypeHolder> for Window { doc.abort(can_gc); } + /// <https://html.spec.whatwg.org/multipage/#dom-window-focus> + fn Focus(&self) { + // > 1. Let `current` be this `Window` object's browsing context. + // > + // > 2. If `current` is null, then return. + let current = match self.undiscarded_window_proxy() { + Some(proxy) => proxy, + None => return, + }; + + // > 3. Run the focusing steps with `current`. + current.focus(); + + // > 4. If current is a top-level browsing context, user agents are + // > encouraged to trigger some sort of notification to indicate to + // > the user that the page is attempting to gain focus. + // + // TODO: Step 4 + } + + // https://html.spec.whatwg.org/multipage/#dom-window-blur + fn Blur(&self) { + // > User agents are encouraged to ignore calls to this `blur()` method + // > entirely. + } + // https://html.spec.whatwg.org/multipage/#dom-open fn Open( &self, diff --git a/components/script/dom/windowproxy.rs b/components/script/dom/windowproxy.rs index e3fc81bf7ec..dc02f9feb49 100644 --- a/components/script/dom/windowproxy.rs +++ b/components/script/dom/windowproxy.rs @@ -620,6 +620,23 @@ impl WindowProxy { result } + /// Run [the focusing steps] with this browsing context. + /// + /// [the focusing steps]: https://html.spec.whatwg.org/multipage/#focusing-steps + pub fn focus(&self) { + debug!( + "Requesting the constellation to initiate a focus operation for \ + browsing context {}", + self.browsing_context_id() + ); + self.global() + .script_to_constellation_chan() + .send(ScriptToConstellationMessage::FocusRemoteDocument( + self.browsing_context_id(), + )) + .unwrap(); + } + #[allow(unsafe_code)] /// Change the Window that this WindowProxy resolves to. // TODO: support setting the window proxy to a dummy value, diff --git a/components/script/dom/writablestream.rs b/components/script/dom/writablestream.rs index 8c2b2434cd2..1b029f592de 100644 --- a/components/script/dom/writablestream.rs +++ b/components/script/dom/writablestream.rs @@ -893,7 +893,7 @@ impl WritableStream { global.note_cross_realm_transform_writable(&cross_realm_transform_writable, port_id); // Enable port’s port message queue. - port.Start(); + port.Start(can_gc); // Perform ! SetUpWritableStreamDefaultController controller @@ -1202,7 +1202,7 @@ impl CrossRealmTransformWritable { .error_if_needed(cx, rooted_error.handle(), global, can_gc); // Disentangle port. - global.disentangle_port(port); + global.disentangle_port(port, can_gc); } } diff --git a/components/script/dom/writablestreamdefaultcontroller.rs b/components/script/dom/writablestreamdefaultcontroller.rs index 301404ffdb2..084165a6892 100644 --- a/components/script/dom/writablestreamdefaultcontroller.rs +++ b/components/script/dom/writablestreamdefaultcontroller.rs @@ -173,11 +173,11 @@ impl Callback for TransferBackPressurePromiseReaction { self.port .pack_and_post_message_handling_error("chunk", chunk.handle(), can_gc); - // Disentangle port. - global.disentangle_port(&self.port); - // If result is an abrupt completion, if let Err(error) = result { + // Disentangle port. + global.disentangle_port(&self.port, can_gc); + // Return a promise rejected with result.[[Value]]. self.result_promise.reject_error(error, can_gc); } else { @@ -609,7 +609,7 @@ impl WritableStreamDefaultController { let result = port.pack_and_post_message_handling_error("error", reason, can_gc); // Disentangle port. - global.disentangle_port(port); + global.disentangle_port(port, can_gc); let promise = Promise::new(global, can_gc); @@ -752,7 +752,7 @@ impl WritableStreamDefaultController { .expect("Sending close should not fail."); // Disentangle port. - global.disentangle_port(port); + global.disentangle_port(port, can_gc); // Return a promise resolved with undefined. Promise::new_resolved(global, cx, (), can_gc) diff --git a/components/script/messaging.rs b/components/script/messaging.rs index 7d0b7aabe05..e0ea9e30af2 100644 --- a/components/script/messaging.rs +++ b/components/script/messaging.rs @@ -72,6 +72,8 @@ impl MixedMessage { ScriptThreadMessage::UpdateHistoryState(id, ..) => Some(*id), ScriptThreadMessage::RemoveHistoryStates(id, ..) => Some(*id), ScriptThreadMessage::FocusIFrame(id, ..) => Some(*id), + ScriptThreadMessage::FocusDocument(id, ..) => Some(*id), + ScriptThreadMessage::Unfocus(id, ..) => Some(*id), ScriptThreadMessage::WebDriverScriptCommand(id, ..) => Some(*id), ScriptThreadMessage::TickAllAnimations(..) => None, ScriptThreadMessage::WebFontLoaded(id, ..) => Some(*id), 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/script_thread.rs b/components/script/script_thread.rs index 9c93bef22df..2129979ad42 100644 --- a/components/script/script_thread.rs +++ b/components/script/script_thread.rs @@ -50,8 +50,9 @@ use devtools_traits::{ }; use embedder_traits::user_content_manager::UserContentManager; use embedder_traits::{ - CompositorHitTestResult, EmbedderMsg, InputEvent, MediaSessionActionType, MouseButton, - MouseButtonAction, MouseButtonEvent, Theme, ViewportDetails, WebDriverScriptCommand, + CompositorHitTestResult, EmbedderMsg, FocusSequenceNumber, InputEvent, MediaSessionActionType, + MouseButton, MouseButtonAction, MouseButtonEvent, Theme, ViewportDetails, + WebDriverScriptCommand, }; use euclid::Point2D; use euclid::default::Rect; @@ -124,7 +125,7 @@ use crate::dom::customelementregistry::{ CallbackReaction, CustomElementDefinition, CustomElementReactionStack, }; use crate::dom::document::{ - Document, DocumentSource, FocusType, HasBrowsingContext, IsHTMLDocument, TouchEventResult, + Document, DocumentSource, FocusInitiator, HasBrowsingContext, IsHTMLDocument, TouchEventResult, }; use crate::dom::element::Element; use crate::dom::globalscope::GlobalScope; @@ -1803,8 +1804,14 @@ impl ScriptThread { ScriptThreadMessage::RemoveHistoryStates(pipeline_id, history_states) => { self.handle_remove_history_states(pipeline_id, history_states) }, - ScriptThreadMessage::FocusIFrame(parent_pipeline_id, frame_id) => { - self.handle_focus_iframe_msg(parent_pipeline_id, frame_id, can_gc) + ScriptThreadMessage::FocusIFrame(parent_pipeline_id, frame_id, sequence) => { + self.handle_focus_iframe_msg(parent_pipeline_id, frame_id, sequence, can_gc) + }, + ScriptThreadMessage::FocusDocument(pipeline_id, sequence) => { + self.handle_focus_document_msg(pipeline_id, sequence, can_gc) + }, + ScriptThreadMessage::Unfocus(pipeline_id, sequence) => { + self.handle_unfocus_msg(pipeline_id, sequence, can_gc) }, ScriptThreadMessage::WebDriverScriptCommand(pipeline_id, msg) => { self.handle_webdriver_msg(pipeline_id, msg, can_gc) @@ -2513,6 +2520,7 @@ impl ScriptThread { &self, parent_pipeline_id: PipelineId, browsing_context_id: BrowsingContextId, + sequence: FocusSequenceNumber, can_gc: CanGc, ) { let document = self @@ -2532,7 +2540,65 @@ impl ScriptThread { return; }; - document.request_focus(Some(&iframe_element_root), FocusType::Parent, can_gc); + if document.get_focus_sequence() > sequence { + debug!( + "Disregarding the FocusIFrame message because the contained sequence number is \ + too old ({:?} < {:?})", + sequence, + document.get_focus_sequence() + ); + return; + } + + document.request_focus(Some(&iframe_element_root), FocusInitiator::Remote, can_gc); + } + + fn handle_focus_document_msg( + &self, + pipeline_id: PipelineId, + sequence: FocusSequenceNumber, + can_gc: CanGc, + ) { + if let Some(doc) = self.documents.borrow().find_document(pipeline_id) { + if doc.get_focus_sequence() > sequence { + debug!( + "Disregarding the FocusDocument message because the contained sequence number is \ + too old ({:?} < {:?})", + sequence, + doc.get_focus_sequence() + ); + return; + } + doc.request_focus(None, FocusInitiator::Remote, can_gc); + } else { + warn!( + "Couldn't find document by pipleline_id:{pipeline_id:?} when handle_focus_document_msg." + ); + } + } + + fn handle_unfocus_msg( + &self, + pipeline_id: PipelineId, + sequence: FocusSequenceNumber, + can_gc: CanGc, + ) { + if let Some(doc) = self.documents.borrow().find_document(pipeline_id) { + if doc.get_focus_sequence() > sequence { + debug!( + "Disregarding the Unfocus message because the contained sequence number is \ + too old ({:?} < {:?})", + sequence, + doc.get_focus_sequence() + ); + return; + } + doc.handle_container_unfocus(can_gc); + } else { + warn!( + "Couldn't find document by pipleline_id:{pipeline_id:?} when handle_unfocus_msg." + ); + } } fn handle_post_message_msg( diff --git a/components/script_bindings/codegen/Bindings.conf b/components/script_bindings/codegen/Bindings.conf index c50bc31a7f5..4ab0b21cabe 100644 --- a/components/script_bindings/codegen/Bindings.conf +++ b/components/script_bindings/codegen/Bindings.conf @@ -474,7 +474,7 @@ DOMInterfaces = { 'MessagePort': { 'weakReferenceable': True, - 'canGc': ['GetOnmessage'], + 'canGc': ['Close', 'GetOnmessage', 'SetOnmessage', 'Start'], }, 'MessageEvent': { diff --git a/components/script_bindings/webidls/MessagePort.webidl b/components/script_bindings/webidls/MessagePort.webidl index 6fc1f432b38..b7082fc7fc3 100644 --- a/components/script_bindings/webidls/MessagePort.webidl +++ b/components/script_bindings/webidls/MessagePort.webidl @@ -16,6 +16,7 @@ interface MessagePort : EventTarget { // event handlers attribute EventHandler onmessage; attribute EventHandler onmessageerror; + attribute EventHandler onclose; }; dictionary StructuredSerializeOptions { diff --git a/components/script_bindings/webidls/Window.webidl b/components/script_bindings/webidls/Window.webidl index 81c442b119f..eb7c3e1d03d 100644 --- a/components/script_bindings/webidls/Window.webidl +++ b/components/script_bindings/webidls/Window.webidl @@ -27,8 +27,8 @@ [CrossOriginCallable] undefined close(); [CrossOriginReadable] readonly attribute boolean closed; undefined stop(); - //[CrossOriginCallable] void focus(); - //[CrossOriginCallable] void blur(); + [CrossOriginCallable] undefined focus(); + [CrossOriginCallable] undefined blur(); // other browsing contexts [Replaceable, CrossOriginReadable] readonly attribute WindowProxy frames; diff --git a/components/servo/Cargo.toml b/components/servo/Cargo.toml index 498d170492d..b49f60e742a 100644 --- a/components/servo/Cargo.toml +++ b/components/servo/Cargo.toml @@ -46,14 +46,14 @@ tracing = [ webdriver = ["webdriver_server"] webgl_backtrace = [ "script/webgl_backtrace", - "canvas/webgl_backtrace", + "webgl/webgl_backtrace", "canvas_traits/webgl_backtrace", ] webxr = [ "dep:webxr", "dep:webxr-api", "compositing/webxr", - "canvas/webxr", + "webgl/webxr", "script/webxr", ] webgpu = [ @@ -68,7 +68,8 @@ base = { workspace = true } bincode = { workspace = true } bluetooth = { path = "../bluetooth", optional = true } bluetooth_traits = { workspace = true, optional = true } -canvas = { path = "../canvas", default-features = false } +canvas = { path = "../canvas" } +webgl = { path = "../webgl", default-features = false } canvas_traits = { workspace = true } cfg-if = { workspace = true } compositing = { path = "../compositing" } diff --git a/components/servo/lib.rs b/components/servo/lib.rs index 7fb990527ec..366685e1123 100644 --- a/components/servo/lib.rs +++ b/components/servo/lib.rs @@ -38,7 +38,6 @@ use base::id::{PipelineNamespace, PipelineNamespaceId}; use bluetooth::BluetoothThreadFactory; #[cfg(feature = "bluetooth")] use bluetooth_traits::BluetoothRequest; -use canvas::WebGLComm; use canvas::canvas_paint_thread::CanvasPaintThread; use canvas_traits::webgl::{GlType, WebGLThreads}; use clipboard_delegate::StringRequest; @@ -99,6 +98,7 @@ use servo_delegate::DefaultServoDelegate; use servo_media::ServoMedia; use servo_media::player::context::GlContext; use servo_url::ServoUrl; +use webgl::WebGLComm; #[cfg(feature = "webgpu")] pub use webgpu; #[cfg(feature = "webgpu")] @@ -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/constellation/from_script_message.rs b/components/shared/constellation/from_script_message.rs index 8346551fd15..ddc9f788617 100644 --- a/components/shared/constellation/from_script_message.rs +++ b/components/shared/constellation/from_script_message.rs @@ -4,7 +4,7 @@ //! Messages send from the ScriptThread to the Constellation. -use std::collections::{HashMap, VecDeque}; +use std::collections::HashMap; use std::fmt; use base::Epoch; @@ -15,7 +15,8 @@ use base::id::{ use canvas_traits::canvas::{CanvasId, CanvasMsg}; use devtools_traits::{DevtoolScriptControlMsg, ScriptToDevtoolsControlMsg, WorkerId}; use embedder_traits::{ - AnimationState, EmbedderMsg, MediaSessionEvent, TouchEventResult, ViewportDetails, + AnimationState, EmbedderMsg, FocusSequenceNumber, MediaSessionEvent, TouchEventResult, + ViewportDetails, }; use euclid::default::Size2D as UntypedSize2D; use http::{HeaderMap, Method}; @@ -34,7 +35,9 @@ use webgpu_traits::{WebGPU, WebGPUAdapterResponse}; use webrender_api::ImageKey; use crate::structured_data::{BroadcastMsg, StructuredSerializedData}; -use crate::{LogEntry, MessagePortMsg, PortMessageTask, TraversalDirection, WindowSizeType}; +use crate::{ + LogEntry, MessagePortMsg, PortMessageTask, PortTransferInfo, TraversalDirection, WindowSizeType, +}; /// A Script to Constellation channel. #[derive(Clone, Debug, Deserialize, Serialize)] @@ -469,7 +472,7 @@ pub enum ScriptToConstellationMessage { /* The ids of ports transferred successfully */ Vec<MessagePortId>, /* The ids, and buffers, of ports whose transfer failed */ - HashMap<MessagePortId, VecDeque<PortMessageTask>>, + HashMap<MessagePortId, PortTransferInfo>, ), /// A new message-port was created or transferred, with corresponding control-sender. NewMessagePort(MessagePortRouterId, MessagePortId), @@ -481,10 +484,14 @@ pub enum ScriptToConstellationMessage { RerouteMessagePort(MessagePortId, PortMessageTask), /// A message-port was shipped, let the entangled port know. MessagePortShipped(MessagePortId), - /// A message-port has been discarded by script. - RemoveMessagePort(MessagePortId), /// Entangle two message-ports. EntanglePorts(MessagePortId, MessagePortId), + /// Disentangle two message-ports. + /// The first is the initiator, the second the other port, + /// unless the message is sent to complete a disentanglement, + /// in which case the first one is the other port, + /// and the second is none. + DisentanglePorts(MessagePortId, Option<MessagePortId>), /// A global has started managing broadcast-channels. NewBroadcastChannelRouter( BroadcastChannelRouterId, @@ -519,8 +526,21 @@ pub enum ScriptToConstellationMessage { UntypedSize2D<u64>, IpcSender<(IpcSender<CanvasMsg>, CanvasId, ImageKey)>, ), - /// Notifies the constellation that this frame has received focus. - Focus, + /// Notifies the constellation that this pipeline is requesting focus. + /// + /// When this message is sent, the sender pipeline has already its local + /// focus state updated. The constellation, after receiving this message, + /// will broadcast messages to other pipelines that are affected by this + /// focus operation. + /// + /// The first field contains the browsing context ID of the container + /// element if one was focused. + /// + /// The second field is a sequence number that the constellation should use + /// when sending a focus-related message to the sender pipeline next time. + Focus(Option<BrowsingContextId>, FocusSequenceNumber), + /// Requests the constellation to focus the specified browsing context. + FocusRemoteDocument(BrowsingContextId), /// Get the top-level browsing context info for a given browsing context. GetTopForBrowsingContext(BrowsingContextId, IpcSender<Option<WebViewId>>), /// Get the browsing context id of the browsing context in which pipeline is diff --git a/components/shared/constellation/lib.rs b/components/shared/constellation/lib.rs index b3d4fe525a1..559bc2dd2d1 100644 --- a/components/shared/constellation/lib.rs +++ b/components/shared/constellation/lib.rs @@ -157,18 +157,29 @@ pub struct PortMessageTask { pub data: StructuredSerializedData, } +/// The information needed by a global to process the transfer of a port. +#[derive(Debug, Deserialize, MallocSizeOf, Serialize)] +pub struct PortTransferInfo { + /// <https://html.spec.whatwg.org/multipage/#port-message-queue> + pub port_message_queue: VecDeque<PortMessageTask>, + /// A boolean indicating whether the port has been disentangled while in transfer, + /// if so, the disentanglement should be completed along with the transfer. + /// <https://html.spec.whatwg.org/multipage/#disentangle> + pub disentangled: bool, +} + /// Messages for communication between the constellation and a global managing ports. #[derive(Debug, Deserialize, Serialize)] #[allow(clippy::large_enum_variant)] pub enum MessagePortMsg { /// Complete the transfer for a batch of ports. - CompleteTransfer(HashMap<MessagePortId, VecDeque<PortMessageTask>>), + CompleteTransfer(HashMap<MessagePortId, PortTransferInfo>), /// Complete the transfer of a single port, /// whose transfer was pending because it had been requested /// while a previous failed transfer was being rolled-back. - CompletePendingTransfer(MessagePortId, VecDeque<PortMessageTask>), - /// Remove a port, the entangled one doesn't exists anymore. - RemoveMessagePort(MessagePortId), + CompletePendingTransfer(MessagePortId, PortTransferInfo), + /// <https://html.spec.whatwg.org/multipage/#disentangle> + CompleteDisentanglement(MessagePortId), /// Handle a new port-message-task. NewTask(MessagePortId, PortMessageTask), } diff --git a/components/shared/constellation/structured_data/transferable.rs b/components/shared/constellation/structured_data/transferable.rs index cd6388f5f34..7e4fe0e6d2d 100644 --- a/components/shared/constellation/structured_data/transferable.rs +++ b/components/shared/constellation/structured_data/transferable.rs @@ -77,7 +77,12 @@ impl MessagePortImpl { self.entangled_port } - /// Entanged this port with another. + /// <https://html.spec.whatwg.org/multipage/#disentangle> + pub fn disentangle(&mut self) -> Option<MessagePortId> { + self.entangled_port.take() + } + + /// <https://html.spec.whatwg.org/multipage/#entangle> pub fn entangle(&mut self, other_id: MessagePortId) { self.entangled_port = Some(other_id); } 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/embedder/lib.rs b/components/shared/embedder/lib.rs index 5f1171859dc..c87fa9019ef 100644 --- a/components/shared/embedder/lib.rs +++ b/components/shared/embedder/lib.rs @@ -14,7 +14,7 @@ pub mod user_content_manager; mod webdriver; use std::ffi::c_void; -use std::fmt::{Debug, Error, Formatter}; +use std::fmt::{Debug, Display, Error, Formatter}; use std::path::PathBuf; use std::sync::Arc; @@ -784,3 +784,76 @@ pub enum AnimationState { /// No animations are active but callbacks are queued NoAnimationCallbacksPresent, } + +/// A sequence number generated by a script thread for its pipelines. The +/// constellation attaches the target pipeline's last seen `FocusSequenceNumber` +/// to every focus-related message it sends. +/// +/// This is used to resolve the inconsistency that occurs due to bidirectional +/// focus state synchronization and provide eventual consistency. Example: +/// +/// ```text +/// script constellation +/// ----------------------------------------------------------------------- +/// send ActivateDocument ----------> receive ActivateDocument +/// ,---- send FocusDocument +/// | +/// focus an iframe | +/// send Focus -----------------|---> receive Focus +/// | focus the iframe's content document +/// receive FocusDocument <-----' send FocusDocument to the content pipeline --> ... +/// unfocus the iframe +/// focus the document +/// +/// Final state: Final state: +/// the iframe is not focused the iframe is focused +/// ``` +/// +/// When the above sequence completes, from the script thread's point of view, +/// the iframe is unfocused, but from the constellation's point of view, the +/// iframe is still focused. +/// +/// This inconsistency can be resolved by associating a sequence number to each +/// message. Whenever a script thread initiates a focus operation, it generates +/// and sends a brand new sequence number. The constellation attaches the +/// last-received sequence number to each message it sends. This way, the script +/// thread can discard out-dated incoming focus messages, and eventually, all +/// actors converge to the consistent state which is determined based on the +/// last focus message received by the constellation. +/// +/// ```text +/// script constellation +/// ----------------------------------------------------------------------- +/// send ActivateDocument ----------> receive ActivateDocument +/// ,---- send FocusDocument (0) +/// | +/// seq_number += 1 | +/// focus an iframe | +/// send Focus (1) -------------|---> receive Focus (1) +/// | focus the iframe's content document +/// receive FocusDocument (0) <-' send FocusDocument to the content pipeline --> ... +/// ignore it because 0 < 1 +/// +/// Final state: Final state: +/// the iframe is focused the iframe is focused +/// ``` +#[derive( + Clone, + Copy, + Debug, + Default, + Deserialize, + Eq, + Hash, + MallocSizeOf, + PartialEq, + Serialize, + PartialOrd, +)] +pub struct FocusSequenceNumber(pub u64); + +impl Display for FocusSequenceNumber { + fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> { + Display::fmt(&self.0, f) + } +} diff --git a/components/shared/script/lib.rs b/components/shared/script/lib.rs index 7323907cba3..748c42400a8 100644 --- a/components/shared/script/lib.rs +++ b/components/shared/script/lib.rs @@ -27,8 +27,8 @@ use crossbeam_channel::{RecvTimeoutError, Sender}; use devtools_traits::ScriptToDevtoolsControlMsg; use embedder_traits::user_content_manager::UserContentManager; use embedder_traits::{ - CompositorHitTestResult, InputEvent, MediaSessionActionType, Theme, ViewportDetails, - WebDriverScriptCommand, + CompositorHitTestResult, FocusSequenceNumber, InputEvent, MediaSessionActionType, Theme, + ViewportDetails, WebDriverScriptCommand, }; use euclid::{Rect, Scale, Size2D, UnknownUnit}; use ipc_channel::ipc::{IpcReceiver, IpcSender}; @@ -191,7 +191,15 @@ pub enum ScriptThreadMessage { RemoveHistoryStates(PipelineId, Vec<HistoryStateId>), /// Set an iframe to be focused. Used when an element in an iframe gains focus. /// PipelineId is for the parent, BrowsingContextId is for the nested browsing context - FocusIFrame(PipelineId, BrowsingContextId), + FocusIFrame(PipelineId, BrowsingContextId, FocusSequenceNumber), + /// Focus the document. Used when the container gains focus. + FocusDocument(PipelineId, FocusSequenceNumber), + /// Notifies that the document's container (e.g., an iframe) is not included + /// in the top-level browsing context's focus chain (not considering system + /// focus) anymore. + /// + /// Obviously, this message is invalid for a top-level document. + Unfocus(PipelineId, FocusSequenceNumber), /// Passes a webdriver command to the script thread for execution WebDriverScriptCommand(PipelineId, WebDriverScriptCommand), /// Notifies script thread that all animations are done diff --git a/components/webgl/Cargo.toml b/components/webgl/Cargo.toml new file mode 100644 index 00000000000..b0c1c0ceb29 --- /dev/null +++ b/components/webgl/Cargo.toml @@ -0,0 +1,36 @@ +[package] +name = "webgl" +version.workspace = true +authors.workspace = true +license.workspace = true +edition.workspace = true +publish.workspace = true +rust-version.workspace = true + +[lib] +name = "webgl" +path = "lib.rs" + +[features] +webgl_backtrace = ["canvas_traits/webgl_backtrace"] +webxr = ["dep:webxr", "dep:webxr-api"] + +[dependencies] +bitflags = { workspace = true } +byteorder = { workspace = true } +canvas_traits = { workspace = true } +compositing_traits = { workspace = true } +crossbeam-channel = { workspace = true } +euclid = { workspace = true } +fnv = { workspace = true } +glow = { workspace = true } +half = "2" +ipc-channel = { workspace = true } +log = { workspace = true } +pixels = { path = "../pixels" } +snapshot = { workspace = true } +surfman = { workspace = true } +webrender = { workspace = true } +webrender_api = { workspace = true } +webxr = { path = "../webxr", features = ["ipc"], optional = true } +webxr-api = { workspace = true, features = ["ipc"], optional = true } diff --git a/components/webgl/lib.rs b/components/webgl/lib.rs new file mode 100644 index 00000000000..923e7faad24 --- /dev/null +++ b/components/webgl/lib.rs @@ -0,0 +1,13 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +#![deny(unsafe_code)] + +pub use webgl_mode::WebGLComm; + +mod webgl_limits; +mod webgl_mode; +pub mod webgl_thread; +#[cfg(feature = "webxr")] +mod webxr; diff --git a/components/canvas/webgl_limits.rs b/components/webgl/webgl_limits.rs index f683b6efff6..f683b6efff6 100644 --- a/components/canvas/webgl_limits.rs +++ b/components/webgl/webgl_limits.rs diff --git a/components/canvas/webgl_mode/inprocess.rs b/components/webgl/webgl_mode/inprocess.rs index 566da2c58c8..566da2c58c8 100644 --- a/components/canvas/webgl_mode/inprocess.rs +++ b/components/webgl/webgl_mode/inprocess.rs diff --git a/components/canvas/webgl_mode/mod.rs b/components/webgl/webgl_mode/mod.rs index 8bc74f6e244..8bc74f6e244 100644 --- a/components/canvas/webgl_mode/mod.rs +++ b/components/webgl/webgl_mode/mod.rs diff --git a/components/canvas/webgl_thread.rs b/components/webgl/webgl_thread.rs index b1ac2b2d3c4..b1ac2b2d3c4 100644 --- a/components/canvas/webgl_thread.rs +++ b/components/webgl/webgl_thread.rs diff --git a/components/canvas/webxr.rs b/components/webgl/webxr.rs index d43303e7393..d43303e7393 100644 --- a/components/canvas/webxr.rs +++ b/components/webgl/webxr.rs |