aboutsummaryrefslogtreecommitdiffstats
path: root/components
diff options
context:
space:
mode:
Diffstat (limited to 'components')
-rw-r--r--components/canvas/Cargo.toml13
-rw-r--r--components/canvas/backend.rs242
-rw-r--r--components/canvas/canvas_data.rs494
-rw-r--r--components/canvas/canvas_paint_thread.rs359
-rw-r--r--components/canvas/lib.rs8
-rw-r--r--components/canvas/raqote_backend.rs529
-rw-r--r--components/constellation/constellation.rs524
-rw-r--r--components/constellation/pipeline.rs5
-rw-r--r--components/constellation/tracing.rs5
-rw-r--r--components/devtools/actors/inspector/node.rs15
-rw-r--r--components/net/async_runtime.rs40
-rw-r--r--components/net/connector.rs2
-rw-r--r--components/net/http_loader.rs4
-rw-r--r--components/net/resource_thread.rs2
-rw-r--r--components/net/websocket_loader.rs33
-rw-r--r--components/script/canvas_context.rs182
-rw-r--r--components/script/canvas_state.rs13
-rw-r--r--components/script/dom/dissimilaroriginwindow.rs7
-rw-r--r--components/script/dom/document.rs447
-rw-r--r--components/script/dom/element.rs16
-rw-r--r--components/script/dom/globalscope.rs199
-rw-r--r--components/script/dom/htmlcanvaselement.rs99
-rw-r--r--components/script/dom/htmldetailselement.rs4
-rw-r--r--components/script/dom/htmlelement.rs9
-rw-r--r--components/script/dom/macros.rs23
-rw-r--r--components/script/dom/messageport.rs38
-rw-r--r--components/script/dom/node.rs77
-rw-r--r--components/script/dom/nodelist.rs1
-rw-r--r--components/script/dom/offscreencanvas.rs58
-rw-r--r--components/script/dom/offscreencanvasrenderingcontext2d.rs29
-rw-r--r--components/script/dom/readablestream.rs8
-rw-r--r--components/script/dom/underlyingsourcecontainer.rs2
-rw-r--r--components/script/dom/window.rs26
-rw-r--r--components/script/dom/windowproxy.rs17
-rw-r--r--components/script/dom/writablestream.rs4
-rw-r--r--components/script/dom/writablestreamdefaultcontroller.rs10
-rw-r--r--components/script/messaging.rs2
-rw-r--r--components/script/script_module.rs5
-rw-r--r--components/script/script_thread.rs78
-rw-r--r--components/script_bindings/codegen/Bindings.conf2
-rw-r--r--components/script_bindings/webidls/MessagePort.webidl1
-rw-r--r--components/script_bindings/webidls/Window.webidl4
-rw-r--r--components/servo/Cargo.toml7
-rw-r--r--components/servo/lib.rs10
-rw-r--r--components/shared/constellation/from_script_message.rs36
-rw-r--r--components/shared/constellation/lib.rs19
-rw-r--r--components/shared/constellation/structured_data/transferable.rs7
-rw-r--r--components/shared/devtools/lib.rs9
-rw-r--r--components/shared/embedder/lib.rs75
-rw-r--r--components/shared/script/lib.rs14
-rw-r--r--components/webgl/Cargo.toml36
-rw-r--r--components/webgl/lib.rs13
-rw-r--r--components/webgl/webgl_limits.rs (renamed from components/canvas/webgl_limits.rs)0
-rw-r--r--components/webgl/webgl_mode/inprocess.rs (renamed from components/canvas/webgl_mode/inprocess.rs)0
-rw-r--r--components/webgl/webgl_mode/mod.rs (renamed from components/canvas/webgl_mode/mod.rs)0
-rw-r--r--components/webgl/webgl_thread.rs (renamed from components/canvas/webgl_thread.rs)0
-rw-r--r--components/webgl/webxr.rs (renamed from components/canvas/webxr.rs)0
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