diff options
Diffstat (limited to 'components')
49 files changed, 1668 insertions, 1321 deletions
diff --git a/components/config/prefs.rs b/components/config/prefs.rs index a9ec112e3eb..64dd9659e56 100644 --- a/components/config/prefs.rs +++ b/components/config/prefs.rs @@ -99,7 +99,6 @@ pub struct Preferences { pub dom_serviceworker_timeout_seconds: i64, pub dom_servo_helpers_enabled: bool, pub dom_servoparser_async_html_tokenizer_enabled: bool, - pub dom_shadowdom_enabled: bool, pub dom_svg_enabled: bool, pub dom_testable_crash_enabled: bool, pub dom_testbinding_enabled: bool, @@ -277,7 +276,6 @@ impl Preferences { dom_serviceworker_timeout_seconds: 60, dom_servo_helpers_enabled: false, dom_servoparser_async_html_tokenizer_enabled: false, - dom_shadowdom_enabled: true, dom_svg_enabled: false, dom_testable_crash_enabled: false, dom_testbinding_enabled: false, diff --git a/components/layout/display_list/background.rs b/components/layout/display_list/background.rs index f49ddfbe6ce..563bce28450 100644 --- a/components/layout/display_list/background.rs +++ b/components/layout/display_list/background.rs @@ -66,7 +66,7 @@ impl<'a> BackgroundPainter<'a> { if &BackgroundAttachment::Fixed == get_cyclic(&background.background_attachment.0, layer_index) { - let viewport_size = builder.display_list.compositor_info.viewport_size; + let viewport_size = builder.compositor_info.viewport_size; return units::LayoutRect::from_origin_and_size(Point2D::origin(), viewport_size); } @@ -121,7 +121,7 @@ impl<'a> BackgroundPainter<'a> { if &BackgroundAttachment::Fixed == get_cyclic(&style.get_background().background_attachment.0, layer_index) { - common.spatial_id = builder.current_reference_frame_scroll_node_id.spatial_id; + common.spatial_id = builder.spatial_id(builder.current_reference_frame_scroll_node_id); } common } diff --git a/components/layout/display_list/clip.rs b/components/layout/display_list/clip.rs new file mode 100644 index 00000000000..d5bd0f52b69 --- /dev/null +++ b/components/layout/display_list/clip.rs @@ -0,0 +1,276 @@ +/* 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 app_units::Au; +use base::id::ScrollTreeNodeId; +use style::values::computed::basic_shape::{BasicShape, ClipPath}; +use style::values::computed::length_percentage::NonNegativeLengthPercentage; +use style::values::computed::position::Position; +use style::values::generics::basic_shape::{GenericShapeRadius, ShapeBox, ShapeGeometryBox}; +use style::values::generics::position::GenericPositionOrAuto; +use webrender_api::BorderRadius; +use webrender_api::units::{LayoutRect, LayoutSideOffsets, LayoutSize}; + +use super::{BuilderForBoxFragment, compute_margin_box_radius, normalize_radii}; + +/// An identifier for a clip used during StackingContextTree construction. This is a simple index in +/// a [`ClipStore`]s vector of clips. +#[derive(Clone, Copy, Debug, PartialEq)] +pub(crate) struct ClipId(pub usize); + +impl ClipId { + /// Equivalent to [`ClipChainId::INVALID`]. This means "no clip." + pub(crate) const INVALID: ClipId = ClipId(usize::MAX); +} + +/// All the information needed to create a clip on a WebRender display list. These are created at +/// two times: during `StackingContextTree` creation and during WebRender display list construction. +/// Only the former are stored in a [`ClipStore`]. +#[derive(Clone)] +pub(crate) struct Clip { + pub id: ClipId, + pub radii: BorderRadius, + pub rect: LayoutRect, + pub parent_scroll_node_id: ScrollTreeNodeId, + pub parent_clip_id: ClipId, +} + +/// A simple vector of [`Clip`] that is built during `StackingContextTree` construction. +/// These are later turned into WebRender clips and clip chains during WebRender display +/// list construction. +#[derive(Clone, Default)] +pub(crate) struct StackingContextTreeClipStore(pub Vec<Clip>); + +impl StackingContextTreeClipStore { + pub(crate) fn add( + &mut self, + radii: webrender_api::BorderRadius, + rect: LayoutRect, + parent_scroll_node_id: ScrollTreeNodeId, + parent_clip_id: ClipId, + ) -> ClipId { + let id = ClipId(self.0.len()); + self.0.push(Clip { + id, + radii, + rect, + parent_scroll_node_id, + parent_clip_id, + }); + id + } + + pub(super) fn add_for_clip_path( + &mut self, + clip_path: ClipPath, + parent_scroll_node_id: &ScrollTreeNodeId, + parent_clip_chain_id: &ClipId, + fragment_builder: BuilderForBoxFragment, + ) -> Option<ClipId> { + let geometry_box = match clip_path { + ClipPath::Shape(_, ShapeGeometryBox::ShapeBox(shape_box)) => shape_box, + ClipPath::Shape(_, ShapeGeometryBox::ElementDependent) => ShapeBox::BorderBox, + ClipPath::Box(ShapeGeometryBox::ShapeBox(shape_box)) => shape_box, + ClipPath::Box(ShapeGeometryBox::ElementDependent) => ShapeBox::BorderBox, + _ => return None, + }; + let layout_rect = match geometry_box { + ShapeBox::BorderBox => fragment_builder.border_rect, + ShapeBox::ContentBox => *fragment_builder.content_rect(), + ShapeBox::PaddingBox => *fragment_builder.padding_rect(), + ShapeBox::MarginBox => *fragment_builder.margin_rect(), + }; + if let ClipPath::Shape(shape, _) = clip_path { + match *shape { + BasicShape::Circle(_) | BasicShape::Ellipse(_) | BasicShape::Rect(_) => self + .add_for_basic_shape( + *shape, + layout_rect, + parent_scroll_node_id, + parent_clip_chain_id, + ), + BasicShape::Polygon(_) | BasicShape::PathOrShape(_) => None, + } + } else { + Some(self.add( + match geometry_box { + ShapeBox::MarginBox => compute_margin_box_radius( + fragment_builder.border_radius, + layout_rect.size(), + fragment_builder.fragment, + ), + _ => fragment_builder.border_radius, + }, + layout_rect, + *parent_scroll_node_id, + *parent_clip_chain_id, + )) + } + } + + #[cfg_attr( + feature = "tracing", + tracing::instrument( + name = "StackingContextClipStore::add_for_basic_shape", + skip_all, + fields(servo_profiling = true), + level = "trace", + ) + )] + fn add_for_basic_shape( + &mut self, + shape: BasicShape, + layout_box: LayoutRect, + parent_scroll_node_id: &ScrollTreeNodeId, + parent_clip_chain_id: &ClipId, + ) -> Option<ClipId> { + match shape { + BasicShape::Rect(rect) => { + let box_height = Au::from_f32_px(layout_box.height()); + let box_width = Au::from_f32_px(layout_box.width()); + let insets = LayoutSideOffsets::new( + rect.rect.0.to_used_value(box_height).to_f32_px(), + rect.rect.1.to_used_value(box_width).to_f32_px(), + rect.rect.2.to_used_value(box_height).to_f32_px(), + rect.rect.3.to_used_value(box_width).to_f32_px(), + ); + + // `inner_rect()` will cause an assertion failure if the insets are larger than the + // rectangle dimension. + let shape_rect = if insets.left + insets.right >= layout_box.width() || + insets.top + insets.bottom > layout_box.height() + { + LayoutRect::from_origin_and_size(layout_box.min, LayoutSize::zero()) + } else { + layout_box.to_rect().inner_rect(insets).to_box2d() + }; + + let corner = |corner: &style::values::computed::BorderCornerRadius| { + LayoutSize::new( + corner.0.width.0.to_used_value(box_width).to_f32_px(), + corner.0.height.0.to_used_value(box_height).to_f32_px(), + ) + }; + let mut radii = webrender_api::BorderRadius { + top_left: corner(&rect.round.top_left), + top_right: corner(&rect.round.top_right), + bottom_left: corner(&rect.round.bottom_left), + bottom_right: corner(&rect.round.bottom_right), + }; + normalize_radii(&layout_box, &mut radii); + Some(self.add( + radii, + shape_rect, + *parent_scroll_node_id, + *parent_clip_chain_id, + )) + }, + BasicShape::Circle(circle) => { + let center = match circle.position { + GenericPositionOrAuto::Position(position) => position, + GenericPositionOrAuto::Auto => Position::center(), + }; + let anchor_x = center + .horizontal + .to_used_value(Au::from_f32_px(layout_box.width())); + let anchor_y = center + .vertical + .to_used_value(Au::from_f32_px(layout_box.height())); + let center = layout_box + .min + .add_size(&LayoutSize::new(anchor_x.to_f32_px(), anchor_y.to_f32_px())); + + let horizontal = compute_shape_radius( + center.x, + &circle.radius, + layout_box.min.x, + layout_box.max.x, + ); + let vertical = compute_shape_radius( + center.y, + &circle.radius, + layout_box.min.y, + layout_box.max.y, + ); + + // If the value is `Length` then both values should be the same at this point. + let radius = match circle.radius { + GenericShapeRadius::FarthestSide => horizontal.max(vertical), + GenericShapeRadius::ClosestSide => horizontal.min(vertical), + GenericShapeRadius::Length(_) => horizontal, + }; + let radius = LayoutSize::new(radius, radius); + let mut radii = webrender_api::BorderRadius { + top_left: radius, + top_right: radius, + bottom_left: radius, + bottom_right: radius, + }; + let start = center.add_size(&-radius); + let rect = LayoutRect::from_origin_and_size(start, radius * 2.); + normalize_radii(&layout_box, &mut radii); + Some(self.add(radii, rect, *parent_scroll_node_id, *parent_clip_chain_id)) + }, + BasicShape::Ellipse(ellipse) => { + let center = match ellipse.position { + GenericPositionOrAuto::Position(position) => position, + GenericPositionOrAuto::Auto => Position::center(), + }; + let anchor_x = center + .horizontal + .to_used_value(Au::from_f32_px(layout_box.width())); + let anchor_y = center + .vertical + .to_used_value(Au::from_f32_px(layout_box.height())); + let center = layout_box + .min + .add_size(&LayoutSize::new(anchor_x.to_f32_px(), anchor_y.to_f32_px())); + + let width = compute_shape_radius( + center.x, + &ellipse.semiaxis_x, + layout_box.min.x, + layout_box.max.x, + ); + let height = compute_shape_radius( + center.y, + &ellipse.semiaxis_y, + layout_box.min.y, + layout_box.max.y, + ); + + let mut radii = webrender_api::BorderRadius { + top_left: LayoutSize::new(width, height), + top_right: LayoutSize::new(width, height), + bottom_left: LayoutSize::new(width, height), + bottom_right: LayoutSize::new(width, height), + }; + let size = LayoutSize::new(width, height); + let start = center.add_size(&-size); + let rect = LayoutRect::from_origin_and_size(start, size * 2.); + normalize_radii(&rect, &mut radii); + Some(self.add(radii, rect, *parent_scroll_node_id, *parent_clip_chain_id)) + }, + _ => None, + } + } +} + +fn compute_shape_radius( + center: f32, + radius: &GenericShapeRadius<NonNegativeLengthPercentage>, + min_edge: f32, + max_edge: f32, +) -> f32 { + let distance_from_min_edge = (min_edge - center).abs(); + let distance_from_max_edge = (max_edge - center).abs(); + match radius { + GenericShapeRadius::FarthestSide => distance_from_min_edge.max(distance_from_max_edge), + GenericShapeRadius::ClosestSide => distance_from_min_edge.min(distance_from_max_edge), + GenericShapeRadius::Length(length) => length + .0 + .to_used_value(Au::from_f32_px(max_edge - min_edge)) + .to_f32_px(), + } +} diff --git a/components/layout/display_list/clip_path.rs b/components/layout/display_list/clip_path.rs deleted file mode 100644 index 419d15c3572..00000000000 --- a/components/layout/display_list/clip_path.rs +++ /dev/null @@ -1,259 +0,0 @@ -/* 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 app_units::Au; -use base::id::ScrollTreeNodeId; -use style::values::computed::basic_shape::{BasicShape, ClipPath}; -use style::values::computed::length_percentage::NonNegativeLengthPercentage; -use style::values::computed::position::Position; -use style::values::generics::basic_shape::{GenericShapeRadius, ShapeBox, ShapeGeometryBox}; -use style::values::generics::position::GenericPositionOrAuto; -use webrender_api::ClipChainId; -use webrender_api::units::{LayoutRect, LayoutSideOffsets, LayoutSize}; - -use super::{BuilderForBoxFragment, DisplayList, compute_margin_box_radius, normalize_radii}; - -pub(super) fn build_clip_path_clip_chain_if_necessary( - clip_path: ClipPath, - display_list: &mut DisplayList, - parent_scroll_node_id: &ScrollTreeNodeId, - parent_clip_chain_id: &ClipChainId, - fragment_builder: BuilderForBoxFragment, -) -> Option<ClipChainId> { - let geometry_box = match clip_path { - ClipPath::Shape(_, ShapeGeometryBox::ShapeBox(shape_box)) => shape_box, - ClipPath::Shape(_, ShapeGeometryBox::ElementDependent) => ShapeBox::BorderBox, - ClipPath::Box(ShapeGeometryBox::ShapeBox(shape_box)) => shape_box, - ClipPath::Box(ShapeGeometryBox::ElementDependent) => ShapeBox::BorderBox, - _ => return None, - }; - let layout_rect = match geometry_box { - ShapeBox::BorderBox => fragment_builder.border_rect, - ShapeBox::ContentBox => *fragment_builder.content_rect(), - ShapeBox::PaddingBox => *fragment_builder.padding_rect(), - ShapeBox::MarginBox => *fragment_builder.margin_rect(), - }; - if let ClipPath::Shape(shape, _) = clip_path { - match *shape { - BasicShape::Circle(_) | BasicShape::Ellipse(_) | BasicShape::Rect(_) => { - build_simple_shape( - *shape, - layout_rect, - parent_scroll_node_id, - parent_clip_chain_id, - display_list, - ) - }, - BasicShape::Polygon(_) | BasicShape::PathOrShape(_) => None, - } - } else { - Some(create_rect_clip_chain( - match geometry_box { - ShapeBox::MarginBox => compute_margin_box_radius( - fragment_builder.border_radius, - layout_rect.size(), - fragment_builder.fragment, - ), - _ => fragment_builder.border_radius, - }, - layout_rect, - parent_scroll_node_id, - parent_clip_chain_id, - display_list, - )) - } -} - -#[cfg_attr( - feature = "tracing", - tracing::instrument( - name = "display_list::build_simple_shape", - skip_all, - fields(servo_profiling = true), - level = "trace", - ) -)] -fn build_simple_shape( - shape: BasicShape, - layout_box: LayoutRect, - parent_scroll_node_id: &ScrollTreeNodeId, - parent_clip_chain_id: &ClipChainId, - display_list: &mut DisplayList, -) -> Option<ClipChainId> { - match shape { - BasicShape::Rect(rect) => { - let box_height = Au::from_f32_px(layout_box.height()); - let box_width = Au::from_f32_px(layout_box.width()); - let insets = LayoutSideOffsets::new( - rect.rect.0.to_used_value(box_height).to_f32_px(), - rect.rect.1.to_used_value(box_width).to_f32_px(), - rect.rect.2.to_used_value(box_height).to_f32_px(), - rect.rect.3.to_used_value(box_width).to_f32_px(), - ); - - // `inner_rect()` will cause an assertion failure if the insets are larger than the - // rectangle dimension. - let shape_rect = if insets.left + insets.right >= layout_box.width() || - insets.top + insets.bottom > layout_box.height() - { - LayoutRect::from_origin_and_size(layout_box.min, LayoutSize::zero()) - } else { - layout_box.to_rect().inner_rect(insets).to_box2d() - }; - - let corner = |corner: &style::values::computed::BorderCornerRadius| { - LayoutSize::new( - corner.0.width.0.to_used_value(box_width).to_f32_px(), - corner.0.height.0.to_used_value(box_height).to_f32_px(), - ) - }; - let mut radii = webrender_api::BorderRadius { - top_left: corner(&rect.round.top_left), - top_right: corner(&rect.round.top_right), - bottom_left: corner(&rect.round.bottom_left), - bottom_right: corner(&rect.round.bottom_right), - }; - normalize_radii(&layout_box, &mut radii); - Some(create_rect_clip_chain( - radii, - shape_rect, - parent_scroll_node_id, - parent_clip_chain_id, - display_list, - )) - }, - BasicShape::Circle(circle) => { - let center = match circle.position { - GenericPositionOrAuto::Position(position) => position, - GenericPositionOrAuto::Auto => Position::center(), - }; - let anchor_x = center - .horizontal - .to_used_value(Au::from_f32_px(layout_box.width())); - let anchor_y = center - .vertical - .to_used_value(Au::from_f32_px(layout_box.height())); - let center = layout_box - .min - .add_size(&LayoutSize::new(anchor_x.to_f32_px(), anchor_y.to_f32_px())); - - let horizontal = - compute_shape_radius(center.x, &circle.radius, layout_box.min.x, layout_box.max.x); - let vertical = - compute_shape_radius(center.y, &circle.radius, layout_box.min.y, layout_box.max.y); - - // If the value is `Length` then both values should be the same at this point. - let radius = match circle.radius { - GenericShapeRadius::FarthestSide => horizontal.max(vertical), - GenericShapeRadius::ClosestSide => horizontal.min(vertical), - GenericShapeRadius::Length(_) => horizontal, - }; - let radius = LayoutSize::new(radius, radius); - let mut radii = webrender_api::BorderRadius { - top_left: radius, - top_right: radius, - bottom_left: radius, - bottom_right: radius, - }; - let start = center.add_size(&-radius); - let rect = LayoutRect::from_origin_and_size(start, radius * 2.); - normalize_radii(&layout_box, &mut radii); - Some(create_rect_clip_chain( - radii, - rect, - parent_scroll_node_id, - parent_clip_chain_id, - display_list, - )) - }, - BasicShape::Ellipse(ellipse) => { - let center = match ellipse.position { - GenericPositionOrAuto::Position(position) => position, - GenericPositionOrAuto::Auto => Position::center(), - }; - let anchor_x = center - .horizontal - .to_used_value(Au::from_f32_px(layout_box.width())); - let anchor_y = center - .vertical - .to_used_value(Au::from_f32_px(layout_box.height())); - let center = layout_box - .min - .add_size(&LayoutSize::new(anchor_x.to_f32_px(), anchor_y.to_f32_px())); - - let width = compute_shape_radius( - center.x, - &ellipse.semiaxis_x, - layout_box.min.x, - layout_box.max.x, - ); - let height = compute_shape_radius( - center.y, - &ellipse.semiaxis_y, - layout_box.min.y, - layout_box.max.y, - ); - - let mut radii = webrender_api::BorderRadius { - top_left: LayoutSize::new(width, height), - top_right: LayoutSize::new(width, height), - bottom_left: LayoutSize::new(width, height), - bottom_right: LayoutSize::new(width, height), - }; - let size = LayoutSize::new(width, height); - let start = center.add_size(&-size); - let rect = LayoutRect::from_origin_and_size(start, size * 2.); - normalize_radii(&rect, &mut radii); - Some(create_rect_clip_chain( - radii, - rect, - parent_scroll_node_id, - parent_clip_chain_id, - display_list, - )) - }, - _ => None, - } -} - -fn compute_shape_radius( - center: f32, - radius: &GenericShapeRadius<NonNegativeLengthPercentage>, - min_edge: f32, - max_edge: f32, -) -> f32 { - let distance_from_min_edge = (min_edge - center).abs(); - let distance_from_max_edge = (max_edge - center).abs(); - match radius { - GenericShapeRadius::FarthestSide => distance_from_min_edge.max(distance_from_max_edge), - GenericShapeRadius::ClosestSide => distance_from_min_edge.min(distance_from_max_edge), - GenericShapeRadius::Length(length) => length - .0 - .to_used_value(Au::from_f32_px(max_edge - min_edge)) - .to_f32_px(), - } -} -fn create_rect_clip_chain( - radii: webrender_api::BorderRadius, - rect: LayoutRect, - parent_scroll_node_id: &ScrollTreeNodeId, - parent_clip_chain_id: &ClipChainId, - display_list: &mut DisplayList, -) -> ClipChainId { - let new_clip_id = if radii.is_zero() { - display_list - .wr - .define_clip_rect(parent_scroll_node_id.spatial_id, rect) - } else { - display_list.wr.define_clip_rounded_rect( - parent_scroll_node_id.spatial_id, - webrender_api::ComplexClipRegion { - rect, - radii, - mode: webrender_api::ClipMode::Clip, - }, - ) - }; - display_list.define_clip_chain(*parent_clip_chain_id, [new_clip_id]) -} diff --git a/components/layout/display_list/mod.rs b/components/layout/display_list/mod.rs index f017642908d..95689cf1186 100644 --- a/components/layout/display_list/mod.rs +++ b/components/layout/display_list/mod.rs @@ -8,13 +8,15 @@ use std::sync::Arc; use app_units::{AU_PER_PX, Au}; use base::WebRenderEpochToU16; use base::id::ScrollTreeNodeId; -use compositing_traits::display_list::{AxesScrollSensitivity, CompositorDisplayListInfo}; +use clip::{Clip, ClipId}; +use compositing_traits::display_list::{CompositorDisplayListInfo, SpatialTreeNodeInfo}; use embedder_traits::Cursor; use euclid::{Point2D, SideOffsets2D, Size2D, UnknownUnit}; use fonts::GlyphStore; use gradient::WebRenderGradient; use range::Range as ServoRange; use servo_arc::Arc as ServoArc; +use servo_config::opts::DebugOptions; use servo_geometry::MaxRect; use style::Zero; use style::color::{AbsoluteColor, ColorSpace}; @@ -35,8 +37,9 @@ use style::values::specified::ui::CursorKind; use style_traits::CSSPixel; use webrender_api::units::{DevicePixel, LayoutPixel, LayoutRect, LayoutSize}; use webrender_api::{ - self as wr, BorderDetails, BoxShadowClipMode, ClipChainId, CommonItemProperties, - ImageRendering, NinePatchBorder, NinePatchBorderSource, SpatialId, units, + self as wr, BorderDetails, BoxShadowClipMode, BuiltDisplayList, ClipChainId, ClipMode, + CommonItemProperties, ComplexClipRegion, ImageRendering, NinePatchBorder, + NinePatchBorderSource, PropertyBinding, SpatialId, SpatialTreeItemKey, units, }; use wr::units::LayoutVector2D; @@ -55,7 +58,7 @@ use crate::replaced::NaturalSizes; use crate::style_ext::{BorderStyleColor, ComputedValuesExt}; mod background; -mod clip_path; +mod clip; mod conversions; mod gradient; mod stacking_context; @@ -74,68 +77,6 @@ type ItemTag = (u64, u16); type HitInfo = Option<ItemTag>; const INSERTION_POINT_LOGICAL_WIDTH: Au = Au(AU_PER_PX); -/// Where the information that's used to build display lists is stored. This -/// includes both a [wr::DisplayListBuilder] for building up WebRender-specific -/// display list information and a [CompositorDisplayListInfo] used to store -/// information used by the compositor, such as a compositor-side scroll tree. -pub struct DisplayList { - /// The [wr::DisplayListBuilder] used to collect display list items. - pub wr: wr::DisplayListBuilder, - - /// The information about the WebRender display list that the compositor - /// consumes. This curerntly contains the out-of-band hit testing information - /// data structure that the compositor uses to map hit tests to information - /// about the item hit. - pub compositor_info: CompositorDisplayListInfo, - - /// A count of the number of SpatialTree nodes pushed to the WebRender display - /// list. This is merely to ensure that the currently-unused SpatialTreeItemKey - /// produced for every SpatialTree node is unique. - pub spatial_tree_count: u64, -} - -impl DisplayList { - /// Create a new [DisplayList] given the dimensions of the layout and the WebRender - /// pipeline id. - pub fn new( - viewport_size: units::LayoutSize, - content_size: units::LayoutSize, - pipeline_id: wr::PipelineId, - epoch: wr::Epoch, - viewport_scroll_sensitivity: AxesScrollSensitivity, - first_reflow: bool, - ) -> Self { - Self { - wr: wr::DisplayListBuilder::new(pipeline_id), - compositor_info: CompositorDisplayListInfo::new( - viewport_size, - content_size, - pipeline_id, - epoch, - viewport_scroll_sensitivity, - first_reflow, - ), - spatial_tree_count: 0, - } - } - - pub fn define_clip_chain<I>(&mut self, parent: ClipChainId, clips: I) -> ClipChainId - where - I: IntoIterator<Item = wr::ClipId>, - I::IntoIter: ExactSizeIterator + Clone, - { - // WebRender has two different ways of expressing "no clip." ClipChainId::INVALID should be - // used for primitives, but `None` is used for stacking contexts and clip chains. We convert - // to the `Option<ClipChainId>` representation here. Just passing Some(ClipChainId::INVALID) - // leads to a crash. - let parent = match parent { - ClipChainId::INVALID => None, - parent => Some(parent), - }; - self.wr.define_clip_chain(parent, clips) - } -} - pub(crate) struct DisplayListBuilder<'a> { /// The current [ScrollTreeNodeId] for this [DisplayListBuilder]. This /// allows only passing the builder instead passing the containing @@ -148,18 +89,21 @@ pub(crate) struct DisplayListBuilder<'a> { /// `background-attachment: fixed` need to not scroll while the rest of the fragment does. current_reference_frame_scroll_node_id: ScrollTreeNodeId, - /// The current [wr::ClipId] for this [DisplayListBuilder]. This allows + /// The current [`ClipId`] for this [DisplayListBuilder]. This allows /// only passing the builder instead passing the containing /// [stacking_context::StackingContextContent::Fragment] as an argument to display /// list building functions. - current_clip_chain_id: ClipChainId, + current_clip_id: ClipId, /// A [LayoutContext] used to get information about the device pixel ratio /// and get handles to WebRender images. pub context: &'a LayoutContext<'a>, - /// The [DisplayList] used to collect display list items and metadata. - pub display_list: &'a mut DisplayList, + /// The [`wr::DisplayListBuilder`] for this Servo [`DisplayListBuilder`]. + pub webrender_display_list_builder: &'a mut wr::DisplayListBuilder, + + /// The [`CompositorDisplayListInfo`] used to collect display list items and metadata. + pub compositor_info: &'a mut CompositorDisplayListInfo, /// Data about the fragments that are highlighted by the inspector, if any. /// @@ -171,6 +115,10 @@ pub(crate) struct DisplayListBuilder<'a> { /// element inherits the `<body>`'s background to paint the page canvas background. /// See <https://drafts.csswg.org/css-backgrounds/#body-background>. paint_body_background: bool, + + /// A mapping from [`ClipId`] To WebRender [`ClipChainId`] used when building this WebRender + /// display list. + clip_map: Vec<ClipChainId>, } struct InspectorHighlight { @@ -207,45 +155,224 @@ impl InspectorHighlight { } } -impl DisplayList { - pub fn build( - &mut self, +impl DisplayListBuilder<'_> { + pub(crate) fn build( context: &LayoutContext, + stacking_context_tree: &mut StackingContextTree, fragment_tree: &FragmentTree, - root_stacking_context: &StackingContext, - ) { + debug: &DebugOptions, + ) -> BuiltDisplayList { + // Build the rest of the display list which inclues all of the WebRender primitives. + let compositor_info = &mut stacking_context_tree.compositor_info; + compositor_info.hit_test_info.clear(); + + let mut webrender_display_list_builder = + webrender_api::DisplayListBuilder::new(compositor_info.pipeline_id); + webrender_display_list_builder.begin(); + + // `dump_serialized_display_list` doesn't actually print anything. It sets up + // the display list for printing the serialized version when `finalize()` is called. + // We need to call this before adding any display items so that they are printed + // during `finalize()`. + if debug.dump_display_list { + webrender_display_list_builder.dump_serialized_display_list(); + } + #[cfg(feature = "tracing")] - let _span = tracing::trace_span!("display_list::build", servo_profiling = true).entered(); + let _span = + tracing::trace_span!("DisplayListBuilder::build", servo_profiling = true).entered(); let mut builder = DisplayListBuilder { - current_scroll_node_id: self.compositor_info.root_reference_frame_id, - current_reference_frame_scroll_node_id: self.compositor_info.root_reference_frame_id, - current_clip_chain_id: ClipChainId::INVALID, + current_scroll_node_id: compositor_info.root_reference_frame_id, + current_reference_frame_scroll_node_id: compositor_info.root_reference_frame_id, + current_clip_id: ClipId::INVALID, context, - display_list: self, + webrender_display_list_builder: &mut webrender_display_list_builder, + compositor_info, inspector_highlight: context .highlighted_dom_node .map(InspectorHighlight::for_node), paint_body_background: true, + clip_map: Default::default(), }; - fragment_tree.build_display_list(&mut builder, root_stacking_context); - if let Some(highlight) = builder - .inspector_highlight - .take() - .and_then(|highlight| highlight.state) - { - builder.paint_dom_inspector_highlight(highlight); + builder.add_all_spatial_nodes(); + + for clip in stacking_context_tree.clip_store.0.iter() { + builder.add_clip_to_display_list(clip); } + + // Paint the canvas’ background (if any) before/under everything else + stacking_context_tree + .root_stacking_context + .build_canvas_background_display_list(&mut builder, fragment_tree); + stacking_context_tree + .root_stacking_context + .build_display_list(&mut builder); + builder.paint_dom_inspector_highlight(); + + webrender_display_list_builder.end().1 } -} -impl DisplayListBuilder<'_> { fn wr(&mut self) -> &mut wr::DisplayListBuilder { - &mut self.display_list.wr + self.webrender_display_list_builder + } + + fn pipeline_id(&mut self) -> wr::PipelineId { + self.compositor_info.pipeline_id } fn mark_is_contentful(&mut self) { - self.display_list.compositor_info.is_contentful = true; + self.compositor_info.is_contentful = true; + } + + fn spatial_id(&self, id: ScrollTreeNodeId) -> SpatialId { + self.compositor_info.scroll_tree.webrender_id(&id) + } + + fn clip_chain_id(&self, id: ClipId) -> ClipChainId { + match id { + ClipId::INVALID => ClipChainId::INVALID, + _ => *self + .clip_map + .get(id.0) + .expect("Should never try to get clip before adding it to WebRender display list"), + } + } + + pub(crate) fn add_all_spatial_nodes(&mut self) { + // A count of the number of SpatialTree nodes pushed to the WebRender display + // list. This is merely to ensure that the currently-unused SpatialTreeItemKey + // produced for every SpatialTree node is unique. + let mut spatial_tree_count = 0; + let mut scroll_tree = std::mem::take(&mut self.compositor_info.scroll_tree); + let mut mapping = Vec::with_capacity(scroll_tree.nodes.len()); + + mapping.push(SpatialId::root_reference_frame(self.pipeline_id())); + mapping.push(SpatialId::root_scroll_node(self.pipeline_id())); + + let pipeline_id = self.pipeline_id(); + let pipeline_tag = ((pipeline_id.0 as u64) << 32) | pipeline_id.1 as u64; + + for node in scroll_tree.nodes.iter().skip(2) { + let parent_scroll_node_id = node + .parent + .expect("Should have already added root reference frame"); + let parent_spatial_node_id = mapping + .get(parent_scroll_node_id.index) + .expect("Should add spatial nodes to display list in order"); + + // Produce a new SpatialTreeItemKey. This is currently unused by WebRender, + // but has to be unique to the entire scene. + spatial_tree_count += 1; + let spatial_tree_item_key = SpatialTreeItemKey::new(pipeline_tag, spatial_tree_count); + + mapping.push(match &node.info { + SpatialTreeNodeInfo::ReferenceFrame(info) => { + let spatial_id = self.wr().push_reference_frame( + info.origin, + *parent_spatial_node_id, + info.transform_style, + PropertyBinding::Value(info.transform), + info.kind, + spatial_tree_item_key, + ); + self.wr().pop_reference_frame(); + spatial_id + }, + SpatialTreeNodeInfo::Scroll(info) => { + self.wr().define_scroll_frame( + *parent_spatial_node_id, + info.external_id, + info.content_rect, + info.clip_rect, + LayoutVector2D::zero(), /* external_scroll_offset */ + 0, /* scroll_offset_generation */ + wr::HasScrollLinkedEffect::No, + spatial_tree_item_key, + ) + }, + SpatialTreeNodeInfo::Sticky(info) => { + self.wr().define_sticky_frame( + *parent_spatial_node_id, + info.frame_rect, + info.margins, + info.vertical_offset_bounds, + info.horizontal_offset_bounds, + LayoutVector2D::zero(), /* previously_applied_offset */ + spatial_tree_item_key, + None, /* transform */ + ) + }, + }); + } + + scroll_tree.update_mapping(mapping); + self.compositor_info.scroll_tree = scroll_tree; + } + + /// Add the given [`Clip`] to the WebRender display list and create a mapping from + /// its [`ClipId`] to a WebRender [`ClipChainId`]. This happens: + /// - When WebRender display list construction starts: All clips created during the + /// `StackingContextTree` construction are added in one batch. These clips are used + /// for things such as `overflow: scroll` elements. + /// - When a clip is added during WebRender display list construction for individual + /// items. In that case, this is called by [`Self::maybe_create_clip`]. + pub(crate) fn add_clip_to_display_list(&mut self, clip: &Clip) -> ClipChainId { + assert_eq!( + clip.id.0, + self.clip_map.len(), + "Clips should be added in order" + ); + + let spatial_id = self.spatial_id(clip.parent_scroll_node_id); + let new_clip_id = if clip.radii.is_zero() { + self.wr().define_clip_rect(spatial_id, clip.rect) + } else { + self.wr().define_clip_rounded_rect( + spatial_id, + ComplexClipRegion { + rect: clip.rect, + radii: clip.radii, + mode: ClipMode::Clip, + }, + ) + }; + + // WebRender has two different ways of expressing "no clip." ClipChainId::INVALID should be + // used for primitives, but `None` is used for stacking contexts and clip chains. We convert + // to the `Option<ClipChainId>` representation here. Just passing Some(ClipChainId::INVALID) + // leads to a crash. + let parent_clip_chain_id = match self.clip_chain_id(clip.parent_clip_id) { + ClipChainId::INVALID => None, + parent => Some(parent), + }; + let clip_chain_id = self + .wr() + .define_clip_chain(parent_clip_chain_id, [new_clip_id]); + self.clip_map.push(clip_chain_id); + clip_chain_id + } + + /// Add a new clip to the WebRender display list being built. This only happens during + /// WebRender display list building and these clips should be added after all clips + /// from the `StackingContextTree` have already been processed. + fn maybe_create_clip( + &mut self, + radii: wr::BorderRadius, + rect: units::LayoutRect, + force_clip_creation: bool, + ) -> Option<ClipChainId> { + if radii.is_zero() && !force_clip_creation { + return None; + } + + Some(self.add_clip_to_display_list(&Clip { + id: ClipId(self.clip_map.len()), + radii, + rect, + parent_scroll_node_id: self.current_scroll_node_id, + parent_clip_id: self.current_clip_id, + })) } fn common_properties( @@ -258,8 +385,8 @@ impl DisplayListBuilder<'_> { // for fragments that paint their entire border rectangle. wr::CommonItemProperties { clip_rect, - spatial_id: self.current_scroll_node_id.spatial_id, - clip_chain_id: self.current_clip_chain_id, + spatial_id: self.spatial_id(self.current_scroll_node_id), + clip_chain_id: self.clip_chain_id(self.current_clip_id), flags: style.get_webrender_primitive_flags(), } } @@ -277,19 +404,24 @@ impl DisplayListBuilder<'_> { return None; } - let hit_test_index = self.display_list.compositor_info.add_hit_test_info( + let hit_test_index = self.compositor_info.add_hit_test_info( tag?.node.0 as u64, Some(cursor(inherited_ui.cursor.keyword, auto_cursor)), self.current_scroll_node_id, ); - Some(( - hit_test_index as u64, - self.display_list.compositor_info.epoch.as_u16(), - )) + Some((hit_test_index as u64, self.compositor_info.epoch.as_u16())) } /// Draw highlights around the node that is currently hovered in the devtools. - fn paint_dom_inspector_highlight(&mut self, highlight: HighlightTraversalState) { + fn paint_dom_inspector_highlight(&mut self) { + let Some(highlight) = self + .inspector_highlight + .take() + .and_then(|highlight| highlight.state) + else { + return; + }; + const CONTENT_BOX_HIGHLIGHT_COLOR: webrender_api::ColorF = webrender_api::ColorF { r: 0.23, g: 0.7, @@ -327,8 +459,7 @@ impl DisplayListBuilder<'_> { flags: wr::PrimitiveFlags::default(), }; - self.display_list - .wr + self.wr() .push_rect(&properties, content_box, CONTENT_BOX_HIGHLIGHT_COLOR); // Highlight margin, border and padding @@ -442,12 +573,14 @@ impl Fragment { is_hit_test_for_scrollable_overflow: bool, is_collapsed_table_borders: bool, ) { + let spatial_id = builder.spatial_id(builder.current_scroll_node_id); + let clip_chain_id = builder.clip_chain_id(builder.current_clip_id); if let Some(inspector_highlight) = &mut builder.inspector_highlight { if self.tag() == Some(inspector_highlight.tag) { inspector_highlight.register_fragment_of_highlighted_dom_node( self, - builder.current_scroll_node_id.spatial_id, - builder.current_clip_chain_id, + spatial_id, + clip_chain_id, containing_block, ); } @@ -574,8 +707,8 @@ impl Fragment { None => return, }; - let clip_chain_id = builder.current_clip_chain_id; - let spatial_id = builder.current_scroll_node_id.spatial_id; + let clip_chain_id = builder.clip_chain_id(builder.current_clip_id); + let spatial_id = builder.spatial_id(builder.current_scroll_node_id); builder.wr().push_hit_test( rect.to_webrender(), clip_chain_id, @@ -765,8 +898,9 @@ impl Fragment { if text_decoration_style == ComputedTextDecorationStyle::MozNone { return; } - builder.display_list.wr.push_line( - &builder.common_properties(rect, parent_style), + let common_properties = builder.common_properties(rect, parent_style); + builder.wr().push_line( + &common_properties, &rect, wavy_line_thickness, wr::LineOrientation::Horizontal, @@ -878,12 +1012,8 @@ impl<'a> BuilderForBoxFragment<'a> { return Some(clip); } - let maybe_clip = create_clip_chain( - self.border_radius, - self.border_rect, - builder, - force_clip_creation, - ); + let maybe_clip = + builder.maybe_create_clip(self.border_radius, self.border_rect, force_clip_creation); *self.border_edge_clip_chain_id.borrow_mut() = maybe_clip; maybe_clip } @@ -899,7 +1029,7 @@ impl<'a> BuilderForBoxFragment<'a> { let radii = inner_radii(self.border_radius, self.fragment.border.to_webrender()); let maybe_clip = - create_clip_chain(radii, *self.padding_rect(), builder, force_clip_creation); + builder.maybe_create_clip(radii, *self.padding_rect(), force_clip_creation); *self.padding_edge_clip_chain_id.borrow_mut() = maybe_clip; maybe_clip } @@ -918,7 +1048,7 @@ impl<'a> BuilderForBoxFragment<'a> { (self.fragment.border + self.fragment.padding).to_webrender(), ); let maybe_clip = - create_clip_chain(radii, *self.content_rect(), builder, force_clip_creation); + builder.maybe_create_clip(radii, *self.content_rect(), force_clip_creation); *self.content_edge_clip_chain_id.borrow_mut() = maybe_clip; maybe_clip } @@ -1553,38 +1683,6 @@ fn offset_radii(mut radii: wr::BorderRadius, offset: f32) -> wr::BorderRadius { radii } -fn create_clip_chain( - radii: wr::BorderRadius, - rect: units::LayoutRect, - builder: &mut DisplayListBuilder, - force_clip_creation: bool, -) -> Option<ClipChainId> { - if radii.is_zero() && !force_clip_creation { - return None; - } - - let spatial_id = builder.current_scroll_node_id.spatial_id; - let parent_clip_chain_id = builder.current_clip_chain_id; - let new_clip_id = if radii.is_zero() { - builder.wr().define_clip_rect(spatial_id, rect) - } else { - builder.wr().define_clip_rounded_rect( - spatial_id, - wr::ComplexClipRegion { - rect, - radii, - mode: wr::ClipMode::Clip, - }, - ) - }; - - Some( - builder - .display_list - .define_clip_chain(parent_clip_chain_id, [new_clip_id]), - ) -} - /// Resolve the WebRender border-image outset area from the style values. fn resolve_border_image_outset( outset: BorderImageOutset, diff --git a/components/layout/display_list/stacking_context.rs b/components/layout/display_list/stacking_context.rs index 27fa73a680c..bcd882e3fcc 100644 --- a/components/layout/display_list/stacking_context.rs +++ b/components/layout/display_list/stacking_context.rs @@ -9,18 +9,19 @@ use std::mem; use app_units::Au; use base::id::ScrollTreeNodeId; use base::print_tree::PrintTree; -use compositing_traits::display_list::{AxesScrollSensitivity, ScrollableNodeInfo}; +use compositing_traits::display_list::{ + AxesScrollSensitivity, CompositorDisplayListInfo, ReferenceFrameNodeInfo, ScrollableNodeInfo, + SpatialTreeNodeInfo, StickyNodeInfo, +}; use euclid::SideOffsets2D; use euclid::default::{Point2D, Rect, Size2D}; use log::warn; -use servo_arc::Arc as ServoArc; use servo_config::opts::DebugOptions; use style::Zero; use style::computed_values::float::T as ComputedFloat; use style::computed_values::mix_blend_mode::T as ComputedMixBlendMode; use style::computed_values::overflow_x::T as ComputedOverflow; use style::computed_values::position::T as ComputedPosition; -use style::properties::ComputedValues; use style::values::computed::angle::Angle; use style::values::computed::basic_shape::ClipPath; use style::values::computed::{ClipRectOrAuto, Length}; @@ -29,11 +30,12 @@ use style::values::generics::transform::{self, GenericRotate, GenericScale, Gene use style::values::specified::box_::DisplayOutside; use webrender_api::units::{LayoutPoint, LayoutRect, LayoutTransform, LayoutVector2D}; use webrender_api::{self as wr, BorderRadius}; +use wr::StickyOffsetBounds; use wr::units::{LayoutPixel, LayoutSize}; -use wr::{ClipChainId, SpatialTreeItemKey, StickyOffsetBounds}; -use super::DisplayList; -use super::clip_path::build_clip_path_clip_chain_if_necessary; +use super::ClipId; +use super::clip::StackingContextTreeClipStore; +use crate::ArcRefCell; use crate::display_list::conversions::{FilterToWebRender, ToWebRender}; use crate::display_list::{BuilderForBoxFragment, DisplayListBuilder, offset_radii}; use crate::fragment_tree::{ @@ -54,9 +56,8 @@ pub(crate) struct ContainingBlock { /// frame and sticky positioning isn't taken into account. scroll_frame_size: Option<LayoutSize>, - /// The WebRender ClipId to use for this children of this containing - /// block. - clip_chain_id: wr::ClipChainId, + /// The [`ClipId`] to use for the children of this containing block. + clip_id: ClipId, /// The physical rect of this containing block. rect: PhysicalRect<Au>, @@ -67,12 +68,12 @@ impl ContainingBlock { rect: PhysicalRect<Au>, scroll_node_id: ScrollTreeNodeId, scroll_frame_size: Option<LayoutSize>, - clip_chain_id: wr::ClipChainId, + clip_id: ClipId, ) -> Self { ContainingBlock { scroll_node_id, scroll_frame_size, - clip_chain_id, + clip_id, rect, } } @@ -95,40 +96,56 @@ pub(crate) enum StackingContextSection { Outline, } -impl DisplayList { - /// Produce a new SpatialTreeItemKey. This is currently unused by WebRender, - /// but has to be unique to the entire scene. - fn get_next_spatial_tree_item_key(&mut self) -> SpatialTreeItemKey { - self.spatial_tree_count += 1; - let pipeline_tag = ((self.wr.pipeline_id.0 as u64) << 32) | self.wr.pipeline_id.1 as u64; - SpatialTreeItemKey::new(pipeline_tag, self.spatial_tree_count) - } +pub(crate) struct StackingContextTree { + /// The root stacking context of this [`StackingContextTree`]. + pub root_stacking_context: StackingContext, - #[cfg_attr( - feature = "tracing", - tracing::instrument( - name = "display_list::build_stacking_context_tree", - skip_all, - fields(servo_profiling = true), - level = "trace", - ) - )] - pub fn build_stacking_context_tree( - &mut self, + /// The information about the WebRender display list that the compositor + /// consumes. This curerntly contains the out-of-band hit testing information + /// data structure that the compositor uses to map hit tests to information + /// about the item hit. + pub compositor_info: CompositorDisplayListInfo, + + /// All of the clips collected for this [`StackingContextTree`]. These are added + /// for things like `overflow`. More clips may be created later during WebRender + /// display list construction, but they are never added here. + pub clip_store: StackingContextTreeClipStore, +} + +impl StackingContextTree { + /// Create a new [DisplayList] given the dimensions of the layout and the WebRender + /// pipeline id. + pub fn new( fragment_tree: &FragmentTree, + viewport_size: LayoutSize, + content_size: LayoutSize, + pipeline_id: wr::PipelineId, + viewport_scroll_sensitivity: AxesScrollSensitivity, + first_reflow: bool, debug: &DebugOptions, - ) -> StackingContext { + ) -> Self { + let compositor_info = CompositorDisplayListInfo::new( + viewport_size, + content_size, + pipeline_id, + // This epoch is set when the WebRender display list is built. For now use a dummy value. + wr::Epoch(0), + viewport_scroll_sensitivity, + first_reflow, + ); + + let root_scroll_node_id = compositor_info.root_scroll_node_id; let cb_for_non_fixed_descendants = ContainingBlock::new( fragment_tree.initial_containing_block, - self.compositor_info.root_scroll_node_id, - Some(self.compositor_info.viewport_size), - ClipChainId::INVALID, + root_scroll_node_id, + Some(compositor_info.viewport_size), + ClipId::INVALID, ); let cb_for_fixed_descendants = ContainingBlock::new( fragment_tree.initial_containing_block, - self.compositor_info.root_reference_frame_id, + compositor_info.root_reference_frame_id, None, - ClipChainId::INVALID, + ClipId::INVALID, ); // We need to specify all three containing blocks here, because absolute @@ -143,17 +160,31 @@ impl DisplayList { for_absolute_and_fixed_descendants: &cb_for_fixed_descendants, }; - let mut root_stacking_context = StackingContext::create_root(&self.wr, debug); + let mut stacking_context_tree = Self { + // This is just a temporary value that will be replaced once we have finished building the tree. + root_stacking_context: StackingContext::create_root(root_scroll_node_id, debug), + compositor_info, + clip_store: Default::default(), + }; + + let mut root_stacking_context = StackingContext::create_root(root_scroll_node_id, debug); for fragment in &fragment_tree.root_fragments { fragment.build_stacking_context_tree( - self, + &mut stacking_context_tree, &containing_block_info, &mut root_stacking_context, StackingContextBuildMode::SkipHoisted, ); } root_stacking_context.sort(); - root_stacking_context + + if debug.dump_stacking_context_tree { + root_stacking_context.debug_print(); + } + + stacking_context_tree.root_stacking_context = root_stacking_context; + + stacking_context_tree } fn push_reference_frame( @@ -161,53 +192,20 @@ impl DisplayList { origin: LayoutPoint, parent_scroll_node_id: &ScrollTreeNodeId, transform_style: wr::TransformStyle, - transform: wr::PropertyBinding<LayoutTransform>, + transform: LayoutTransform, kind: wr::ReferenceFrameKind, ) -> ScrollTreeNodeId { - let spatial_tree_item_key = self.get_next_spatial_tree_item_key(); - let new_spatial_id = self.wr.push_reference_frame( - origin, - parent_scroll_node_id.spatial_id, - transform_style, - transform, - kind, - spatial_tree_item_key, - ); self.compositor_info.scroll_tree.add_scroll_tree_node( Some(parent_scroll_node_id), - new_spatial_id, - None, + SpatialTreeNodeInfo::ReferenceFrame(ReferenceFrameNodeInfo { + origin, + transform_style, + transform, + kind, + }), ) } - fn pop_reference_frame(&mut self) { - self.wr.pop_reference_frame(); - } - - fn clip_overflow_frame( - &mut self, - parent_scroll_node_id: &ScrollTreeNodeId, - parent_clip_id: &ClipChainId, - clip_rect: LayoutRect, - radii: wr::BorderRadius, - ) -> ClipChainId { - let new_clip_id = if radii.is_zero() { - self.wr - .define_clip_rect(parent_scroll_node_id.spatial_id, clip_rect) - } else { - self.wr.define_clip_rounded_rect( - parent_scroll_node_id.spatial_id, - webrender_api::ComplexClipRegion { - rect: clip_rect, - radii, - mode: webrender_api::ClipMode::Clip, - }, - ) - }; - - self.define_clip_chain(*parent_clip_id, [new_clip_id]) - } - fn define_scroll_frame( &mut self, parent_scroll_node_id: &ScrollTreeNodeId, @@ -216,25 +214,12 @@ impl DisplayList { clip_rect: LayoutRect, scroll_sensitivity: AxesScrollSensitivity, ) -> ScrollTreeNodeId { - let spatial_tree_item_key = self.get_next_spatial_tree_item_key(); - - let new_spatial_id = self.wr.define_scroll_frame( - parent_scroll_node_id.spatial_id, - external_id, - content_rect, - clip_rect, - LayoutVector2D::zero(), /* external_scroll_offset */ - 0, /* scroll_offset_generation */ - wr::HasScrollLinkedEffect::No, - spatial_tree_item_key, - ); - self.compositor_info.scroll_tree.add_scroll_tree_node( Some(parent_scroll_node_id), - new_spatial_id, - Some(ScrollableNodeInfo { + SpatialTreeNodeInfo::Scroll(ScrollableNodeInfo { external_id, - scrollable_size: content_rect.size() - clip_rect.size(), + content_rect, + clip_rect, scroll_sensitivity, offset: LayoutVector2D::zero(), }), @@ -249,21 +234,14 @@ impl DisplayList { vertical_offset_bounds: StickyOffsetBounds, horizontal_offset_bounds: StickyOffsetBounds, ) -> ScrollTreeNodeId { - let spatial_tree_item_key = self.get_next_spatial_tree_item_key(); - let new_spatial_id = self.wr.define_sticky_frame( - parent_scroll_node_id.spatial_id, - frame_rect, - margins, - vertical_offset_bounds, - horizontal_offset_bounds, - LayoutVector2D::zero(), /* previously_applied_offset */ - spatial_tree_item_key, - None, /* transform */ - ); self.compositor_info.scroll_tree.add_scroll_tree_node( Some(parent_scroll_node_id), - new_spatial_id, - None, + SpatialTreeNodeInfo::Sticky(StickyNodeInfo { + frame_rect, + margins, + vertical_offset_bounds, + horizontal_offset_bounds, + }), ) } } @@ -277,7 +255,7 @@ pub(crate) enum StackingContextContent { Fragment { scroll_node_id: ScrollTreeNodeId, reference_frame_scroll_node_id: ScrollTreeNodeId, - clip_chain_id: wr::ClipChainId, + clip_id: ClipId, section: StackingContextSection, containing_block: PhysicalRect<Au>, fragment: Fragment, @@ -308,7 +286,7 @@ impl StackingContextContent { Self::Fragment { scroll_node_id, reference_frame_scroll_node_id, - clip_chain_id, + clip_id, section, containing_block, fragment, @@ -317,7 +295,7 @@ impl StackingContextContent { } => { builder.current_scroll_node_id = *scroll_node_id; builder.current_reference_frame_scroll_node_id = *reference_frame_scroll_node_id; - builder.current_clip_chain_id = *clip_chain_id; + builder.current_clip_id = *clip_id; fragment.build_display_list( builder, containing_block, @@ -349,16 +327,14 @@ pub(crate) enum StackingContextType { pub struct StackingContext { /// The spatial id of this fragment. This is used to properly handle /// things like preserve-3d. - spatial_id: wr::SpatialId, + scroll_tree_node_id: ScrollTreeNodeId, /// The clip chain id of this stacking context if it has one. Used for filter clipping. - clip_chain_id: Option<wr::ClipChainId>, - - /// The style of the fragment that established this stacking context. - initializing_fragment_style: Option<ServoArc<ComputedValues>>, + clip_id: Option<ClipId>, - /// The [`FragmentFlags`] of the [`Fragment`] that established this stacking context. - initializing_fragment_flags: FragmentFlags, + /// The [`BoxFragment`] that established this stacking context. We store the fragment here + /// rather than just the style, so that incremental layout can automatically update the style. + initializing_fragment: Option<ArcRefCell<BoxFragment>>, /// The type of this stacking context. Used for collecting and sorting. context_type: StackingContextType, @@ -415,25 +391,23 @@ pub enum DebugPrintField { impl StackingContext { fn create_descendant( &self, - spatial_id: wr::SpatialId, - clip_chain_id: wr::ClipChainId, - initializing_fragment_style: ServoArc<ComputedValues>, - initializing_fragment_flags: FragmentFlags, + spatial_id: ScrollTreeNodeId, + clip_id: ClipId, + initializing_fragment: ArcRefCell<BoxFragment>, context_type: StackingContextType, ) -> Self { // WebRender has two different ways of expressing "no clip." ClipChainId::INVALID should be // used for primitives, but `None` is used for stacking contexts and clip chains. We convert - // to the `Option<ClipChainId>` representation here. Just passing Some(ClipChainId::INVALID) + // to the `Option<ClipId>` representation here. Just passing Some(ClipChainId::INVALID) // leads to a crash. - let clip_chain_id: Option<ClipChainId> = match clip_chain_id { - ClipChainId::INVALID => None, - clip_chain_id => Some(clip_chain_id), + let clip_id = match clip_id { + ClipId::INVALID => None, + clip_id => Some(clip_id), }; Self { - spatial_id, - clip_chain_id, - initializing_fragment_style: Some(initializing_fragment_style), - initializing_fragment_flags, + scroll_tree_node_id: spatial_id, + clip_id, + initializing_fragment: Some(initializing_fragment), context_type, contents: vec![], real_stacking_contexts_and_positioned_stacking_containers: vec![], @@ -443,12 +417,11 @@ impl StackingContext { } } - pub(crate) fn create_root(wr: &wr::DisplayListBuilder, debug: &DebugOptions) -> Self { + fn create_root(root_scroll_node_id: ScrollTreeNodeId, debug: &DebugOptions) -> Self { Self { - spatial_id: wr::SpaceAndClipInfo::root_scroll(wr.pipeline_id).spatial_id, - clip_chain_id: None, - initializing_fragment_style: None, - initializing_fragment_flags: FragmentFlags::empty(), + scroll_tree_node_id: root_scroll_node_id, + clip_id: None, + initializing_fragment: None, context_type: StackingContextType::RealStackingContext, contents: vec![], real_stacking_contexts_and_positioned_stacking_containers: vec![], @@ -476,11 +449,10 @@ impl StackingContext { } fn z_index(&self) -> i32 { - self.initializing_fragment_style - .as_ref() - .map_or(0, |style| { - style.effective_z_index(self.initializing_fragment_flags) - }) + self.initializing_fragment.as_ref().map_or(0, |fragment| { + let fragment = fragment.borrow(); + fragment.style.effective_z_index(fragment.base.flags) + }) } pub(crate) fn sort(&mut self) { @@ -519,13 +491,14 @@ impl StackingContext { &self, builder: &mut DisplayListBuilder, ) -> bool { - let style = match self.initializing_fragment_style.as_ref() { - Some(style) => style, + let fragment = match self.initializing_fragment.as_ref() { + Some(fragment) => fragment.borrow(), None => return false, }; // WebRender only uses the stacking context to apply certain effects. If we don't // actually need to create a stacking context, just avoid creating one. + let style = &fragment.style; let effects = style.get_effects(); if effects.filter.0.is_empty() && effects.opacity == 1.0 && @@ -557,11 +530,13 @@ impl StackingContext { // This will require additional tracking during layout // before we start collecting stacking contexts so that // information will be available when we reach this point. + let spatial_id = builder.spatial_id(self.scroll_tree_node_id); + let clip_chain_id = self.clip_id.map(|clip_id| builder.clip_chain_id(clip_id)); builder.wr().push_stacking_context( LayoutPoint::zero(), // origin - self.spatial_id, + spatial_id, style.get_webrender_primitive_flags(), - self.clip_chain_id, + clip_chain_id, style.get_used_transform_style().to_webrender(), effects.mix_blend_mode.to_webrender(), &filters, @@ -635,10 +610,7 @@ impl StackingContext { if background_color.alpha > 0.0 { let common = builder.common_properties(painting_area, &source_style); let color = super::rgba(background_color); - builder - .display_list - .wr - .push_rect(&common, painting_area, color) + builder.wr().push_rect(&common, painting_area, color) } let mut fragment_builder = BuilderForBoxFragment::new( @@ -741,7 +713,7 @@ impl StackingContext { } if pushed_context { - builder.display_list.wr.pop_stacking_context(); + builder.wr().pop_stacking_context(); } } @@ -824,7 +796,7 @@ pub(crate) enum StackingContextBuildMode { impl Fragment { pub(crate) fn build_stacking_context_tree( &self, - display_list: &mut DisplayList, + stacking_context_tree: &mut StackingContextTree, containing_block_info: &ContainingBlockInfo, stacking_context: &mut StackingContext, mode: StackingContextBuildMode, @@ -852,7 +824,7 @@ impl Fragment { fragment.build_stacking_context_tree( fragment_clone, - display_list, + stacking_context_tree, containing_block, containing_block_info, stacking_context, @@ -866,7 +838,7 @@ impl Fragment { }; fragment_ref.build_stacking_context_tree( - display_list, + stacking_context_tree, containing_block_info, stacking_context, StackingContextBuildMode::IncludeHoisted, @@ -875,7 +847,7 @@ impl Fragment { Fragment::Positioning(fragment) => { let fragment = fragment.borrow(); fragment.build_stacking_context_tree( - display_list, + stacking_context_tree, containing_block, containing_block_info, stacking_context, @@ -890,7 +862,7 @@ impl Fragment { reference_frame_scroll_node_id: containing_block_info .for_absolute_and_fixed_descendants .scroll_node_id, - clip_chain_id: containing_block.clip_chain_id, + clip_id: containing_block.clip_id, containing_block: containing_block.rect, fragment: fragment_clone, is_hit_test_for_scrollable_overflow: false, @@ -912,7 +884,7 @@ struct ScrollFrameData { } struct OverflowFrameData { - clip_chain_id: wr::ClipChainId, + clip_id: ClipId, scroll_frame_data: Option<ScrollFrameData>, } @@ -953,14 +925,14 @@ impl BoxFragment { fn build_stacking_context_tree( &self, fragment: Fragment, - display_list: &mut DisplayList, + stacking_context_tree: &mut StackingContextTree, containing_block: &ContainingBlock, containing_block_info: &ContainingBlockInfo, parent_stacking_context: &mut StackingContext, ) { self.build_stacking_context_tree_maybe_creating_reference_frame( fragment, - display_list, + stacking_context_tree, containing_block, containing_block_info, parent_stacking_context, @@ -970,7 +942,7 @@ impl BoxFragment { fn build_stacking_context_tree_maybe_creating_reference_frame( &self, fragment: Fragment, - display_list: &mut DisplayList, + stacking_context_tree: &mut StackingContextTree, containing_block: &ContainingBlock, containing_block_info: &ContainingBlockInfo, parent_stacking_context: &mut StackingContext, @@ -981,7 +953,7 @@ impl BoxFragment { None => { return self.build_stacking_context_tree_maybe_creating_stacking_context( fragment, - display_list, + stacking_context_tree, containing_block, containing_block_info, parent_stacking_context, @@ -989,11 +961,11 @@ impl BoxFragment { }, }; - let new_spatial_id = display_list.push_reference_frame( + let new_spatial_id = stacking_context_tree.push_reference_frame( reference_frame_data.origin.to_webrender(), &containing_block.scroll_node_id, self.style.get_box().transform_style.to_webrender(), - wr::PropertyBinding::Value(reference_frame_data.transform), + reference_frame_data.transform, reference_frame_data.kind, ); @@ -1016,26 +988,24 @@ impl BoxFragment { .translate(-reference_frame_data.origin.to_vector()), new_spatial_id, None, - containing_block.clip_chain_id, + containing_block.clip_id, ); let new_containing_block_info = containing_block_info.new_for_non_absolute_descendants(&adjusted_containing_block); self.build_stacking_context_tree_maybe_creating_stacking_context( fragment, - display_list, + stacking_context_tree, &adjusted_containing_block, &new_containing_block_info, parent_stacking_context, ); - - display_list.pop_reference_frame(); } fn build_stacking_context_tree_maybe_creating_stacking_context( &self, fragment: Fragment, - display_list: &mut DisplayList, + stacking_context_tree: &mut StackingContextTree, containing_block: &ContainingBlock, containing_block_info: &ContainingBlockInfo, parent_stacking_context: &mut StackingContext, @@ -1045,7 +1015,7 @@ impl BoxFragment { None => { self.build_stacking_context_tree_for_children( fragment, - display_list, + stacking_context_tree, containing_block, containing_block_info, parent_stacking_context, @@ -1068,30 +1038,37 @@ impl BoxFragment { // `clip-path` needs to be applied before filters and creates a stacking context, so it can be // applied directly to the stacking context itself. // before - let stacking_context_clip_chain_id = build_clip_path_clip_chain_if_necessary( - self.style.clone_clip_path(), - display_list, - &containing_block.scroll_node_id, - &containing_block.clip_chain_id, - BuilderForBoxFragment::new( - self, - &containing_block.rect, - false, /* is_hit_test_for_scrollable_overflow */ - false, /* is_collapsed_table_borders */ - ), - ) - .unwrap_or(containing_block.clip_chain_id); + let stacking_context_clip_id = stacking_context_tree + .clip_store + .add_for_clip_path( + self.style.clone_clip_path(), + &containing_block.scroll_node_id, + &containing_block.clip_id, + BuilderForBoxFragment::new( + self, + &containing_block.rect, + false, /* is_hit_test_for_scrollable_overflow */ + false, /* is_collapsed_table_borders */ + ), + ) + .unwrap_or(containing_block.clip_id); + + let box_fragment = match fragment { + Fragment::Box(ref box_fragment) | Fragment::Float(ref box_fragment) => { + box_fragment.clone() + }, + _ => unreachable!("Should never try to make stacking context for non-BoxFragment"), + }; let mut child_stacking_context = parent_stacking_context.create_descendant( - containing_block.scroll_node_id.spatial_id, - stacking_context_clip_chain_id, - self.style.clone(), - self.base.flags, + containing_block.scroll_node_id, + stacking_context_clip_id, + box_fragment, context_type, ); self.build_stacking_context_tree_for_children( fragment, - display_list, + stacking_context_tree, containing_block, containing_block_info, &mut child_stacking_context, @@ -1116,19 +1093,19 @@ impl BoxFragment { fn build_stacking_context_tree_for_children( &self, fragment: Fragment, - display_list: &mut DisplayList, + stacking_context_tree: &mut StackingContextTree, containing_block: &ContainingBlock, containing_block_info: &ContainingBlockInfo, stacking_context: &mut StackingContext, ) { let mut new_scroll_node_id = containing_block.scroll_node_id; - let mut new_clip_chain_id = containing_block.clip_chain_id; + let mut new_clip_id = containing_block.clip_id; let mut new_scroll_frame_size = containing_block_info .for_non_absolute_descendants .scroll_frame_size; if let Some(scroll_node_id) = self.build_sticky_frame_if_necessary( - display_list, + stacking_context_tree, &new_scroll_node_id, &containing_block.rect, &new_scroll_frame_size, @@ -1136,20 +1113,19 @@ impl BoxFragment { new_scroll_node_id = scroll_node_id; } - if let Some(clip_chain_id) = self.build_clip_frame_if_necessary( - display_list, + if let Some(clip_id) = self.build_clip_frame_if_necessary( + stacking_context_tree, &new_scroll_node_id, - &new_clip_chain_id, + new_clip_id, &containing_block.rect, ) { - new_clip_chain_id = clip_chain_id; + new_clip_id = clip_id; } - if let Some(clip_chain_id) = build_clip_path_clip_chain_if_necessary( + if let Some(clip_id) = stacking_context_tree.clip_store.add_for_clip_path( self.style.clone_clip_path(), - display_list, &new_scroll_node_id, - &new_clip_chain_id, + &new_clip_id, BuilderForBoxFragment::new( self, &containing_block.rect, @@ -1157,7 +1133,7 @@ impl BoxFragment { false, /* is_collapsed_table_borders */ ), ) { - new_clip_chain_id = clip_chain_id; + new_clip_id = clip_id; } let establishes_containing_block_for_all_descendants = self @@ -1182,7 +1158,7 @@ impl BoxFragment { .push(StackingContextContent::Fragment { scroll_node_id: new_scroll_node_id, reference_frame_scroll_node_id: reference_frame_scroll_node_id_for_fragments, - clip_chain_id: new_clip_chain_id, + clip_id: new_clip_id, section, containing_block: containing_block.rect, fragment: fragment.clone(), @@ -1200,12 +1176,12 @@ impl BoxFragment { // We want to build the scroll frame after the background and border, because // they shouldn't scroll with the rest of the box content. if let Some(overflow_frame_data) = self.build_overflow_frame_if_necessary( - display_list, + stacking_context_tree, &new_scroll_node_id, - &new_clip_chain_id, + new_clip_id, &containing_block.rect, ) { - new_clip_chain_id = overflow_frame_data.clip_chain_id; + new_clip_id = overflow_frame_data.clip_id; if let Some(scroll_frame_data) = overflow_frame_data.scroll_frame_data { new_scroll_node_id = scroll_frame_data.scroll_tree_node_id; new_scroll_frame_size = Some(scroll_frame_data.scroll_frame_rect.size()); @@ -1216,7 +1192,7 @@ impl BoxFragment { scroll_node_id: new_scroll_node_id, reference_frame_scroll_node_id: reference_frame_scroll_node_id_for_fragments, - clip_chain_id: new_clip_chain_id, + clip_id: new_clip_id, section, containing_block: containing_block.rect, fragment: fragment.clone(), @@ -1237,13 +1213,13 @@ impl BoxFragment { padding_rect, new_scroll_node_id, new_scroll_frame_size, - new_clip_chain_id, + new_clip_id, ); let for_non_absolute_descendants = ContainingBlock::new( content_rect, new_scroll_node_id, new_scroll_frame_size, - new_clip_chain_id, + new_clip_id, ); // Create a new `ContainingBlockInfo` for descendants depending on @@ -1265,7 +1241,7 @@ impl BoxFragment { for child in &self.children { child.build_stacking_context_tree( - display_list, + stacking_context_tree, &new_containing_block_info, stacking_context, StackingContextBuildMode::SkipHoisted, @@ -1281,7 +1257,7 @@ impl BoxFragment { .push(StackingContextContent::Fragment { scroll_node_id: new_scroll_node_id, reference_frame_scroll_node_id: reference_frame_scroll_node_id_for_fragments, - clip_chain_id: new_clip_chain_id, + clip_id: new_clip_id, section, containing_block: containing_block.rect, fragment: fragment.clone(), @@ -1293,11 +1269,11 @@ impl BoxFragment { fn build_clip_frame_if_necessary( &self, - display_list: &mut DisplayList, + stacking_context_tree: &mut StackingContextTree, parent_scroll_node_id: &ScrollTreeNodeId, - parent_clip_chain_id: &wr::ClipChainId, + parent_clip_id: ClipId, containing_block_rect: &PhysicalRect<Au>, - ) -> Option<wr::ClipChainId> { + ) -> Option<ClipId> { let position = self.style.get_box().position; // https://drafts.csswg.org/css2/#clipping // The clip property applies only to absolutely positioned elements @@ -1316,18 +1292,19 @@ impl BoxFragment { .for_border_rect(border_rect) .translate(containing_block_rect.origin.to_vector()) .to_webrender(); - - let clip_id = display_list - .wr - .define_clip_rect(parent_scroll_node_id.spatial_id, clip_rect); - Some(display_list.define_clip_chain(*parent_clip_chain_id, [clip_id])) + Some(stacking_context_tree.clip_store.add( + BorderRadius::zero(), + clip_rect, + *parent_scroll_node_id, + parent_clip_id, + )) } fn build_overflow_frame_if_necessary( &self, - display_list: &mut DisplayList, + stacking_context_tree: &mut StackingContextTree, parent_scroll_node_id: &ScrollTreeNodeId, - parent_clip_chain_id: &wr::ClipChainId, + parent_clip_id: ClipId, containing_block_rect: &PhysicalRect<Au>, ) -> Option<OverflowFrameData> { let overflow = self.style.effective_overflow(self.base.flags); @@ -1367,15 +1344,15 @@ impl BoxFragment { radii = BorderRadius::zero(); } - let clip_chain_id = display_list.clip_overflow_frame( - parent_scroll_node_id, - parent_clip_chain_id, - overflow_clip_rect, + let clip_id = stacking_context_tree.clip_store.add( radii, + overflow_clip_rect, + *parent_scroll_node_id, + parent_clip_id, ); return Some(OverflowFrameData { - clip_chain_id, + clip_id, scroll_frame_data: None, }); } @@ -1404,17 +1381,17 @@ impl BoxFragment { .translate(containing_block_rect.origin.to_vector()) .to_webrender(); - let clip_chain_id = display_list.clip_overflow_frame( - parent_scroll_node_id, - parent_clip_chain_id, - scroll_frame_rect, + let clip_id = stacking_context_tree.clip_store.add( BuilderForBoxFragment::new(self, containing_block_rect, false, false).border_radius, + scroll_frame_rect, + *parent_scroll_node_id, + parent_clip_id, ); let tag = self.base.tag?; let external_id = wr::ExternalScrollId( tag.to_display_list_fragment_id(), - display_list.wr.pipeline_id, + stacking_context_tree.compositor_info.pipeline_id, ); let sensitivity = AxesScrollSensitivity { @@ -1424,7 +1401,7 @@ impl BoxFragment { let content_rect = self.reachable_scrollable_overflow_region().to_webrender(); - let scroll_tree_node_id = display_list.define_scroll_frame( + let scroll_tree_node_id = stacking_context_tree.define_scroll_frame( parent_scroll_node_id, external_id, content_rect, @@ -1433,7 +1410,7 @@ impl BoxFragment { ); Some(OverflowFrameData { - clip_chain_id, + clip_id, scroll_frame_data: Some(ScrollFrameData { scroll_tree_node_id, scroll_frame_rect, @@ -1443,7 +1420,7 @@ impl BoxFragment { fn build_sticky_frame_if_necessary( &self, - display_list: &mut DisplayList, + stacking_context_tree: &mut StackingContextTree, parent_scroll_node_id: &ScrollTreeNodeId, containing_block_rect: &PhysicalRect<Au>, scroll_frame_size: &Option<LayoutSize>, @@ -1456,7 +1433,7 @@ impl BoxFragment { Some(size) => size, None => { // This is a direct descendant of a reference frame. - &display_list.compositor_info.viewport_size + &stacking_context_tree.compositor_info.viewport_size }, }; @@ -1513,7 +1490,7 @@ impl BoxFragment { offsets.left.non_auto().map(|v| v.to_f32_px()), ); - let sticky_node_id = display_list.define_sticky_frame( + let sticky_node_id = stacking_context_tree.define_sticky_frame( parent_scroll_node_id, frame_rect, margins, @@ -1665,7 +1642,7 @@ impl BoxFragment { impl PositioningFragment { fn build_stacking_context_tree( &self, - display_list: &mut DisplayList, + stacking_context_tree: &mut StackingContextTree, containing_block: &ContainingBlock, containing_block_info: &ContainingBlockInfo, stacking_context: &mut StackingContext, @@ -1679,7 +1656,7 @@ impl PositioningFragment { for child in &self.children { child.build_stacking_context_tree( - display_list, + stacking_context_tree, &new_containing_block_info, stacking_context, StackingContextBuildMode::SkipHoisted, diff --git a/components/layout/dom.rs b/components/layout/dom.rs index e3a22eb5197..88176ffbbb0 100644 --- a/components/layout/dom.rs +++ b/components/layout/dom.rs @@ -126,15 +126,15 @@ impl LayoutBox { .repair_style(context, node, new_style); } }, - LayoutBox::FlexLevel(flex_level_box) => { - flex_level_box.borrow_mut().repair_style(context, new_style) - }, + LayoutBox::FlexLevel(flex_level_box) => flex_level_box + .borrow_mut() + .repair_style(context, node, new_style), LayoutBox::TableLevelBox(table_level_box) => { - table_level_box.repair_style(context, new_style) - }, - LayoutBox::TaffyItemBox(taffy_item_box) => { - taffy_item_box.borrow_mut().repair_style(context, new_style) + table_level_box.repair_style(context, node, new_style) }, + LayoutBox::TaffyItemBox(taffy_item_box) => taffy_item_box + .borrow_mut() + .repair_style(context, node, new_style), } } } diff --git a/components/layout/flexbox/mod.rs b/components/layout/flexbox/mod.rs index 91a12b31812..96a311ee2b5 100644 --- a/components/layout/flexbox/mod.rs +++ b/components/layout/flexbox/mod.rs @@ -4,6 +4,7 @@ use geom::{FlexAxis, MainStartCrossStart}; use malloc_size_of_derive::MallocSizeOf; +use script::layout_dom::ServoLayoutNode; use servo_arc::Arc as ServoArc; use style::context::SharedStyleContext; use style::logical_geometry::WritingMode; @@ -154,16 +155,17 @@ impl FlexLevelBox { pub(crate) fn repair_style( &mut self, context: &SharedStyleContext, + node: &ServoLayoutNode, new_style: &ServoArc<ComputedValues>, ) { match self { FlexLevelBox::FlexItem(flex_item_box) => flex_item_box .independent_formatting_context - .repair_style(context, new_style), + .repair_style(context, node, new_style), FlexLevelBox::OutOfFlowAbsolutelyPositionedBox(positioned_box) => positioned_box .borrow_mut() .context - .repair_style(context, new_style), + .repair_style(context, node, new_style), } } diff --git a/components/layout/flow/inline/construct.rs b/components/layout/flow/inline/construct.rs index a99de1679a4..600da9b721a 100644 --- a/components/layout/flow/inline/construct.rs +++ b/components/layout/flow/inline/construct.rs @@ -31,7 +31,7 @@ pub(crate) struct InlineFormattingContextBuilder { /// inline box stack, and importantly, one for every `display: contents` element that we are /// currently processing. Normally `display: contents` elements don't affect the structure of /// the [`InlineFormattingContext`], but the styles they provide do style their children. - shared_inline_styles_stack: Vec<SharedInlineStyles>, + pub shared_inline_styles_stack: Vec<SharedInlineStyles>, /// The collection of text strings that make up this [`InlineFormattingContext`] under /// construction. diff --git a/components/layout/flow/inline/mod.rs b/components/layout/flow/inline/mod.rs index 7e69aa1aaae..74d42ca6fb4 100644 --- a/components/layout/flow/inline/mod.rs +++ b/components/layout/flow/inline/mod.rs @@ -91,6 +91,7 @@ use line_breaker::LineBreaker; use malloc_size_of_derive::MallocSizeOf; use range::Range; use script::layout_dom::ServoLayoutNode; +use script_layout_interface::wrapper_traits::{LayoutNode, ThreadSafeLayoutNode}; use servo_arc::Arc; use style::Zero; use style::computed_values::text_wrap_mode::T as TextWrapMode; @@ -158,6 +159,10 @@ pub(crate) struct InlineFormattingContext { /// context in order to avoid duplicating this information. pub font_metrics: Vec<FontKeyAndMetrics>, + /// The [`SharedInlineStyles`] for the root of this [`InlineFormattingContext`] that are used to + /// share styles with all [`TextRun`] children. + pub(super) shared_inline_styles: SharedInlineStyles, + pub(super) text_decoration_line: TextDecorationLine, /// Whether this IFC contains the 1st formatted line of an element: @@ -237,12 +242,14 @@ impl InlineItem { InlineItem::OutOfFlowAbsolutelyPositionedBox(positioned_box, ..) => positioned_box .borrow_mut() .context - .repair_style(context, new_style), + .repair_style(context, node, new_style), InlineItem::OutOfFlowFloatBox(float_box) => float_box .borrow_mut() .contents - .repair_style(context, new_style), - InlineItem::Atomic(atomic, ..) => atomic.borrow_mut().repair_style(context, new_style), + .repair_style(context, node, new_style), + InlineItem::Atomic(atomic, ..) => { + atomic.borrow_mut().repair_style(context, node, new_style) + }, } } @@ -1699,6 +1706,11 @@ impl InlineFormattingContext { inline_items: builder.inline_items, inline_boxes: builder.inline_boxes, font_metrics, + shared_inline_styles: builder + .shared_inline_styles_stack + .last() + .expect("Should have at least one SharedInlineStyle for the root of an IFC") + .clone(), text_decoration_line: propagated_data.text_decoration, has_first_formatted_line, contains_floats: builder.contains_floats, @@ -1707,6 +1719,11 @@ impl InlineFormattingContext { } } + pub(crate) fn repair_style(&self, node: &ServoLayoutNode, new_style: &Arc<ComputedValues>) { + *self.shared_inline_styles.style.borrow_mut() = new_style.clone(); + *self.shared_inline_styles.selected.borrow_mut() = node.to_threadsafe().selected_style(); + } + pub(super) fn layout( &self, layout_context: &LayoutContext, diff --git a/components/layout/flow/mod.rs b/components/layout/flow/mod.rs index 99b84d088e5..4776b65771c 100644 --- a/components/layout/flow/mod.rs +++ b/components/layout/flow/mod.rs @@ -78,6 +78,15 @@ impl BlockContainer { BlockContainer::InlineFormattingContext(context) => context.contains_floats, } } + + pub(crate) fn repair_style(&mut self, node: &ServoLayoutNode, new_style: &Arc<ComputedValues>) { + match self { + BlockContainer::BlockLevelBoxes(..) => {}, + BlockContainer::InlineFormattingContext(inline_formatting_context) => { + inline_formatting_context.repair_style(node, new_style) + }, + } + } } #[derive(Debug, MallocSizeOf)] @@ -106,20 +115,21 @@ impl BlockLevelBox { match self { BlockLevelBox::Independent(independent_formatting_context) => { - independent_formatting_context.repair_style(context, new_style) + independent_formatting_context.repair_style(context, node, new_style) }, BlockLevelBox::OutOfFlowAbsolutelyPositionedBox(positioned_box) => positioned_box .borrow_mut() .context - .repair_style(context, new_style), + .repair_style(context, node, new_style), BlockLevelBox::OutOfFlowFloatBox(float_box) => { - float_box.contents.repair_style(context, new_style) + float_box.contents.repair_style(context, node, new_style) }, BlockLevelBox::OutsideMarker(outside_marker) => { outside_marker.repair_style(context, node, new_style) }, - BlockLevelBox::SameFormattingContextBlock { base, .. } => { + BlockLevelBox::SameFormattingContextBlock { base, contents, .. } => { base.repair_style(new_style); + contents.repair_style(node, new_style); }, } } @@ -477,6 +487,10 @@ impl BlockFormattingContext { pub(crate) fn layout_style<'a>(&self, base: &'a LayoutBoxBase) -> LayoutStyle<'a> { LayoutStyle::Default(&base.style) } + + pub(crate) fn repair_style(&mut self, node: &ServoLayoutNode, new_style: &Arc<ComputedValues>) { + self.contents.repair_style(node, new_style); + } } /// Finds the min/max-content inline size of the block-level children of a block container. diff --git a/components/layout/formatting_contexts.rs b/components/layout/formatting_contexts.rs index a489df2b663..2b242c00361 100644 --- a/components/layout/formatting_contexts.rs +++ b/components/layout/formatting_contexts.rs @@ -4,7 +4,7 @@ use app_units::Au; use malloc_size_of_derive::MallocSizeOf; -use script::layout_dom::ServoLayoutElement; +use script::layout_dom::{ServoLayoutElement, ServoLayoutNode}; use servo_arc::Arc; use style::context::SharedStyleContext; use style::properties::ComputedValues; @@ -223,12 +223,13 @@ impl IndependentFormattingContext { pub(crate) fn repair_style( &mut self, context: &SharedStyleContext, + node: &ServoLayoutNode, new_style: &Arc<ComputedValues>, ) { self.base.repair_style(new_style); match &mut self.contents { IndependentFormattingContextContents::NonReplaced(content) => { - content.repair_style(context, new_style); + content.repair_style(context, node, new_style); }, IndependentFormattingContextContents::Replaced(..) => {}, } @@ -356,9 +357,16 @@ impl IndependentNonReplacedContents { matches!(self, Self::Table(_)) } - fn repair_style(&mut self, context: &SharedStyleContext, new_style: &Arc<ComputedValues>) { + fn repair_style( + &mut self, + context: &SharedStyleContext, + node: &ServoLayoutNode, + new_style: &Arc<ComputedValues>, + ) { match self { - IndependentNonReplacedContents::Flow(..) => {}, + IndependentNonReplacedContents::Flow(block_formatting_context) => { + block_formatting_context.repair_style(node, new_style); + }, IndependentNonReplacedContents::Flex(flex_container) => { flex_container.repair_style(new_style) }, diff --git a/components/layout/fragment_tree/box_fragment.rs b/components/layout/fragment_tree/box_fragment.rs index 9b96b1c4fb4..b7c3a2a3524 100644 --- a/components/layout/fragment_tree/box_fragment.rs +++ b/components/layout/fragment_tree/box_fragment.rs @@ -92,7 +92,7 @@ pub(crate) struct BoxFragment { pub scrollable_overflow_from_children: PhysicalRect<Au>, /// The resolved box insets if this box is `position: sticky`. These are calculated - /// during stacking context tree construction because they rely on the size of the + /// during `StackingContextTree` construction because they rely on the size of the /// scroll container. pub(crate) resolved_sticky_insets: AtomicRefCell<Option<PhysicalSides<AuOrAuto>>>, diff --git a/components/layout/fragment_tree/fragment_tree.rs b/components/layout/fragment_tree/fragment_tree.rs index 979bd0090fc..ba03a72ac21 100644 --- a/components/layout/fragment_tree/fragment_tree.rs +++ b/components/layout/fragment_tree/fragment_tree.rs @@ -14,7 +14,6 @@ use webrender_api::units; use super::{BoxFragment, ContainingBlockManager, Fragment}; use crate::ArcRefCell; use crate::context::LayoutContext; -use crate::display_list::StackingContext; use crate::geom::PhysicalRect; #[derive(MallocSizeOf)] @@ -91,16 +90,6 @@ impl FragmentTree { fragment_tree } - pub(crate) fn build_display_list( - &self, - builder: &mut crate::display_list::DisplayListBuilder, - root_stacking_context: &StackingContext, - ) { - // Paint the canvas’ background (if any) before/under everything else - root_stacking_context.build_canvas_background_display_list(builder, self); - root_stacking_context.build_display_list(builder); - } - pub fn print(&self) { let mut print_tree = PrintTree::new("Fragment Tree".to_string()); for fragment in &self.root_fragments { diff --git a/components/layout/layout_impl.rs b/components/layout/layout_impl.rs index fcf658036b2..b490b4a0506 100644 --- a/components/layout/layout_impl.rs +++ b/components/layout/layout_impl.rs @@ -77,7 +77,7 @@ use webrender_api::units::{DevicePixel, DevicePoint, LayoutPixel, LayoutPoint, L use webrender_api::{ExternalScrollId, HitTestFlags}; use crate::context::LayoutContext; -use crate::display_list::{DisplayList, WebRenderImageInfo}; +use crate::display_list::{DisplayListBuilder, StackingContextTree, WebRenderImageInfo}; use crate::query::{ get_the_text_steps, process_client_rect_request, process_content_box_request, process_content_boxes_request, process_node_scroll_area_request, process_offset_parent_query, @@ -144,6 +144,9 @@ pub struct LayoutThread { /// The fragment tree. fragment_tree: RefCell<Option<Arc<FragmentTree>>>, + /// The [`StackingContextTree`] cached from previous layouts. + stacking_context_tree: RefCell<Option<StackingContextTree>>, + /// A counter for epoch messages epoch: Cell<Epoch>, @@ -521,6 +524,7 @@ impl LayoutThread { first_reflow: Cell::new(true), box_tree: Default::default(), fragment_tree: Default::default(), + stacking_context_tree: Default::default(), // Epoch starts at 1 because of the initial display list for epoch 0 that we send to WR epoch: Cell::new(Epoch(1)), viewport_size: Size2D::new( @@ -649,15 +653,17 @@ impl LayoutThread { highlighted_dom_node: reflow_request.highlighted_dom_node, }; - self.restyle_and_build_trees( + let did_reflow = self.restyle_and_build_trees( &reflow_request, root_element, rayon_pool, &mut layout_context, ); + + self.build_stacking_context_tree(&reflow_request, did_reflow); self.build_display_list(&reflow_request, &mut layout_context); - self.first_reflow.set(false); + self.first_reflow.set(false); if let ReflowGoal::UpdateScrollNode(scroll_state) = reflow_request.reflow_goal { self.update_scroll_node_state(&scroll_state); } @@ -666,6 +672,7 @@ impl LayoutThread { let iframe_sizes = std::mem::take(&mut *layout_context.iframe_sizes.lock()); let node_to_image_animation_map = std::mem::take(&mut *layout_context.node_image_animation_map.write()); + Some(ReflowResult { pending_images, iframe_sizes, @@ -742,7 +749,7 @@ impl LayoutThread { root_element: ServoLayoutElement<'_>, rayon_pool: Option<&ThreadPool>, layout_context: &mut LayoutContext<'_>, - ) { + ) -> bool { let dirty_root = unsafe { ServoLayoutNode::new(&reflow_request.dirty_root.unwrap()) .as_element() @@ -758,7 +765,7 @@ impl LayoutThread { if !token.should_traverse() { layout_context.style_context.stylist.rule_tree().maybe_gc(); - return; + return false; } let dirty_root: ServoLayoutNode = @@ -768,7 +775,7 @@ impl LayoutThread { let damage = compute_damage_and_repair_style(layout_context.shared_context(), root_node); if damage == RestyleDamage::REPAINT { layout_context.style_context.stylist.rule_tree().maybe_gc(); - return; + return false; } let mut box_tree = self.box_tree.borrow_mut(); @@ -803,8 +810,15 @@ impl LayoutThread { run_layout() }); + if self.debug.dump_flow_tree { + fragment_tree.print(); + } *self.fragment_tree.borrow_mut() = Some(fragment_tree); + // The FragmentTree has been updated, so any existing StackingContext tree that layout + // had is now out of date and should be rebuilt. + *self.stacking_context_tree.borrow_mut() = None; + if self.debug.dump_style_tree { println!( "{:?}", @@ -822,75 +836,80 @@ impl LayoutThread { // GC the rule tree if some heuristics are met. layout_context.style_context.stylist.rule_tree().maybe_gc(); + true } - fn build_display_list( - &self, - reflow_request: &ReflowRequest, - layout_context: &mut LayoutContext<'_>, - ) { + fn build_stacking_context_tree(&self, reflow_request: &ReflowRequest, did_reflow: bool) { + if !reflow_request.reflow_goal.needs_display_list() && + !reflow_request.reflow_goal.needs_display() + { + return; + } let Some(fragment_tree) = &*self.fragment_tree.borrow() else { return; }; - if !reflow_request.reflow_goal.needs_display_list() { + if !did_reflow && self.stacking_context_tree.borrow().is_some() { return; } - let mut epoch = self.epoch.get(); - epoch.next(); - self.epoch.set(epoch); - let viewport_size = LayoutSize::from_untyped(Size2D::new( self.viewport_size.width.to_f32_px(), self.viewport_size.height.to_f32_px(), )); - let mut display_list = DisplayList::new( + + // Build the StackingContextTree. This turns the `FragmentTree` into a + // tree of fragments in CSS painting order and also creates all + // applicable spatial and clip nodes. + *self.stacking_context_tree.borrow_mut() = Some(StackingContextTree::new( + fragment_tree, viewport_size, fragment_tree.scrollable_overflow(), self.id.into(), - epoch.into(), fragment_tree.viewport_scroll_sensitivity, self.first_reflow.get(), - ); - display_list.wr.begin(); - - // `dump_serialized_display_list` doesn't actually print anything. It sets up - // the display list for printing the serialized version when `finalize()` is called. - // We need to call this before adding any display items so that they are printed - // during `finalize()`. - if self.debug.dump_display_list { - display_list.wr.dump_serialized_display_list(); - } + &self.debug, + )); + } - // Build the root stacking context. This turns the `FragmentTree` into a - // tree of fragments in CSS painting order and also creates all - // applicable spatial and clip nodes. - let root_stacking_context = - display_list.build_stacking_context_tree(fragment_tree, &self.debug); + fn build_display_list( + &self, + reflow_request: &ReflowRequest, + layout_context: &mut LayoutContext<'_>, + ) { + if !reflow_request.reflow_goal.needs_display() { + return; + } + let Some(fragment_tree) = &*self.fragment_tree.borrow() else { + return; + }; - // Build the rest of the display list which inclues all of the WebRender primitives. - display_list.build(layout_context, fragment_tree, &root_stacking_context); + let mut stacking_context_tree = self.stacking_context_tree.borrow_mut(); + let Some(stacking_context_tree) = stacking_context_tree.as_mut() else { + return; + }; - if self.debug.dump_flow_tree { - fragment_tree.print(); - } - if self.debug.dump_stacking_context_tree { - root_stacking_context.debug_print(); - } + let mut epoch = self.epoch.get(); + epoch.next(); + self.epoch.set(epoch); + stacking_context_tree.compositor_info.epoch = epoch.into(); - if reflow_request.reflow_goal.needs_display() { - self.compositor_api.send_display_list( - self.webview_id, - display_list.compositor_info, - display_list.wr.end().1, - ); + let built_display_list = DisplayListBuilder::build( + layout_context, + stacking_context_tree, + fragment_tree, + &self.debug, + ); + self.compositor_api.send_display_list( + self.webview_id, + &stacking_context_tree.compositor_info, + built_display_list, + ); - let (keys, instance_keys) = self - .font_context - .collect_unused_webrender_resources(false /* all */); - self.compositor_api - .remove_unused_font_resources(keys, instance_keys) - } + let (keys, instance_keys) = self + .font_context + .collect_unused_webrender_resources(false /* all */); + self.compositor_api + .remove_unused_font_resources(keys, instance_keys) } fn update_scroll_node_state(&self, state: &ScrollState) { diff --git a/components/layout/table/mod.rs b/components/layout/table/mod.rs index 72b67863e7d..78884c377e9 100644 --- a/components/layout/table/mod.rs +++ b/components/layout/table/mod.rs @@ -76,7 +76,7 @@ pub(crate) use construct::AnonymousTableContent; pub use construct::TableBuilder; use euclid::{Point2D, Size2D, UnknownUnit, Vector2D}; use malloc_size_of_derive::MallocSizeOf; -use script::layout_dom::ServoLayoutElement; +use script::layout_dom::{ServoLayoutElement, ServoLayoutNode}; use servo_arc::Arc; use style::context::SharedStyleContext; use style::properties::ComputedValues; @@ -425,13 +425,14 @@ impl TableLevelBox { pub(crate) fn repair_style( &self, context: &SharedStyleContext<'_>, + node: &ServoLayoutNode, new_style: &Arc<ComputedValues>, ) { match self { TableLevelBox::Caption(caption) => caption .borrow_mut() .context - .repair_style(context, new_style), + .repair_style(context, node, new_style), TableLevelBox::Cell(cell) => cell.borrow_mut().repair_style(new_style), TableLevelBox::TrackGroup(track_group) => { track_group.borrow_mut().repair_style(new_style); diff --git a/components/layout/taffy/mod.rs b/components/layout/taffy/mod.rs index ba80824fa99..2bc7a598d08 100644 --- a/components/layout/taffy/mod.rs +++ b/components/layout/taffy/mod.rs @@ -7,6 +7,7 @@ use std::fmt; use app_units::Au; use malloc_size_of_derive::MallocSizeOf; +use script::layout_dom::ServoLayoutNode; use servo_arc::Arc; use style::context::SharedStyleContext; use style::properties::ComputedValues; @@ -152,17 +153,18 @@ impl TaffyItemBox { pub(crate) fn repair_style( &mut self, context: &SharedStyleContext, + node: &ServoLayoutNode, new_style: &Arc<ComputedValues>, ) { self.style = new_style.clone(); match &mut self.taffy_level_box { TaffyItemBoxInner::InFlowBox(independent_formatting_context) => { - independent_formatting_context.repair_style(context, new_style) + independent_formatting_context.repair_style(context, node, new_style) }, TaffyItemBoxInner::OutOfFlowAbsolutelyPositionedBox(positioned_box) => positioned_box .borrow_mut() .context - .repair_style(context, new_style), + .repair_style(context, node, new_style), } } } diff --git a/components/layout/traversal.rs b/components/layout/traversal.rs index 17c3d0b1c20..d05deb24bfa 100644 --- a/components/layout/traversal.rs +++ b/components/layout/traversal.rs @@ -112,15 +112,14 @@ pub(crate) fn compute_damage_and_repair_style_inner( .element_data .borrow_mut(); + original_damage = std::mem::take(&mut element_data.damage); if let Some(ref style) = element_data.styles.primary { if style.get_box().display == Display::None { return parent_restyle_damage; } } - original_damage = std::mem::take(&mut element_data.damage); - element_data.damage |= parent_restyle_damage; - element_data.damage + original_damage | parent_restyle_damage }; let mut propagated_damage = damage; diff --git a/components/net/Cargo.toml b/components/net/Cargo.toml index 9af310f32bc..1945165b8c1 100644 --- a/components/net/Cargo.toml +++ b/components/net/Cargo.toml @@ -29,6 +29,7 @@ crossbeam-channel = { workspace = true } data-url = { workspace = true } devtools_traits = { workspace = true } embedder_traits = { workspace = true } +fst = "0.4" futures = { version = "0.3", package = "futures" } futures-core = { version = "0.3.30", default-features = false } futures-util = { version = "0.3.30", default-features = false } @@ -77,6 +78,7 @@ webrender_api = { workspace = true } [dev-dependencies] embedder_traits = { workspace = true, features = ["baked-default-resources"] } flate2 = "1" +fst = "0.4" futures = { version = "0.3", features = ["compat"] } hyper = { workspace = true, features = ["full"] } hyper-util = { workspace = true, features = ["server-graceful"] } diff --git a/components/net/fetch/methods.rs b/components/net/fetch/methods.rs index 65c173fce3b..33d0da952fb 100644 --- a/components/net/fetch/methods.rs +++ b/components/net/fetch/methods.rs @@ -18,6 +18,7 @@ use http::{HeaderValue, Method, StatusCode}; use ipc_channel::ipc; use log::{debug, trace, warn}; use mime::{self, Mime}; +use net_traits::fetch::headers::extract_mime_type_as_mime; use net_traits::filemanager_thread::{FileTokenCheck, RelativePos}; use net_traits::http_status::HttpStatus; use net_traits::policy_container::{PolicyContainer, RequestPolicyContainer}; @@ -886,7 +887,7 @@ pub fn should_be_blocked_due_to_nosniff( // Step 2 // Note: an invalid MIME type will produce a `None`. - let content_type_header = response_headers.typed_get::<ContentType>(); + let mime_type = extract_mime_type_as_mime(response_headers); /// <https://html.spec.whatwg.org/multipage/#scriptingLanguages> #[inline] @@ -915,16 +916,12 @@ pub fn should_be_blocked_due_to_nosniff( .any(|mime| mime.type_() == mime_type.type_() && mime.subtype() == mime_type.subtype()) } - match content_type_header { + match mime_type { // Step 4 - Some(ref ct) if destination.is_script_like() => { - !is_javascript_mime_type(&ct.clone().into()) - }, - + Some(ref mime_type) if destination.is_script_like() => !is_javascript_mime_type(mime_type), // Step 5 - Some(ref ct) if destination == Destination::Style => { - let m: mime::Mime = ct.clone().into(); - m.type_() != mime::TEXT && m.subtype() != mime::CSS + Some(ref mime_type) if destination == Destination::Style => { + mime_type.type_() != mime::TEXT && mime_type.subtype() != mime::CSS }, None if destination == Destination::Style || destination.is_script_like() => true, @@ -938,18 +935,22 @@ fn should_be_blocked_due_to_mime_type( destination: Destination, response_headers: &HeaderMap, ) -> bool { - // Step 1 - let mime_type: mime::Mime = match response_headers.typed_get::<ContentType>() { - Some(header) => header.into(), + // Step 1: Let mimeType be the result of extracting a MIME type from response’s header list. + let mime_type: mime::Mime = match extract_mime_type_as_mime(response_headers) { + Some(mime_type) => mime_type, + // Step 2: If mimeType is failure, then return allowed. None => return false, }; - // Step 2-3 + // Step 3: Let destination be request’s destination. + // Step 4: If destination is script-like and one of the following is true, then return blocked: + // - mimeType’s essence starts with "audio/", "image/", or "video/". + // - mimeType’s essence is "text/csv". + // Step 5: Return allowed. destination.is_script_like() && match mime_type.type_() { mime::AUDIO | mime::VIDEO | mime::IMAGE => true, mime::TEXT if mime_type.subtype() == mime::CSV => true, - // Step 4 _ => false, } } diff --git a/components/net/hsts.rs b/components/net/hsts.rs index be955980d2b..c50f3304142 100644 --- a/components/net/hsts.rs +++ b/components/net/hsts.rs @@ -9,9 +9,11 @@ use std::sync::LazyLock; use std::time::Duration; use embedder_traits::resources::{self, Resource}; +use fst::{Map, MapBuilder}; use headers::{HeaderMapExt, StrictTransportSecurity}; use http::HeaderMap; use log::{debug, error, info}; +use malloc_size_of::{MallocSizeOf, MallocSizeOfOps}; use malloc_size_of_derive::MallocSizeOf; use net_traits::IncludeSubdomains; use net_traits::pub_domains::reg_suffix; @@ -85,99 +87,67 @@ pub struct HstsList { /// it is split out to allow sharing between the private and public http state /// as well as potentially swpaping out the underlying type to something immutable /// and more efficient like FSTs or DAFSA/DAWGs. -#[derive(Clone, Debug, Default, Deserialize, MallocSizeOf, Serialize)] -pub struct HstsPreloadList { - pub entries_map: HashMap<String, Vec<HstsEntry>>, +/// To generate a new version of the FST map file run `./mach update-hsts-preload` +#[derive(Clone, Debug)] +pub struct HstsPreloadList(pub fst::Map<Vec<u8>>); + +impl MallocSizeOf for HstsPreloadList { + #[allow(unsafe_code)] + fn size_of(&self, ops: &mut malloc_size_of::MallocSizeOfOps) -> usize { + unsafe { ops.malloc_size_of(self.0.as_fst().as_inner().as_ptr()) } + } } -pub static PRELOAD_LIST_ENTRIES: LazyLock<HstsPreloadList> = +static PRELOAD_LIST_ENTRIES: LazyLock<HstsPreloadList> = LazyLock::new(HstsPreloadList::from_servo_preload); +pub fn hsts_preload_size_of(ops: &mut MallocSizeOfOps) -> usize { + PRELOAD_LIST_ENTRIES.size_of(ops) +} + impl HstsPreloadList { /// Create an `HstsList` from the bytes of a JSON preload file. - pub fn from_preload(preload_content: &str) -> Option<HstsPreloadList> { - #[derive(Deserialize)] - struct HstsEntries { - entries: Vec<HstsEntry>, - } - - let hsts_entries: Option<HstsEntries> = serde_json::from_str(preload_content).ok(); - - hsts_entries.map(|hsts_entries| { - let mut hsts_list: HstsPreloadList = HstsPreloadList::default(); - - for hsts_entry in hsts_entries.entries { - hsts_list.push(hsts_entry); - } - - hsts_list - }) + pub fn from_preload(preload_content: Vec<u8>) -> Option<HstsPreloadList> { + Map::new(preload_content).map(HstsPreloadList).ok() } pub fn from_servo_preload() -> HstsPreloadList { debug!("Intializing HSTS Preload list"); - let list = resources::read_string(Resource::HstsPreloadList); - HstsPreloadList::from_preload(&list).unwrap_or_else(|| { + let map_bytes = resources::read_bytes(Resource::HstsPreloadList); + HstsPreloadList::from_preload(map_bytes).unwrap_or_else(|| { error!("HSTS preload file is invalid. Setting HSTS list to default values"); - HstsPreloadList { - entries_map: Default::default(), - } + HstsPreloadList(MapBuilder::memory().into_map()) }) } pub fn is_host_secure(&self, host: &str) -> bool { let base_domain = reg_suffix(host); - self.entries_map.get(base_domain).is_some_and(|entries| { - // No need to check for expiration in the preload list - entries.iter().any(|e| { - if e.include_subdomains { - e.matches_subdomain(host) || e.matches_domain(host) - } else { - e.matches_domain(host) - } - }) - }) - } + let parts = host[..host.len() - base_domain.len()].rsplit_terminator('.'); + let mut domain_to_test = base_domain.to_owned(); - pub fn has_domain(&self, host: &str, base_domain: &str) -> bool { - self.entries_map - .get(base_domain) - .is_some_and(|entries| entries.iter().any(|e| e.matches_domain(host))) - } - - pub fn has_subdomain(&self, host: &str, base_domain: &str) -> bool { - self.entries_map.get(base_domain).is_some_and(|entries| { - entries - .iter() - .any(|e| e.include_subdomains && e.matches_subdomain(host)) - }) - } - - pub fn push(&mut self, entry: HstsEntry) { - let host = entry.host.clone(); - let base_domain = reg_suffix(&host); - let have_domain = self.has_domain(&entry.host, base_domain); - let have_subdomain = self.has_subdomain(&entry.host, base_domain); + if self.0.get(&domain_to_test).is_some_and(|id| { + // The FST map ids were constructed such that the parity represents the includeSubdomain flag + id % 2 == 1 || domain_to_test == host + }) { + return true; + } - let entries = self.entries_map.entry(base_domain.to_owned()).or_default(); - if !have_domain && !have_subdomain { - entries.push(entry); - } else if !have_subdomain { - for e in entries { - if e.matches_domain(&entry.host) { - e.include_subdomains = entry.include_subdomains; - // TODO(sebsebmc): We could shrink the the HSTS preload memory use further by using a type - // that doesn't store an expiry since all preload entries should be "forever" - e.expires_at = entry.expires_at; - } + // Check all further subdomains up to the passed host + for part in parts { + domain_to_test = format!("{}.{}", part, domain_to_test); + if self.0.get(&domain_to_test).is_some_and(|id| { + // The FST map ids were constructed such that the parity represents the includeSubdomain flag + id % 2 == 1 || domain_to_test == host + }) { + return true; } } + false } } impl HstsList { pub fn is_host_secure(&self, host: &str) -> bool { - debug!("HSTS: is {host} secure?"); if PRELOAD_LIST_ENTRIES.is_host_secure(host) { info!("{host} is in the preload list"); return true; diff --git a/components/net/resource_thread.rs b/components/net/resource_thread.rs index 4077256d6ca..94592d19bed 100644 --- a/components/net/resource_thread.rs +++ b/components/net/resource_thread.rs @@ -21,9 +21,9 @@ use embedder_traits::EmbedderProxy; use hyper_serde::Serde; use ipc_channel::ipc::{self, IpcReceiver, IpcReceiverSet, IpcSender}; use log::{debug, trace, warn}; -use malloc_size_of::MallocSizeOf; use net_traits::blob_url_store::parse_blob_url; use net_traits::filemanager_thread::FileTokenCheck; +use net_traits::pub_domains::public_suffix_list_size_of; use net_traits::request::{Destination, RequestBuilder, RequestId}; use net_traits::response::{Response, ResponseInit}; use net_traits::storage_thread::StorageThreadMsg; @@ -97,14 +97,15 @@ pub fn new_resource_threads( let (public_core, private_core) = new_core_resource_thread( devtools_sender, time_profiler_chan, - mem_profiler_chan, + mem_profiler_chan.clone(), embedder_proxy, config_dir.clone(), ca_certificates, ignore_certificate_errors, protocols, ); - let storage: IpcSender<StorageThreadMsg> = StorageThreadFactory::new(config_dir); + let storage: IpcSender<StorageThreadMsg> = + StorageThreadFactory::new(config_dir, mem_profiler_chan); ( ResourceThreads::new(public_core, storage.clone()), ResourceThreads::new(private_core, storage), @@ -287,11 +288,18 @@ impl ResourceChannelManager { perform_memory_report(|ops| { let mut reports = public_http_state.memory_reports("public", ops); reports.extend(private_http_state.memory_reports("private", ops)); - reports.push(Report { - path: path!["hsts-preload-list"], - kind: ReportKind::ExplicitJemallocHeapSize, - size: hsts::PRELOAD_LIST_ENTRIES.size_of(ops), - }); + reports.extend(vec![ + Report { + path: path!["hsts-preload-list"], + kind: ReportKind::ExplicitJemallocHeapSize, + size: hsts::hsts_preload_size_of(ops), + }, + Report { + path: path!["public-suffix-list"], + kind: ReportKind::ExplicitJemallocHeapSize, + size: public_suffix_list_size_of(ops), + }, + ]); msg.send(ProcessReports::new(reports)); }) } diff --git a/components/net/storage_thread.rs b/components/net/storage_thread.rs index dd058f17170..899214a450c 100644 --- a/components/net/storage_thread.rs +++ b/components/net/storage_thread.rs @@ -8,7 +8,12 @@ use std::path::PathBuf; use std::thread; use ipc_channel::ipc::{self, IpcReceiver, IpcSender}; +use malloc_size_of::MallocSizeOf; use net_traits::storage_thread::{StorageThreadMsg, StorageType}; +use profile_traits::mem::{ + ProcessReports, ProfilerChan as MemProfilerChan, Report, ReportKind, perform_memory_report, +}; +use profile_traits::path; use servo_url::ServoUrl; use crate::resource_thread; @@ -16,17 +21,26 @@ use crate::resource_thread; const QUOTA_SIZE_LIMIT: usize = 5 * 1024 * 1024; pub trait StorageThreadFactory { - fn new(config_dir: Option<PathBuf>) -> Self; + fn new(config_dir: Option<PathBuf>, mem_profiler_chan: MemProfilerChan) -> Self; } impl StorageThreadFactory for IpcSender<StorageThreadMsg> { /// Create a storage thread - fn new(config_dir: Option<PathBuf>) -> IpcSender<StorageThreadMsg> { + fn new( + config_dir: Option<PathBuf>, + mem_profiler_chan: MemProfilerChan, + ) -> IpcSender<StorageThreadMsg> { let (chan, port) = ipc::channel().unwrap(); + let chan2 = chan.clone(); thread::Builder::new() .name("StorageManager".to_owned()) .spawn(move || { - StorageManager::new(port, config_dir).start(); + mem_profiler_chan.run_with_memory_reporting( + || StorageManager::new(port, config_dir).start(), + String::from("storage-reporter"), + chan2, + StorageThreadMsg::CollectMemoryReport, + ); }) .expect("Thread spawning failed"); chan @@ -83,6 +97,10 @@ impl StorageManager { self.clear(sender, url, storage_type); self.save_state() }, + StorageThreadMsg::CollectMemoryReport(sender) => { + let reports = self.collect_memory_reports(); + sender.send(ProcessReports::new(reports)); + }, StorageThreadMsg::Exit(sender) => { // Nothing to do since we save localstorage set eagerly. let _ = sender.send(()); @@ -92,6 +110,24 @@ impl StorageManager { } } + fn collect_memory_reports(&self) -> Vec<Report> { + let mut reports = vec![]; + perform_memory_report(|ops| { + reports.push(Report { + path: path!["storage", "local"], + kind: ReportKind::ExplicitJemallocHeapSize, + size: self.local_data.size_of(ops), + }); + + reports.push(Report { + path: path!["storage", "session"], + kind: ReportKind::ExplicitJemallocHeapSize, + size: self.session_data.size_of(ops), + }); + }); + reports + } + fn save_state(&self) { if let Some(ref config_dir) = self.config_dir { resource_thread::write_json_to_file(&self.local_data, config_dir, "local_data.json"); diff --git a/components/net/tests/hsts.rs b/components/net/tests/hsts.rs index e1e754beb3c..3598b232111 100644 --- a/components/net/tests/hsts.rs +++ b/components/net/tests/hsts.rs @@ -6,13 +6,14 @@ use std::collections::HashMap; use std::num::NonZeroU64; use std::time::Duration as StdDuration; +use base64::Engine; use net::hsts::{HstsEntry, HstsList, HstsPreloadList}; use net_traits::IncludeSubdomains; #[test] fn test_hsts_entry_is_not_expired_when_it_has_no_expires_at() { let entry = HstsEntry { - host: "mozilla.org".to_owned(), + host: "example.com".to_owned(), include_subdomains: false, expires_at: None, }; @@ -23,7 +24,7 @@ fn test_hsts_entry_is_not_expired_when_it_has_no_expires_at() { #[test] fn test_hsts_entry_is_expired_when_it_has_reached_its_max_age() { let entry = HstsEntry { - host: "mozilla.org".to_owned(), + host: "example.com".to_owned(), include_subdomains: false, expires_at: Some(NonZeroU64::new(1).unwrap()), }; @@ -59,7 +60,7 @@ fn test_base_domain_in_entries_map() { list.push( HstsEntry::new( - "servo.mozilla.org".to_owned(), + "servo.example.com".to_owned(), IncludeSubdomains::NotIncluded, None, ) @@ -67,7 +68,7 @@ fn test_base_domain_in_entries_map() { ); list.push( HstsEntry::new( - "firefox.mozilla.org".to_owned(), + "firefox.example.com".to_owned(), IncludeSubdomains::NotIncluded, None, ) @@ -75,7 +76,7 @@ fn test_base_domain_in_entries_map() { ); list.push( HstsEntry::new( - "bugzilla.org".to_owned(), + "example.org".to_owned(), IncludeSubdomains::NotIncluded, None, ) @@ -83,17 +84,17 @@ fn test_base_domain_in_entries_map() { ); assert_eq!(list.entries_map.len(), 2); - assert_eq!(list.entries_map.get("mozilla.org").unwrap().len(), 2); + assert_eq!(list.entries_map.get("example.com").unwrap().len(), 2); } #[test] fn test_push_entry_with_0_max_age_is_not_secure() { let mut entries_map = HashMap::new(); entries_map.insert( - "mozilla.org".to_owned(), + "example.com".to_owned(), vec![ HstsEntry::new( - "mozilla.org".to_owned(), + "example.com".to_owned(), IncludeSubdomains::NotIncluded, Some(StdDuration::from_secs(500000)), ) @@ -106,23 +107,23 @@ fn test_push_entry_with_0_max_age_is_not_secure() { list.push( HstsEntry::new( - "mozilla.org".to_owned(), + "example.com".to_owned(), IncludeSubdomains::NotIncluded, Some(StdDuration::ZERO), ) .unwrap(), ); - assert_eq!(list.is_host_secure("mozilla.org"), false) + assert_eq!(list.is_host_secure("example.com"), false) } fn test_push_entry_with_0_max_age_evicts_entry_from_list() { let mut entries_map = HashMap::new(); entries_map.insert( - "mozilla.org".to_owned(), + "example.com".to_owned(), vec![ HstsEntry::new( - "mozilla.org".to_owned(), + "example.com".to_owned(), IncludeSubdomains::NotIncluded, Some(StdDuration::from_secs(500000)), ) @@ -133,25 +134,25 @@ fn test_push_entry_with_0_max_age_evicts_entry_from_list() { entries_map: entries_map, }; - assert_eq!(list.entries_map.get("mozilla.org").unwrap().len(), 1); + assert_eq!(list.entries_map.get("example.com").unwrap().len(), 1); list.push( HstsEntry::new( - "mozilla.org".to_owned(), + "example.com".to_owned(), IncludeSubdomains::NotIncluded, Some(StdDuration::ZERO), ) .unwrap(), ); - assert_eq!(list.entries_map.get("mozilla.org").unwrap().len(), 0); + assert_eq!(list.entries_map.get("example.com").unwrap().len(), 0); } #[test] fn test_push_entry_to_hsts_list_should_not_add_subdomains_whose_superdomain_is_already_matched() { let mut entries_map = HashMap::new(); entries_map.insert( - "mozilla.org".to_owned(), - vec![HstsEntry::new("mozilla.org".to_owned(), IncludeSubdomains::Included, None).unwrap()], + "example.com".to_owned(), + vec![HstsEntry::new("example.com".to_owned(), IncludeSubdomains::Included, None).unwrap()], ); let mut list = HstsList { entries_map: entries_map, @@ -159,24 +160,24 @@ fn test_push_entry_to_hsts_list_should_not_add_subdomains_whose_superdomain_is_a list.push( HstsEntry::new( - "servo.mozilla.org".to_owned(), + "servo.example.com".to_owned(), IncludeSubdomains::NotIncluded, None, ) .unwrap(), ); - assert_eq!(list.entries_map.get("mozilla.org").unwrap().len(), 1) + assert_eq!(list.entries_map.get("example.com").unwrap().len(), 1) } #[test] fn test_push_entry_to_hsts_list_should_add_subdomains_whose_superdomain_doesnt_include() { let mut entries_map = HashMap::new(); entries_map.insert( - "mozilla.org".to_owned(), + "example.com".to_owned(), vec![ HstsEntry::new( - "mozilla.org".to_owned(), + "example.com".to_owned(), IncludeSubdomains::NotIncluded, None, ) @@ -189,49 +190,49 @@ fn test_push_entry_to_hsts_list_should_add_subdomains_whose_superdomain_doesnt_i list.push( HstsEntry::new( - "servo.mozilla.org".to_owned(), + "servo.example.com".to_owned(), IncludeSubdomains::NotIncluded, None, ) .unwrap(), ); - assert_eq!(list.entries_map.get("mozilla.org").unwrap().len(), 2) + assert_eq!(list.entries_map.get("example.com").unwrap().len(), 2) } #[test] fn test_push_entry_to_hsts_list_should_update_existing_domain_entrys_include_subdomains() { let mut entries_map = HashMap::new(); entries_map.insert( - "mozilla.org".to_owned(), - vec![HstsEntry::new("mozilla.org".to_owned(), IncludeSubdomains::Included, None).unwrap()], + "example.com".to_owned(), + vec![HstsEntry::new("example.com".to_owned(), IncludeSubdomains::Included, None).unwrap()], ); let mut list = HstsList { entries_map: entries_map, }; - assert!(list.is_host_secure("servo.mozilla.org")); + assert!(list.is_host_secure("servo.example.com")); list.push( HstsEntry::new( - "mozilla.org".to_owned(), + "example.com".to_owned(), IncludeSubdomains::NotIncluded, None, ) .unwrap(), ); - assert!(!list.is_host_secure("servo.mozilla.org")) + assert!(!list.is_host_secure("servo.example.com")) } #[test] fn test_push_entry_to_hsts_list_should_not_create_duplicate_entry() { let mut entries_map = HashMap::new(); entries_map.insert( - "mozilla.org".to_owned(), + "example.com".to_owned(), vec![ HstsEntry::new( - "mozilla.org".to_owned(), + "example.com".to_owned(), IncludeSubdomains::NotIncluded, None, ) @@ -244,14 +245,14 @@ fn test_push_entry_to_hsts_list_should_not_create_duplicate_entry() { list.push( HstsEntry::new( - "mozilla.org".to_owned(), + "example.com".to_owned(), IncludeSubdomains::NotIncluded, None, ) .unwrap(), ); - assert_eq!(list.entries_map.get("mozilla.org").unwrap().len(), 1) + assert_eq!(list.entries_map.get("example.com").unwrap().len(), 1) } #[test] @@ -260,16 +261,14 @@ fn test_push_multiple_entrie_to_hsts_list_should_add_them_all() { entries_map: HashMap::new(), }; - assert!(!list.is_host_secure("mozilla.org")); - assert!(!list.is_host_secure("bugzilla.org")); + assert!(!list.is_host_secure("example.com")); + assert!(!list.is_host_secure("example.org")); - list.push(HstsEntry::new("mozilla.org".to_owned(), IncludeSubdomains::Included, None).unwrap()); - list.push( - HstsEntry::new("bugzilla.org".to_owned(), IncludeSubdomains::Included, None).unwrap(), - ); + list.push(HstsEntry::new("example.com".to_owned(), IncludeSubdomains::Included, None).unwrap()); + list.push(HstsEntry::new("example.org".to_owned(), IncludeSubdomains::Included, None).unwrap()); - assert!(list.is_host_secure("mozilla.org")); - assert!(list.is_host_secure("bugzilla.org")); + assert!(list.is_host_secure("example.com")); + assert!(list.is_host_secure("example.org")); } #[test] @@ -278,25 +277,16 @@ fn test_push_entry_to_hsts_list_should_add_an_entry() { entries_map: HashMap::new(), }; - assert!(!list.is_host_secure("mozilla.org")); + assert!(!list.is_host_secure("example.com")); - list.push(HstsEntry::new("mozilla.org".to_owned(), IncludeSubdomains::Included, None).unwrap()); + list.push(HstsEntry::new("example.com".to_owned(), IncludeSubdomains::Included, None).unwrap()); - assert!(list.is_host_secure("mozilla.org")); + assert!(list.is_host_secure("example.com")); } #[test] fn test_parse_hsts_preload_should_return_none_when_json_invalid() { - let mock_preload_content = "derp"; - assert!( - HstsPreloadList::from_preload(mock_preload_content).is_none(), - "invalid preload list should not have parsed" - ) -} - -#[test] -fn test_parse_hsts_preload_should_return_none_when_json_contains_no_entries_map_key() { - let mock_preload_content = "{\"nothing\": \"to see here\"}"; + let mock_preload_content = "derp".as_bytes().to_vec(); assert!( HstsPreloadList::from_preload(mock_preload_content).is_none(), "invalid preload list should not have parsed" @@ -305,20 +295,17 @@ fn test_parse_hsts_preload_should_return_none_when_json_contains_no_entries_map_ #[test] fn test_parse_hsts_preload_should_decode_host_and_includes_subdomains() { - let mock_preload_content = "{\ - \"entries\": [\ - {\"host\": \"mozilla.org\",\ - \"include_subdomains\": false}\ - ]\ - }"; - let hsts_list = HstsPreloadList::from_preload(mock_preload_content); - let entries_map = hsts_list.unwrap().entries_map; - - assert_eq!( - entries_map.get("mozilla.org").unwrap()[0].host, - "mozilla.org" - ); - assert!(!entries_map.get("mozilla.org").unwrap()[0].include_subdomains); + // Generated with `fst map --sorted` on a csv of "example.com,0\nexample.org,3" + let mock_preload_content = base64::engine::general_purpose::STANDARD + .decode("AwAAAAAAAAAAAAAAAAAAAAAQkMQAEJfHAwABBW9jEQLNws/J0MXqwgIAAAAAAAAAJwAAAAAAAADVOFe6") + .unwrap(); + let hsts_list = HstsPreloadList::from_preload(mock_preload_content).unwrap(); + + assert_eq!(hsts_list.is_host_secure("derp"), false); + assert_eq!(hsts_list.is_host_secure("example.com"), true); + assert_eq!(hsts_list.is_host_secure("servo.example.com"), false); + assert_eq!(hsts_list.is_host_secure("example.org"), true); + assert_eq!(hsts_list.is_host_secure("servo.example.org"), true); } #[test] @@ -327,17 +314,17 @@ fn test_hsts_list_with_no_entries_map_does_not_is_host_secure() { entries_map: HashMap::new(), }; - assert!(!hsts_list.is_host_secure("mozilla.org")); + assert!(!hsts_list.is_host_secure("example.com")); } #[test] fn test_hsts_list_with_exact_domain_entry_is_is_host_secure() { let mut entries_map = HashMap::new(); entries_map.insert( - "mozilla.org".to_owned(), + "example.com".to_owned(), vec![ HstsEntry::new( - "mozilla.org".to_owned(), + "example.com".to_owned(), IncludeSubdomains::NotIncluded, None, ) @@ -349,31 +336,31 @@ fn test_hsts_list_with_exact_domain_entry_is_is_host_secure() { entries_map: entries_map, }; - assert!(hsts_list.is_host_secure("mozilla.org")); + assert!(hsts_list.is_host_secure("example.com")); } #[test] fn test_hsts_list_with_subdomain_when_include_subdomains_is_true_is_is_host_secure() { let mut entries_map = HashMap::new(); entries_map.insert( - "mozilla.org".to_owned(), - vec![HstsEntry::new("mozilla.org".to_owned(), IncludeSubdomains::Included, None).unwrap()], + "example.com".to_owned(), + vec![HstsEntry::new("example.com".to_owned(), IncludeSubdomains::Included, None).unwrap()], ); let hsts_list = HstsList { entries_map: entries_map, }; - assert!(hsts_list.is_host_secure("servo.mozilla.org")); + assert!(hsts_list.is_host_secure("servo.example.com")); } #[test] fn test_hsts_list_with_subdomain_when_include_subdomains_is_false_is_not_is_host_secure() { let mut entries_map = HashMap::new(); entries_map.insert( - "mozilla.org".to_owned(), + "example.com".to_owned(), vec![ HstsEntry::new( - "mozilla.org".to_owned(), + "example.com".to_owned(), IncludeSubdomains::NotIncluded, None, ) @@ -384,44 +371,44 @@ fn test_hsts_list_with_subdomain_when_include_subdomains_is_false_is_not_is_host entries_map: entries_map, }; - assert!(!hsts_list.is_host_secure("servo.mozilla.org")); + assert!(!hsts_list.is_host_secure("servo.example.com")); } #[test] fn test_hsts_list_with_subdomain_when_host_is_not_a_subdomain_is_not_is_host_secure() { let mut entries_map = HashMap::new(); entries_map.insert( - "mozilla.org".to_owned(), - vec![HstsEntry::new("mozilla.org".to_owned(), IncludeSubdomains::Included, None).unwrap()], + "example.com".to_owned(), + vec![HstsEntry::new("example.com".to_owned(), IncludeSubdomains::Included, None).unwrap()], ); let hsts_list = HstsList { entries_map: entries_map, }; - assert!(!hsts_list.is_host_secure("servo-mozilla.org")); + assert!(!hsts_list.is_host_secure("servo-example.com")); } #[test] fn test_hsts_list_with_subdomain_when_host_is_exact_match_is_is_host_secure() { let mut entries_map = HashMap::new(); entries_map.insert( - "mozilla.org".to_owned(), - vec![HstsEntry::new("mozilla.org".to_owned(), IncludeSubdomains::Included, None).unwrap()], + "example.com".to_owned(), + vec![HstsEntry::new("example.com".to_owned(), IncludeSubdomains::Included, None).unwrap()], ); let hsts_list = HstsList { entries_map: entries_map, }; - assert!(hsts_list.is_host_secure("mozilla.org")); + assert!(hsts_list.is_host_secure("example.com")); } #[test] fn test_hsts_list_with_expired_entry_is_not_is_host_secure() { let mut entries_map = HashMap::new(); entries_map.insert( - "mozilla.org".to_owned(), + "example.com".to_owned(), vec![HstsEntry { - host: "mozilla.org".to_owned(), + host: "example.com".to_owned(), include_subdomains: false, expires_at: Some(NonZeroU64::new(1).unwrap()), }], @@ -430,11 +417,11 @@ fn test_hsts_list_with_expired_entry_is_not_is_host_secure() { entries_map: entries_map, }; - assert!(!hsts_list.is_host_secure("mozilla.org")); + assert!(!hsts_list.is_host_secure("example.com")); } #[test] fn test_preload_hsts_domains_well_formed() { let hsts_list = HstsPreloadList::from_servo_preload(); - assert!(!hsts_list.entries_map.is_empty()); + assert_ne!(hsts_list.0.len(), 0); } diff --git a/components/script/dom/bindings/structuredclone.rs b/components/script/dom/bindings/structuredclone.rs index 70638238123..c23156817cb 100644 --- a/components/script/dom/bindings/structuredclone.rs +++ b/components/script/dom/bindings/structuredclone.rs @@ -14,7 +14,7 @@ use base::id::{ }; use constellation_traits::{ BlobImpl, DomException, DomPoint, MessagePortImpl, Serializable as SerializableInterface, - StructuredSerializedData, Transferrable as TransferrableInterface, + StructuredSerializedData, Transferrable as TransferrableInterface, TransformStreamData, }; use js::gc::RootedVec; use js::glue::{ @@ -517,6 +517,8 @@ pub(crate) struct StructuredDataReader<'a> { /// used as part of the "transfer-receiving" steps of ports, /// to produce the DOM ports stored in `message_ports` above. pub(crate) port_impls: Option<HashMap<MessagePortId, MessagePortImpl>>, + /// A map of transform stream implementations, + pub(crate) transform_streams_port_impls: Option<HashMap<MessagePortId, TransformStreamData>>, /// A map of blob implementations, /// used as part of the "deserialize" steps of blobs, /// to produce the DOM blobs stored in `blobs` above. @@ -535,6 +537,8 @@ pub(crate) struct StructuredDataWriter { pub(crate) errors: DOMErrorRecord, /// Transferred ports. pub(crate) ports: Option<HashMap<MessagePortId, MessagePortImpl>>, + /// Transferred transform streams. + pub(crate) transform_streams_port: Option<HashMap<MessagePortId, TransformStreamData>>, /// Serialized points. pub(crate) points: Option<HashMap<DomPointId, DomPoint>>, /// Serialized exceptions. @@ -591,6 +595,7 @@ pub(crate) fn write( let data = StructuredSerializedData { serialized: data, ports: sc_writer.ports.take(), + transform_streams: sc_writer.transform_streams_port.take(), points: sc_writer.points.take(), exceptions: sc_writer.exceptions.take(), blobs: sc_writer.blobs.take(), @@ -613,6 +618,7 @@ pub(crate) fn read( let mut sc_reader = StructuredDataReader { roots, port_impls: data.ports.take(), + transform_streams_port_impls: data.transform_streams.take(), blob_impls: data.blobs.take(), points: data.points.take(), exceptions: data.exceptions.take(), diff --git a/components/script/dom/blob.rs b/components/script/dom/blob.rs index c5c5c480707..18e968aaa70 100644 --- a/components/script/dom/blob.rs +++ b/components/script/dom/blob.rs @@ -12,11 +12,10 @@ use dom_struct::dom_struct; use encoding_rs::UTF_8; use js::jsapi::JSObject; use js::rust::HandleObject; -use js::typedarray::Uint8; +use js::typedarray::{ArrayBufferU8, Uint8}; use net_traits::filemanager_thread::RelativePos; use uuid::Uuid; -use crate::body::{FetchedData, run_array_buffer_data_algorithm}; use crate::dom::bindings::buffer_source::create_buffer_source; use crate::dom::bindings::codegen::Bindings::BlobBinding; use crate::dom::bindings::codegen::Bindings::BlobBinding::BlobMethods; @@ -226,7 +225,7 @@ impl BlobMethods<crate::DomTypeHolder> for Blob { Blob::new(&global, blob_impl, can_gc) } - // https://w3c.github.io/FileAPI/#text-method-algo + /// <https://w3c.github.io/FileAPI/#text-method-algo> fn Text(&self, can_gc: CanGc) -> Rc<Promise> { let global = self.global(); let in_realm_proof = AlreadyInRealm::assert::<crate::DomTypeHolder>(); @@ -250,35 +249,51 @@ impl BlobMethods<crate::DomTypeHolder> for Blob { } // https://w3c.github.io/FileAPI/#arraybuffer-method-algo - fn ArrayBuffer(&self, can_gc: CanGc) -> Rc<Promise> { - let global = self.global(); - let in_realm_proof = AlreadyInRealm::assert::<crate::DomTypeHolder>(); - let p = Promise::new_in_current_realm(InRealm::Already(&in_realm_proof), can_gc); + fn ArrayBuffer(&self, in_realm: InRealm, can_gc: CanGc) -> Rc<Promise> { + let cx = GlobalScope::get_cx(); + let global = GlobalScope::from_safe_context(cx, in_realm); + let promise = Promise::new_in_current_realm(in_realm, can_gc); - let id = self.get_blob_url_id(); + // 1. Let stream be the result of calling get stream on this. + let stream = self.get_stream(can_gc); - global.read_file_async( - id, - p.clone(), - Box::new(|promise, bytes| { - match bytes { - Ok(b) => { - let cx = GlobalScope::get_cx(); - let result = run_array_buffer_data_algorithm(cx, b, CanGc::note()); - - match result { - Ok(FetchedData::ArrayBuffer(a)) => { - promise.resolve_native(&a, CanGc::note()) - }, - Err(e) => promise.reject_error(e, CanGc::note()), - _ => panic!("Unexpected result from run_array_buffer_data_algorithm"), - } - }, - Err(e) => promise.reject_error(e, CanGc::note()), - }; + // 2. Let reader be the result of getting a reader from stream. + // If that threw an exception, return a new promise rejected with that exception. + let reader = match stream.and_then(|s| s.acquire_default_reader(can_gc)) { + Ok(reader) => reader, + Err(error) => { + promise.reject_error(error, can_gc); + return promise; + }, + }; + + // 3. Let promise be the result of reading all bytes from stream with reader. + let success_promise = promise.clone(); + let failure_promise = promise.clone(); + reader.read_all_bytes( + cx, + &global, + Rc::new(move |bytes| { + rooted!(in(*cx) let mut js_object = ptr::null_mut::<JSObject>()); + // 4. Return the result of transforming promise by a fulfillment handler that returns a new + // [ArrayBuffer] + let array_buffer = create_buffer_source::<ArrayBufferU8>( + cx, + bytes, + js_object.handle_mut(), + can_gc, + ) + .expect("Converting input to ArrayBufferU8 should never fail"); + success_promise.resolve_native(&array_buffer, can_gc); }), + Rc::new(move |cx, value| { + failure_promise.reject(cx, value, can_gc); + }), + in_realm, + can_gc, ); - p + + promise } /// <https://w3c.github.io/FileAPI/#dom-blob-bytes> diff --git a/components/script/dom/headers.rs b/components/script/dom/headers.rs index 10a8be731bf..0e8dcf92ccd 100644 --- a/components/script/dom/headers.rs +++ b/components/script/dom/headers.rs @@ -5,12 +5,12 @@ use std::cell::Cell; use std::str::{self, FromStr}; -use data_url::mime::Mime as DataUrlMime; use dom_struct::dom_struct; use http::header::{HeaderMap as HyperHeaders, HeaderName, HeaderValue}; use js::rust::HandleObject; use net_traits::fetch::headers::{ - get_decode_and_split_header_value, get_value_from_header_list, is_forbidden_method, + extract_mime_type, get_decode_and_split_header_value, get_value_from_header_list, + is_forbidden_method, }; use net_traits::request::is_cors_safelisted_request_header; @@ -564,72 +564,3 @@ pub(crate) fn is_vchar(x: u8) -> bool { pub(crate) fn is_obs_text(x: u8) -> bool { matches!(x, 0x80..=0xFF) } - -// https://fetch.spec.whatwg.org/#concept-header-extract-mime-type -// This function uses data_url::Mime to parse the MIME Type because -// mime::Mime does not provide a parser following the Fetch spec -// see https://github.com/hyperium/mime/issues/106 -pub(crate) fn extract_mime_type(headers: &HyperHeaders) -> Option<Vec<u8>> { - let mut charset: Option<String> = None; - let mut essence: String = "".to_string(); - let mut mime_type: Option<DataUrlMime> = None; - - // Step 4 - let headers_values = headers.get_all(http::header::CONTENT_TYPE).iter(); - - // Step 5 - if headers_values.size_hint() == (0, Some(0)) { - return None; - } - - // Step 6 - for header_value in headers_values { - // Step 6.1 - match DataUrlMime::from_str(header_value.to_str().unwrap_or("")) { - // Step 6.2 - Err(_) => continue, - Ok(temp_mime) => { - let temp_essence = format!("{}/{}", temp_mime.type_, temp_mime.subtype); - - // Step 6.2 - if temp_essence == "*/*" { - continue; - } - - let temp_charset = &temp_mime.get_parameter("charset"); - - // Step 6.3 - mime_type = Some(DataUrlMime { - type_: temp_mime.type_.to_string(), - subtype: temp_mime.subtype.to_string(), - parameters: temp_mime.parameters.clone(), - }); - - // Step 6.4 - if temp_essence != essence { - charset = temp_charset.map(|c| c.to_string()); - temp_essence.clone_into(&mut essence); - } else { - // Step 6.5 - if temp_charset.is_none() && charset.is_some() { - let DataUrlMime { - type_: t, - subtype: st, - parameters: p, - } = mime_type.unwrap(); - let mut params = p; - params.push(("charset".to_string(), charset.clone().unwrap())); - mime_type = Some(DataUrlMime { - type_: t.to_string(), - subtype: st.to_string(), - parameters: params, - }) - } - } - }, - } - } - - // Step 7, 8 - mime_type.map(|m| format!("{}", m).into_bytes()) -} diff --git a/components/script/dom/readablestream.rs b/components/script/dom/readablestream.rs index d631a01e1e7..d2c1d853f86 100644 --- a/components/script/dom/readablestream.rs +++ b/components/script/dom/readablestream.rs @@ -24,7 +24,7 @@ use js::typedarray::ArrayBufferViewU8; use crate::dom::bindings::codegen::Bindings::QueuingStrategyBinding::QueuingStrategy; use crate::dom::bindings::codegen::Bindings::ReadableStreamBinding::{ ReadableStreamGetReaderOptions, ReadableStreamMethods, ReadableStreamReaderMode, - StreamPipeOptions, + ReadableWritablePair, StreamPipeOptions, }; use script_bindings::str::DOMString; @@ -2006,6 +2006,50 @@ impl ReadableStreamMethods<crate::DomTypeHolder> for ReadableStream { can_gc, ) } + + /// <https://streams.spec.whatwg.org/#rs-pipe-through> + fn PipeThrough( + &self, + transform: &ReadableWritablePair, + options: &StreamPipeOptions, + realm: InRealm, + can_gc: CanGc, + ) -> Fallible<DomRoot<ReadableStream>> { + let global = self.global(); + let cx = GlobalScope::get_cx(); + + // If ! IsReadableStreamLocked(this) is true, throw a TypeError exception. + if self.is_locked() { + return Err(Error::Type("Source stream is locked".to_owned())); + } + + // If ! IsWritableStreamLocked(transform["writable"]) is true, throw a TypeError exception. + if transform.writable.is_locked() { + return Err(Error::Type("Destination stream is locked".to_owned())); + } + + // Let signal be options["signal"] if it exists, or undefined otherwise. + // TODO: implement AbortSignal. + + // Let promise be ! ReadableStreamPipeTo(this, transform["writable"], + // options["preventClose"], options["preventAbort"], options["preventCancel"], signal). + let promise = self.pipe_to( + cx, + &global, + &transform.writable, + options.preventAbort, + options.preventCancel, + options.preventClose, + realm, + can_gc, + ); + + // Set promise.[[PromiseIsHandled]] to true. + promise.set_promise_is_handled(); + + // Return transform["readable"]. + Ok(transform.readable.clone()) + } } #[allow(unsafe_code)] diff --git a/components/script/dom/transformstream.rs b/components/script/dom/transformstream.rs index 0251498980d..446bf71f172 100644 --- a/components/script/dom/transformstream.rs +++ b/components/script/dom/transformstream.rs @@ -8,7 +8,7 @@ use std::ptr::{self}; use std::rc::Rc; use base::id::{MessagePortId, MessagePortIndex}; -use constellation_traits::MessagePortImpl; +use constellation_traits::TransformStreamData; use dom_struct::dom_struct; use js::jsapi::{Heap, IsPromiseObject, JSObject}; use js::jsval::{JSVal, ObjectValue, UndefinedValue}; @@ -1007,9 +1007,9 @@ impl TransformStreamMethods<crate::DomTypeHolder> for TransformStream { /// <https://streams.spec.whatwg.org/#ts-transfer> impl Transferable for TransformStream { type Index = MessagePortIndex; - type Data = MessagePortImpl; + type Data = TransformStreamData; - fn transfer(&self) -> Result<(MessagePortId, MessagePortImpl), ()> { + fn transfer(&self) -> Result<(MessagePortId, TransformStreamData), ()> { let global = self.global(); let realm = enter_realm(&*global); let comp = InRealm::Entered(&realm); @@ -1023,73 +1023,85 @@ impl Transferable for TransformStream { let writable = self.get_writable(); // If ! IsReadableStreamLocked(readable) is true, throw a "DataCloneError" DOMException. - if readable.is_locked() { - return Err(()); - } - // If ! IsWritableStreamLocked(writable) is true, throw a "DataCloneError" DOMException. - if writable.is_locked() { + if readable.is_locked() || writable.is_locked() { return Err(()); } - // Create the shared port pair - let port_1 = MessagePort::new(&global, can_gc); - global.track_message_port(&port_1, None); - let port_2 = MessagePort::new(&global, can_gc); - global.track_message_port(&port_2, None); - global.entangle_ports(*port_1.message_port_id(), *port_2.message_port_id()); + // First port pair (readable → proxy writable) + let port1 = MessagePort::new(&global, can_gc); + global.track_message_port(&port1, None); + let port1_peer = MessagePort::new(&global, can_gc); + global.track_message_port(&port1_peer, None); + global.entangle_ports(*port1.message_port_id(), *port1_peer.message_port_id()); + + let proxy_readable = ReadableStream::new_with_proto(&global, None, can_gc); + proxy_readable.setup_cross_realm_transform_readable(cx, &port1, can_gc); + proxy_readable + .pipe_to(cx, &global, &writable, false, false, false, comp, can_gc) + .set_promise_is_handled(); + + // Second port pair (proxy readable → writable) + let port2 = MessagePort::new(&global, can_gc); + global.track_message_port(&port2, None); + let port2_peer = MessagePort::new(&global, can_gc); + global.track_message_port(&port2_peer, None); + global.entangle_ports(*port2.message_port_id(), *port2_peer.message_port_id()); - // Create a proxy WritableStream wired to port_1 let proxy_writable = WritableStream::new_with_proto(&global, None, can_gc); - proxy_writable.setup_cross_realm_transform_writable(cx, &port_1, can_gc); + proxy_writable.setup_cross_realm_transform_writable(cx, &port2, can_gc); // Pipe readable into the proxy writable (→ port_1) - let pipe1 = readable.pipe_to( - cx, - &global, - &proxy_writable, - false, - false, - false, - comp, - can_gc, - ); - pipe1.set_promise_is_handled(); - - // Create a proxy ReadableStream wired to port_1 - let proxy_readable = ReadableStream::new_with_proto(&global, None, can_gc); - proxy_readable.setup_cross_realm_transform_readable(cx, &port_1, can_gc); - - // Pipe proxy readable (← port_1) into writable - let pipe2 = - proxy_readable.pipe_to(cx, &global, &writable, false, false, false, comp, can_gc); - pipe2.set_promise_is_handled(); + readable + .pipe_to( + cx, + &global, + &proxy_writable, + false, + false, + false, + comp, + can_gc, + ) + .set_promise_is_handled(); // Set dataHolder.[[readable]] to ! StructuredSerializeWithTransfer(readable, « readable »). // Set dataHolder.[[writable]] to ! StructuredSerializeWithTransfer(writable, « writable »). - port_2.transfer() + Ok(( + *port1_peer.message_port_id(), + TransformStreamData { + readable: port1_peer.transfer()?, + writable: port2_peer.transfer()?, + }, + )) } fn transfer_receive( owner: &GlobalScope, - id: MessagePortId, - port_impl: MessagePortImpl, + _id: MessagePortId, + data: TransformStreamData, ) -> Result<DomRoot<Self>, ()> { let can_gc = CanGc::note(); + let cx = GlobalScope::get_cx(); + + let port1 = MessagePort::transfer_receive(owner, data.readable.0, data.readable.1)?; + let port2 = MessagePort::transfer_receive(owner, data.writable.0, data.writable.1)?; // Let readableRecord be ! StructuredDeserializeWithTransfer(dataHolder.[[readable]], the current Realm). // Set value.[[readable]] to readableRecord.[[Deserialized]]. - let readable = ReadableStream::transfer_receive(owner, id, port_impl.clone())?; - // Let writableRecord be ! StructuredDeserializeWithTransfer(dataHolder.[[writable]], the current Realm). - let writable = WritableStream::transfer_receive(owner, id, port_impl)?; + let proxy_readable = ReadableStream::new_with_proto(owner, None, can_gc); + proxy_readable.setup_cross_realm_transform_readable(cx, &port2, can_gc); + + let proxy_writable = WritableStream::new_with_proto(owner, None, can_gc); + proxy_writable.setup_cross_realm_transform_writable(cx, &port1, can_gc); // Set value.[[readable]] to readableRecord.[[Deserialized]]. // Set value.[[writable]] to writableRecord.[[Deserialized]]. // Set value.[[backpressure]], value.[[backpressureChangePromise]], and value.[[controller]] to undefined. let stream = TransformStream::new_with_proto(owner, None, can_gc); - stream.readable.set(Some(&readable)); - stream.writable.set(Some(&writable)); + stream.readable.set(Some(&proxy_readable)); + stream.writable.set(Some(&proxy_writable)); Ok(stream) } @@ -1098,8 +1110,8 @@ impl Transferable for TransformStream { data: StructuredData<'a, '_>, ) -> &'a mut Option<HashMap<MessagePortId, Self::Data>> { match data { - StructuredData::Reader(r) => &mut r.port_impls, - StructuredData::Writer(w) => &mut w.ports, + StructuredData::Reader(r) => &mut r.transform_streams_port_impls, + StructuredData::Writer(w) => &mut w.transform_streams_port, } } } diff --git a/components/script/dom/xmlhttprequest.rs b/components/script/dom/xmlhttprequest.rs index ca5bb72a1dc..4e7c136f42b 100644 --- a/components/script/dom/xmlhttprequest.rs +++ b/components/script/dom/xmlhttprequest.rs @@ -26,6 +26,7 @@ use js::jsval::{JSVal, NullValue}; use js::rust::wrappers::JS_ParseJSON; use js::rust::{HandleObject, MutableHandleValue}; use js::typedarray::{ArrayBuffer, ArrayBufferU8}; +use net_traits::fetch::headers::extract_mime_type_as_dataurl_mime; use net_traits::http_status::HttpStatus; use net_traits::request::{CredentialsMode, Referrer, RequestBuilder, RequestId, RequestMode}; use net_traits::{ @@ -59,7 +60,7 @@ use crate::dom::document::{Document, DocumentSource, HasBrowsingContext, IsHTMLD use crate::dom::event::{Event, EventBubbles, EventCancelable}; use crate::dom::eventtarget::EventTarget; use crate::dom::globalscope::GlobalScope; -use crate::dom::headers::{extract_mime_type, is_forbidden_request_header}; +use crate::dom::headers::is_forbidden_request_header; use crate::dom::node::Node; use crate::dom::performanceresourcetiming::InitiatorType; use crate::dom::progressevent::ProgressEvent; @@ -1324,11 +1325,7 @@ impl XMLHttpRequest { return response; } // Step 2 - let mime = self - .final_mime_type() - .as_ref() - .map(|m| normalize_type_string(&m.to_string())) - .unwrap_or("".to_owned()); + let mime = normalize_type_string(&self.final_mime_type().to_string()); // Step 3, 4 let bytes = self.response.borrow().to_vec(); @@ -1366,64 +1363,77 @@ impl XMLHttpRequest { return response; } - // Step 1 + // Step 1: If xhr’s response’s body is null, then return. if self.response_status.get().is_err() { return None; } - // Step 2 - let mime_type = self.final_mime_type(); - // Step 5.3, 7 - let charset = self.final_charset().unwrap_or(UTF_8); - let temp_doc: DomRoot<Document>; - match mime_type { - Some(ref mime) if mime.matches(TEXT, HTML) => { - // Step 4 - if self.response_type.get() == XMLHttpRequestResponseType::_empty { - return None; - } else { - // TODO Step 5.2 "If charset is null, prescan the first 1024 bytes of xhr’s received bytes" - // Step 5 - temp_doc = self.document_text_html(can_gc); - } - }, - // Step 7 - None => { - temp_doc = self.handle_xml(can_gc); - // Not sure it the parser should throw an error for this case - // The specification does not indicates this test, - // but for now we check the document has no child nodes - let has_no_child_nodes = temp_doc.upcast::<Node>().children().next().is_none(); - if has_no_child_nodes { - return None; - } - }, - Some(ref mime) - if mime.matches(TEXT, XML) || - mime.matches(APPLICATION, XML) || - mime.has_suffix(XML) => - { - temp_doc = self.handle_xml(can_gc); - // Not sure it the parser should throw an error for this case - // The specification does not indicates this test, - // but for now we check the document has no child nodes - let has_no_child_nodes = temp_doc.upcast::<Node>().children().next().is_none(); - if has_no_child_nodes { - return None; - } - }, - // Step 3 - _ => { + // Step 2: Let finalMIME be the result of get a final MIME type for xhr. + let final_mime = self.final_mime_type(); + + // Step 3: If finalMIME is not an HTML MIME type or an XML MIME type, then return. + let is_xml_mime_type = final_mime.matches(TEXT, XML) || + final_mime.matches(APPLICATION, XML) || + final_mime.has_suffix(XML); + if !final_mime.matches(TEXT, HTML) && !is_xml_mime_type { + return None; + } + + // Step 4: If xhr’s response type is the empty string and finalMIME is an HTML MIME + // type, then return. + let charset; + let temp_doc; + if final_mime.matches(TEXT, HTML) { + if self.response_type.get() == XMLHttpRequestResponseType::_empty { return None; - }, + } + + // Step 5: If finalMIME is an HTML MIME type, then: + // Step 5.1: Let charset be the result of get a final encoding for xhr. + // Step 5.2: If charset is null, prescan the first 1024 bytes of xhr’s received bytes + // and if that does not terminate unsuccessfully then let charset be the return value. + // TODO: This isn't happening right now. + // Step 5.3. If charset is null, then set charset to UTF-8. + charset = Some(self.final_charset().unwrap_or(UTF_8)); + + // Step 5.4: Let document be a document that represents the result parsing xhr’s + // received bytes following the rules set forth in the HTML Standard for an HTML parser + // with scripting disabled and a known definite encoding charset. [HTML] + temp_doc = self.document_text_html(can_gc); + } else { + assert!(is_xml_mime_type); + + // Step 6: Otherwise, let document be a document that represents the result of running + // the XML parser with XML scripting support disabled on xhr’s received bytes. If that + // fails (unsupported character encoding, namespace well-formedness error, etc.), then + // return null. [HTML] + // + // TODO: The spec seems to suggest the charset should come from the XML parser here. + temp_doc = self.handle_xml(can_gc); + charset = self.final_charset(); + + // Not sure it the parser should throw an error for this case + // The specification does not indicates this test, + // but for now we check the document has no child nodes + let has_no_child_nodes = temp_doc.upcast::<Node>().children().next().is_none(); + if has_no_child_nodes { + return None; + } } - // Step 8 + + // Step 7: If charset is null, then set charset to UTF-8. + let charset = charset.unwrap_or(UTF_8); + + // Step 8: Set document’s encoding to charset. temp_doc.set_encoding(charset); - // Step 9 to 11 - // Done by handle_text_html and handle_xml + // Step 9: Set document’s content type to finalMIME. + // Step 10: Set document’s URL to xhr’s response’s URL. + // Step 11: Set document’s origin to xhr’s relevant settings object’s origin. + // + // Done by `handle_text_html()` and `handle_xml()`. - // Step 12 + // Step 12: Set xhr’s response object to document. self.response_xml.set(Some(&temp_doc)); self.response_xml.get() } @@ -1507,7 +1517,7 @@ impl XMLHttpRequest { Ok(parsed) => Some(parsed), Err(_) => None, // Step 7 }; - let content_type = self.final_mime_type(); + let content_type = Some(self.final_mime_type()); Document::new( win, HasBrowsingContext::No, @@ -1598,14 +1608,16 @@ impl XMLHttpRequest { // 3. If responseMIME’s parameters["charset"] exists, then set label to it. let response_charset = self .response_mime_type() - .and_then(|mime| mime.get_parameter(CHARSET).map(|c| c.to_string())); + .get_parameter(CHARSET) + .map(ToString::to_string); // 4. If xhr’s override MIME type’s parameters["charset"] exists, then set label to it. let override_charset = self .override_mime_type .borrow() .as_ref() - .and_then(|mime| mime.get_parameter(CHARSET).map(|c| c.to_string())); + .and_then(|mime| mime.get_parameter(CHARSET)) + .map(ToString::to_string); // 5. If label is null, then return null. // 6. Let encoding be the result of getting an encoding from label. @@ -1617,23 +1629,22 @@ impl XMLHttpRequest { } /// <https://xhr.spec.whatwg.org/#response-mime-type> - fn response_mime_type(&self) -> Option<Mime> { - return extract_mime_type(&self.response_headers.borrow()) - .and_then(|mime_as_bytes| { - String::from_utf8(mime_as_bytes) - .unwrap_or_default() - .parse() - .ok() - }) - .or(Some(Mime::new(TEXT, XML))); + fn response_mime_type(&self) -> Mime { + // 1. Let mimeType be the result of extracting a MIME type from xhr’s response’s + // header list. + // 2. If mimeType is failure, then set mimeType to text/xml. + // 3. Return mimeType. + extract_mime_type_as_dataurl_mime(&self.response_headers.borrow()) + .unwrap_or_else(|| Mime::new(TEXT, XML)) } /// <https://xhr.spec.whatwg.org/#final-mime-type> - fn final_mime_type(&self) -> Option<Mime> { - match *self.override_mime_type.borrow() { - Some(ref override_mime) => Some(override_mime.clone()), - None => self.response_mime_type(), - } + fn final_mime_type(&self) -> Mime { + self.override_mime_type + .borrow() + .as_ref() + .map(MimeExt::clone) + .unwrap_or_else(|| self.response_mime_type()) } } diff --git a/components/script/stylesheet_loader.rs b/components/script/stylesheet_loader.rs index a18d63e323b..5efaf78e542 100644 --- a/components/script/stylesheet_loader.rs +++ b/components/script/stylesheet_loader.rs @@ -163,7 +163,8 @@ impl FetchResponseListener for StylesheetContext { Some(meta) => meta, None => return, }; - let is_css = metadata.content_type.is_some_and(|ct| { + + let mut is_css = metadata.content_type.is_some_and(|ct| { let mime: Mime = ct.into_inner().into(); mime.type_() == mime::TEXT && mime.subtype() == mime::CSS }) || ( @@ -177,6 +178,17 @@ impl FetchResponseListener for StylesheetContext { document.origin().immutable().clone() == metadata.final_url.origin() ); + // From <https://html.spec.whatwg.org/multipage/#link-type-stylesheet>: + // > Quirk: If the document has been set to quirks mode, has the same origin as + // > the URL of the external resource, and the Content-Type metadata of the + // > external resource is not a supported style sheet type, the user agent must + // > instead assume it to be text/css. + if document.quirks_mode() == QuirksMode::Quirks && + document.url().origin() == self.url.origin() + { + is_css = true; + } + let data = if is_css { let data = std::mem::take(&mut self.data); self.unminify_css(data, metadata.final_url.clone()) diff --git a/components/script_bindings/codegen/Bindings.conf b/components/script_bindings/codegen/Bindings.conf index 92871bc54aa..7cc092e574e 100644 --- a/components/script_bindings/codegen/Bindings.conf +++ b/components/script_bindings/codegen/Bindings.conf @@ -39,7 +39,7 @@ DOMInterfaces = { 'Blob': { 'weakReferenceable': True, 'canGc': ['Slice', 'Text', 'ArrayBuffer', 'Stream', 'Bytes'], - 'inRealms': ['Bytes'], + 'inRealms': ['Bytes', 'ArrayBuffer'], }, 'Bluetooth': { @@ -721,8 +721,8 @@ DOMInterfaces = { }, 'ReadableStream': { - 'canGc': ['GetReader', 'Cancel', 'PipeTo', 'Tee'], - 'inRealms': ['PipeTo'], + 'canGc': ['GetReader', 'Cancel', 'PipeTo', 'PipeThrough', 'Tee'], + 'inRealms': ['PipeTo', 'PipeThrough'], }, "ReadableStreamDefaultController": { diff --git a/components/script_bindings/webidls/Element.webidl b/components/script_bindings/webidls/Element.webidl index 42733b91929..4545b18d058 100644 --- a/components/script_bindings/webidls/Element.webidl +++ b/components/script_bindings/webidls/Element.webidl @@ -84,7 +84,7 @@ interface Element : Node { [CEReactions, Throws] undefined insertAdjacentHTML(DOMString position, (TrustedHTML or DOMString) string); - [Throws, Pref="dom_shadowdom_enabled"] ShadowRoot attachShadow(ShadowRootInit init); + [Throws] ShadowRoot attachShadow(ShadowRootInit init); readonly attribute ShadowRoot? shadowRoot; }; diff --git a/components/script_bindings/webidls/ReadableStream.webidl b/components/script_bindings/webidls/ReadableStream.webidl index 12798262953..4ec190bd911 100644 --- a/components/script_bindings/webidls/ReadableStream.webidl +++ b/components/script_bindings/webidls/ReadableStream.webidl @@ -20,8 +20,8 @@ interface _ReadableStream { [Throws] ReadableStreamReader getReader(optional ReadableStreamGetReaderOptions options = {}); - // [Throws] - // ReadableStream pipeThrough(ReadableWritablePair transform, optional StreamPipeOptions options = {}); + [Throws] + ReadableStream pipeThrough(ReadableWritablePair transform, optional StreamPipeOptions options = {}); [NewObject] Promise<undefined> pipeTo(WritableStream destination, optional StreamPipeOptions options = {}); diff --git a/components/shared/base/id.rs b/components/shared/base/id.rs index cc4ad947494..b6ad1b3de9b 100644 --- a/components/shared/base/id.rs +++ b/components/shared/base/id.rs @@ -17,7 +17,7 @@ use malloc_size_of::MallocSizeOfOps; use malloc_size_of_derive::MallocSizeOf; use parking_lot::Mutex; use serde::{Deserialize, Serialize}; -use webrender_api::{ExternalScrollId, PipelineId as WebRenderPipelineId, SpatialId}; +use webrender_api::{ExternalScrollId, PipelineId as WebRenderPipelineId}; /// Asserts the size of a type at compile time. macro_rules! size_of_test { @@ -397,7 +397,4 @@ pub const TEST_WEBVIEW_ID: WebViewId = WebViewId(TEST_BROWSING_CONTEXT_ID); pub struct ScrollTreeNodeId { /// The index of this scroll tree node in the tree's array of nodes. pub index: usize, - - /// The WebRender spatial id of this scroll tree node. - pub spatial_id: SpatialId, } diff --git a/components/shared/compositing/display_list.rs b/components/shared/compositing/display_list.rs index 6aa822cb145..4fb0d5e94db 100644 --- a/components/shared/compositing/display_list.rs +++ b/components/shared/compositing/display_list.rs @@ -6,11 +6,17 @@ use base::id::ScrollTreeNodeId; use embedder_traits::Cursor; +use euclid::SideOffsets2D; use malloc_size_of_derive::MallocSizeOf; use serde::{Deserialize, Serialize}; use style::values::specified::Overflow; -use webrender_api::units::{LayoutSize, LayoutVector2D}; -use webrender_api::{Epoch, ExternalScrollId, PipelineId, ScrollLocation, SpatialId}; +use webrender_api::units::{ + LayoutPixel, LayoutPoint, LayoutRect, LayoutSize, LayoutTransform, LayoutVector2D, +}; +use webrender_api::{ + Epoch, ExternalScrollId, PipelineId, ReferenceFrameKind, ScrollLocation, SpatialId, + StickyOffsetBounds, TransformStyle, +}; /// The scroll sensitivity of a scroll node in a particular axis ie whether it can be scrolled due to /// input events and script events or only script events. @@ -56,6 +62,29 @@ pub struct HitTestInfo { pub scroll_tree_node: ScrollTreeNodeId, } +#[derive(Debug, Deserialize, Serialize)] +pub enum SpatialTreeNodeInfo { + ReferenceFrame(ReferenceFrameNodeInfo), + Scroll(ScrollableNodeInfo), + Sticky(StickyNodeInfo), +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct StickyNodeInfo { + pub frame_rect: LayoutRect, + pub margins: SideOffsets2D<Option<f32>, LayoutPixel>, + pub vertical_offset_bounds: StickyOffsetBounds, + pub horizontal_offset_bounds: StickyOffsetBounds, +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct ReferenceFrameNodeInfo { + pub origin: LayoutPoint, + pub transform_style: TransformStyle, + pub transform: LayoutTransform, + pub kind: ReferenceFrameKind, +} + /// Data stored for nodes in the [ScrollTree] that actually scroll, /// as opposed to reference frames and sticky nodes which do not. #[derive(Debug, Deserialize, Serialize)] @@ -64,8 +93,11 @@ pub struct ScrollableNodeInfo { /// it between successive re-layouts. pub external_id: ExternalScrollId, - /// Amount that this `ScrollableNode` can scroll in both directions. - pub scrollable_size: LayoutSize, + /// The content rectangle for this scroll node; + pub content_rect: LayoutRect, + + /// The clip rectange for this scroll node. + pub clip_rect: LayoutRect, /// Whether this `ScrollableNode` is sensitive to input events. pub scroll_sensitivity: AxesScrollSensitivity, @@ -74,6 +106,12 @@ pub struct ScrollableNodeInfo { pub offset: LayoutVector2D, } +impl ScrollableNodeInfo { + fn scrollable_size(&self) -> LayoutSize { + self.content_rect.size() - self.clip_rect.size() + } +} + #[derive(Debug, Deserialize, Serialize)] /// A node in a tree of scroll nodes. This may either be a scrollable /// node which responds to scroll events or a non-scrollable one. @@ -82,35 +120,51 @@ pub struct ScrollTreeNode { /// None then this is the root node. pub parent: Option<ScrollTreeNodeId>, - /// Scrolling data which will not be None if this is a scrolling node. - pub scroll_info: Option<ScrollableNodeInfo>, + /// The WebRender id, which is filled in when this tree is serialiezd + /// into a WebRender display list. + pub webrender_id: Option<SpatialId>, + + /// Specific information about this node, depending on whether it is a scroll node + /// or a reference frame. + pub info: SpatialTreeNodeInfo, } impl ScrollTreeNode { + /// Get the WebRender [`SpatialId`] for the given [`ScrollNodeId`]. This will + /// panic if [`ScrollTree::build_display_list`] has not been called yet. + pub fn webrender_id(&self) -> SpatialId { + self.webrender_id + .expect("Should have called ScrollTree::build_display_list before querying SpatialId") + } + /// Get the external id of this node. pub fn external_id(&self) -> Option<ExternalScrollId> { - self.scroll_info.as_ref().map(|info| info.external_id) + match self.info { + SpatialTreeNodeInfo::Scroll(ref info) => Some(info.external_id), + _ => None, + } } /// Get the offset id of this node if it applies. pub fn offset(&self) -> Option<LayoutVector2D> { - self.scroll_info.as_ref().map(|info| info.offset) + match self.info { + SpatialTreeNodeInfo::Scroll(ref info) => Some(info.offset), + _ => None, + } } /// Set the offset for this node, returns false if this was a /// non-scrolling node for which you cannot set the offset. pub fn set_offset(&mut self, new_offset: LayoutVector2D) -> bool { - match self.scroll_info { - Some(ref mut info) => { - let scrollable_width = info.scrollable_size.width; - let scrollable_height = info.scrollable_size.height; - - if scrollable_width > 0. { - info.offset.x = (new_offset.x).min(0.0).max(-scrollable_width); + match self.info { + SpatialTreeNodeInfo::Scroll(ref mut info) => { + let scrollable_size = info.scrollable_size(); + if scrollable_size.width > 0. { + info.offset.x = (new_offset.x).min(0.0).max(-scrollable_size.width); } - if scrollable_height > 0. { - info.offset.y = (new_offset.y).min(0.0).max(-scrollable_height); + if scrollable_size.height > 0. { + info.offset.y = (new_offset.y).min(0.0).max(-scrollable_size.height); } true }, @@ -125,9 +179,9 @@ impl ScrollTreeNode { &mut self, scroll_location: ScrollLocation, ) -> Option<(ExternalScrollId, LayoutVector2D)> { - let info = match self.scroll_info { - Some(ref mut data) => data, - None => return None, + let info = match self.info { + SpatialTreeNodeInfo::Scroll(ref mut info) => info, + _ => return None, }; if info.scroll_sensitivity.x != ScrollSensitivity::ScriptAndInputEvents && @@ -148,7 +202,7 @@ impl ScrollTreeNode { return Some((info.external_id, info.offset)); }, ScrollLocation::End => { - let end_pos = -info.scrollable_size.height; + let end_pos = -info.scrollable_size().height; if info.offset.y.round() <= end_pos { // Nothing to do on this layer. return None; @@ -159,20 +213,23 @@ impl ScrollTreeNode { }, }; - let scrollable_width = info.scrollable_size.width; - let scrollable_height = info.scrollable_size.height; + let scrollable_size = info.scrollable_size(); let original_layer_scroll_offset = info.offset; - if scrollable_width > 0. && + if scrollable_size.width > 0. && info.scroll_sensitivity.x == ScrollSensitivity::ScriptAndInputEvents { - info.offset.x = (info.offset.x + delta.x).min(0.0).max(-scrollable_width); + info.offset.x = (info.offset.x + delta.x) + .min(0.0) + .max(-scrollable_size.width); } - if scrollable_height > 0. && + if scrollable_size.height > 0. && info.scroll_sensitivity.y == ScrollSensitivity::ScriptAndInputEvents { - info.offset.y = (info.offset.y + delta.y).min(0.0).max(-scrollable_height); + info.offset.y = (info.offset.y + delta.y) + .min(0.0) + .max(-scrollable_size.height); } if info.offset != original_layer_scroll_offset { @@ -199,16 +256,23 @@ impl ScrollTree { pub fn add_scroll_tree_node( &mut self, parent: Option<&ScrollTreeNodeId>, - spatial_id: SpatialId, - scroll_info: Option<ScrollableNodeInfo>, + info: SpatialTreeNodeInfo, ) -> ScrollTreeNodeId { self.nodes.push(ScrollTreeNode { parent: parent.cloned(), - scroll_info, + webrender_id: None, + info, }); ScrollTreeNodeId { index: self.nodes.len() - 1, - spatial_id, + } + } + + /// Once WebRender display list construction is complete for this [`ScrollTree`], update + /// the mapping of nodes to WebRender [`SpatialId`]s. + pub fn update_mapping(&mut self, mapping: Vec<SpatialId>) { + for (spatial_id, node) in mapping.into_iter().zip(self.nodes.iter_mut()) { + node.webrender_id = Some(spatial_id); } } @@ -218,10 +282,16 @@ impl ScrollTree { } /// Get an immutable reference to the node with the given index. - pub fn get_node(&mut self, id: &ScrollTreeNodeId) -> &ScrollTreeNode { + pub fn get_node(&self, id: &ScrollTreeNodeId) -> &ScrollTreeNode { &self.nodes[id.index] } + /// Get the WebRender [`SpatialId`] for the given [`ScrollNodeId`]. This will + /// panic if [`ScrollTree::build_display_list`] has not been called yet. + pub fn webrender_id(&self, id: &ScrollTreeNodeId) -> SpatialId { + self.get_node(id).webrender_id() + } + /// Scroll the given scroll node on this scroll tree. If the node cannot be scrolled, /// because it isn't a scrollable node or it's already scrolled to the maximum scroll /// extent, try to scroll an ancestor of this node. Returns the node scrolled and the @@ -251,8 +321,10 @@ impl ScrollTree { offset: LayoutVector2D, ) -> bool { for node in self.nodes.iter_mut() { - match node.scroll_info { - Some(ref mut scroll_info) if scroll_info.external_id == external_scroll_id => { + match node.info { + SpatialTreeNodeInfo::Scroll(ref mut scroll_info) + if scroll_info.external_id == external_scroll_id => + { scroll_info.offset = offset; return true; }, @@ -320,15 +392,19 @@ impl CompositorDisplayListInfo { let mut scroll_tree = ScrollTree::default(); let root_reference_frame_id = scroll_tree.add_scroll_tree_node( None, - SpatialId::root_reference_frame(pipeline_id), - None, + SpatialTreeNodeInfo::ReferenceFrame(ReferenceFrameNodeInfo { + origin: Default::default(), + transform_style: TransformStyle::Flat, + transform: LayoutTransform::identity(), + kind: ReferenceFrameKind::default(), + }), ); let root_scroll_node_id = scroll_tree.add_scroll_tree_node( Some(&root_reference_frame_id), - SpatialId::root_scroll_node(pipeline_id), - Some(ScrollableNodeInfo { + SpatialTreeNodeInfo::Scroll(ScrollableNodeInfo { external_id: ExternalScrollId(0, pipeline_id), - scrollable_size: content_size - viewport_size, + content_rect: LayoutRect::from_origin_and_size(LayoutPoint::zero(), content_size), + clip_rect: LayoutRect::from_origin_and_size(LayoutPoint::zero(), viewport_size), scroll_sensitivity: viewport_scroll_sensitivity, offset: LayoutVector2D::zero(), }), diff --git a/components/shared/compositing/lib.rs b/components/shared/compositing/lib.rs index a6701ca2b52..061dfe023df 100644 --- a/components/shared/compositing/lib.rs +++ b/components/shared/compositing/lib.rs @@ -236,7 +236,7 @@ impl CrossProcessCompositorApi { pub fn send_display_list( &self, webview_id: WebViewId, - display_list_info: CompositorDisplayListInfo, + display_list_info: &CompositorDisplayListInfo, list: BuiltDisplayList, ) { let (display_list_data, display_list_descriptor) = list.into_data(); diff --git a/components/shared/compositing/tests/compositor.rs b/components/shared/compositing/tests/compositor.rs index 4d2ecfd99c9..e04f1770964 100644 --- a/components/shared/compositing/tests/compositor.rs +++ b/components/shared/compositing/tests/compositor.rs @@ -4,11 +4,12 @@ use base::id::ScrollTreeNodeId; use compositing_traits::display_list::{ - AxesScrollSensitivity, ScrollSensitivity, ScrollTree, ScrollableNodeInfo, + AxesScrollSensitivity, ScrollSensitivity, ScrollTree, ScrollableNodeInfo, SpatialTreeNodeInfo, + StickyNodeInfo, }; -use euclid::Size2D; +use euclid::{SideOffsets2D, Size2D}; use webrender_api::units::LayoutVector2D; -use webrender_api::{ExternalScrollId, PipelineId, ScrollLocation, SpatialId}; +use webrender_api::{ExternalScrollId, PipelineId, ScrollLocation, StickyOffsetBounds}; fn add_mock_scroll_node(tree: &mut ScrollTree) -> ScrollTreeNodeId { let pipeline_id = PipelineId(0, 0); @@ -16,7 +17,6 @@ fn add_mock_scroll_node(tree: &mut ScrollTree) -> ScrollTreeNodeId { let parent = if num_nodes > 0 { Some(ScrollTreeNodeId { index: num_nodes - 1, - spatial_id: SpatialId::new(num_nodes - 1, pipeline_id), }) } else { None @@ -24,10 +24,10 @@ fn add_mock_scroll_node(tree: &mut ScrollTree) -> ScrollTreeNodeId { tree.add_scroll_tree_node( parent.as_ref(), - SpatialId::new(num_nodes, pipeline_id), - Some(ScrollableNodeInfo { + SpatialTreeNodeInfo::Scroll(ScrollableNodeInfo { external_id: ExternalScrollId(num_nodes as u64, pipeline_id), - scrollable_size: Size2D::new(100.0, 100.0), + content_rect: Size2D::new(200.0, 200.0).into(), + clip_rect: Size2D::new(100.0, 100.0).into(), scroll_sensitivity: AxesScrollSensitivity { x: ScrollSensitivity::ScriptAndInputEvents, y: ScrollSensitivity::ScriptAndInputEvents, @@ -78,8 +78,15 @@ fn test_scroll_tree_simple_scroll_chaining() { let pipeline_id = PipelineId(0, 0); let parent_id = add_mock_scroll_node(&mut scroll_tree); - let unscrollable_child_id = - scroll_tree.add_scroll_tree_node(Some(&parent_id), SpatialId::new(1, pipeline_id), None); + let unscrollable_child_id = scroll_tree.add_scroll_tree_node( + Some(&parent_id), + SpatialTreeNodeInfo::Sticky(StickyNodeInfo { + frame_rect: Size2D::new(100.0, 100.0).into(), + margins: SideOffsets2D::default(), + vertical_offset_bounds: StickyOffsetBounds::new(0.0, 0.0), + horizontal_offset_bounds: StickyOffsetBounds::new(0.0, 0.0), + }), + ); let (scrolled_id, offset) = scroll_tree .scroll_node_or_ancestor( @@ -157,16 +164,14 @@ fn test_scroll_tree_chain_through_overflow_hidden() { let pipeline_id = PipelineId(0, 0); let parent_id = add_mock_scroll_node(&mut scroll_tree); let overflow_hidden_id = add_mock_scroll_node(&mut scroll_tree); - scroll_tree - .get_node_mut(&overflow_hidden_id) - .scroll_info - .as_mut() - .map(|info| { - info.scroll_sensitivity = AxesScrollSensitivity { - x: ScrollSensitivity::Script, - y: ScrollSensitivity::Script, - }; - }); + let node = scroll_tree.get_node_mut(&overflow_hidden_id); + + if let SpatialTreeNodeInfo::Scroll(ref mut scroll_node_info) = node.info { + scroll_node_info.scroll_sensitivity = AxesScrollSensitivity { + x: ScrollSensitivity::Script, + y: ScrollSensitivity::Script, + }; + } let (scrolled_id, offset) = scroll_tree .scroll_node_or_ancestor( diff --git a/components/shared/constellation/lib.rs b/components/shared/constellation/lib.rs index d85fbe31bdf..005295000c9 100644 --- a/components/shared/constellation/lib.rs +++ b/components/shared/constellation/lib.rs @@ -152,7 +152,7 @@ pub enum TraversalDirection { } /// A task on the <https://html.spec.whatwg.org/multipage/#port-message-queue> -#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)] +#[derive(Debug, Deserialize, MallocSizeOf, Serialize)] pub struct PortMessageTask { /// The origin of this task. pub origin: ImmutableOrigin, diff --git a/components/shared/constellation/structured_data/mod.rs b/components/shared/constellation/structured_data/mod.rs index 3fb9d0c5f67..81e3849e476 100644 --- a/components/shared/constellation/structured_data/mod.rs +++ b/components/shared/constellation/structured_data/mod.rs @@ -20,7 +20,7 @@ pub use transferable::*; /// A data-holder for serialized data and transferred objects. /// <https://html.spec.whatwg.org/multipage/#structuredserializewithtransfer> -#[derive(Clone, Debug, Default, Deserialize, MallocSizeOf, Serialize)] +#[derive(Debug, Default, Deserialize, MallocSizeOf, Serialize)] pub struct StructuredSerializedData { /// Data serialized by SpiderMonkey. pub serialized: Vec<u8>, @@ -32,6 +32,8 @@ pub struct StructuredSerializedData { pub exceptions: Option<HashMap<DomExceptionId, DomException>>, /// Transferred objects. pub ports: Option<HashMap<MessagePortId, MessagePortImpl>>, + /// Transform streams transferred objects. + pub transform_streams: Option<HashMap<MessagePortId, TransformStreamData>>, } impl StructuredSerializedData { diff --git a/components/shared/constellation/structured_data/serializable.rs b/components/shared/constellation/structured_data/serializable.rs index 194f0567c51..22370087665 100644 --- a/components/shared/constellation/structured_data/serializable.rs +++ b/components/shared/constellation/structured_data/serializable.rs @@ -88,7 +88,7 @@ impl Clone for BroadcastMsg { } /// File-based blob -#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)] +#[derive(Debug, Deserialize, MallocSizeOf, Serialize)] pub struct FileBlob { #[ignore_malloc_size_of = "Uuid are hard(not really)"] id: Uuid, @@ -164,7 +164,7 @@ impl BroadcastClone for BlobImpl { } /// The data backing a DOM Blob. -#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)] +#[derive(Debug, Deserialize, MallocSizeOf, Serialize)] pub struct BlobImpl { /// UUID of the blob. blob_id: BlobId, @@ -177,7 +177,7 @@ pub struct BlobImpl { } /// Different backends of Blob -#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)] +#[derive(Debug, Deserialize, MallocSizeOf, Serialize)] pub enum BlobData { /// File-based blob, whose content lives in the net process File(FileBlob), diff --git a/components/shared/constellation/structured_data/transferable.rs b/components/shared/constellation/structured_data/transferable.rs index 528c1e79e65..3210a41a538 100644 --- a/components/shared/constellation/structured_data/transferable.rs +++ b/components/shared/constellation/structured_data/transferable.rs @@ -15,6 +15,12 @@ use strum::EnumIter; use crate::PortMessageTask; +#[derive(Debug, Deserialize, MallocSizeOf, Serialize)] +pub struct TransformStreamData { + pub readable: (MessagePortId, MessagePortImpl), + pub writable: (MessagePortId, MessagePortImpl), +} + /// All the DOM interfaces that can be transferred. #[derive(Clone, Copy, Debug, EnumIter)] pub enum Transferrable { @@ -28,7 +34,7 @@ pub enum Transferrable { TransformStream, } -#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)] +#[derive(Debug, Deserialize, MallocSizeOf, Serialize)] enum MessagePortState { /// <https://html.spec.whatwg.org/multipage/#detached> Detached, @@ -42,7 +48,7 @@ enum MessagePortState { Disabled(bool), } -#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)] +#[derive(Debug, Deserialize, MallocSizeOf, Serialize)] /// The data and logic backing the DOM managed MessagePort. pub struct MessagePortImpl { /// The current state of the port. diff --git a/components/shared/embedder/resources.rs b/components/shared/embedder/resources.rs index 830a403698e..28464a95d1a 100644 --- a/components/shared/embedder/resources.rs +++ b/components/shared/embedder/resources.rs @@ -116,7 +116,7 @@ impl Resource { match self { Resource::BluetoothBlocklist => "gatt_blocklist.txt", Resource::DomainList => "public_domains.txt", - Resource::HstsPreloadList => "hsts_preload.json", + Resource::HstsPreloadList => "hsts_preload.fstmap", Resource::BadCertHTML => "badcert.html", Resource::NetErrorHTML => "neterror.html", Resource::RippyPNG => "rippy.png", @@ -155,7 +155,7 @@ fn resources_for_tests() -> Box<dyn ResourceReaderMethods + Sync + Send> { &include_bytes!("../../../resources/public_domains.txt")[..] }, Resource::HstsPreloadList => { - &include_bytes!("../../../resources/hsts_preload.json")[..] + &include_bytes!("../../../resources/hsts_preload.fstmap")[..] }, Resource::BadCertHTML => &include_bytes!("../../../resources/badcert.html")[..], Resource::NetErrorHTML => &include_bytes!("../../../resources/neterror.html")[..], diff --git a/components/shared/net/Cargo.toml b/components/shared/net/Cargo.toml index 79ea936a688..0dfad486455 100644 --- a/components/shared/net/Cargo.toml +++ b/components/shared/net/Cargo.toml @@ -19,6 +19,7 @@ compositing_traits = { workspace = true } content-security-policy = { workspace = true } cookie = { workspace = true } crossbeam-channel = { workspace = true } +data-url = { workspace = true } embedder_traits = { workspace = true } headers = { workspace = true } http = { workspace = true } diff --git a/components/shared/net/fetch/headers.rs b/components/shared/net/fetch/headers.rs index 11bb68d5d0a..5ffd537adf8 100644 --- a/components/shared/net/fetch/headers.rs +++ b/components/shared/net/fetch/headers.rs @@ -3,8 +3,9 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ use std::iter::Peekable; -use std::str::Chars; +use std::str::{Chars, FromStr}; +use data_url::mime::Mime as DataUrlMime; use headers::HeaderMap; /// <https://fetch.spec.whatwg.org/#http-tab-or-space> @@ -184,3 +185,93 @@ fn collect_http_quoted_string(position: &mut Peekable<Chars>, extract_value: boo // Step 6, 7 value } + +/// <https://fetch.spec.whatwg.org/#concept-header-extract-mime-type> +/// This function uses data_url::Mime to parse the MIME Type because +/// mime::Mime does not provide a parser following the Fetch spec +/// see <https://github.com/hyperium/mime/issues/106> +pub fn extract_mime_type_as_dataurl_mime(headers: &HeaderMap) -> Option<DataUrlMime> { + // > 1: Let charset be null. + let mut charset = None; + // > 2: Let essence be null. + let mut essence = String::new(); + // > 3: Let mimeType be null. + let mut mime_type = None; + + // > 4: Let values be the result of getting, decoding, and splitting `Content-Type` + // from headers. + // > 5: If values is null, then return failure. + let headers_values = get_decode_and_split_header_name("content-type", headers)?; + + // > 6: For each value of values: + for header_value in headers_values.iter() { + // > 6.1: Let temporaryMimeType be the result of parsing value. + match DataUrlMime::from_str(header_value) { + // > 6.2: If temporaryMimeType is failure or its essence is "*/*", then continue. + Err(_) => continue, + Ok(temp_mime) => { + let temp_essence = format!("{}/{}", temp_mime.type_, temp_mime.subtype); + + // > 6.2: If temporaryMimeType is failure or its essence is "*/*", then + // continue. + if temp_essence == "*/*" { + continue; + } + + // > 6.3: Set mimeType to temporaryMimeType. + mime_type = Some(DataUrlMime { + type_: temp_mime.type_.to_string(), + subtype: temp_mime.subtype.to_string(), + parameters: temp_mime.parameters.clone(), + }); + + // > 6.4: If mimeType’s essence is not essence, then: + let temp_charset = &temp_mime.get_parameter("charset"); + if temp_essence != essence { + // > 6.4.1: Set charset to null. + // > 6.4.2: If mimeType’s parameters["charset"] exists, then set + // charset to mimeType’s parameters["charset"]. + charset = temp_charset.map(|c| c.to_string()); + // > 6.4.3: Set essence to mimeType’s essence. + essence = temp_essence.to_owned(); + } else { + // > 6.5: Otherwise, if mimeType’s parameters["charset"] does not exist, + // and charset is non-null, set mimeType’s parameters["charset"] to charset. + if temp_charset.is_none() && charset.is_some() { + let DataUrlMime { + type_: t, + subtype: st, + parameters: p, + } = mime_type.unwrap(); + let mut params = p; + params.push(("charset".to_string(), charset.clone().unwrap())); + mime_type = Some(DataUrlMime { + type_: t.to_string(), + subtype: st.to_string(), + parameters: params, + }) + } + } + }, + } + } + + // > 7: If mimeType is null, then return failure. + // > 8: Return mimeType. + mime_type +} + +pub fn extract_mime_type(headers: &HeaderMap) -> Option<Vec<u8>> { + extract_mime_type_as_dataurl_mime(headers).map(|m| format!("{}", m).into_bytes()) +} + +pub fn extract_mime_type_as_mime(headers: &HeaderMap) -> Option<mime::Mime> { + extract_mime_type_as_dataurl_mime(headers).and_then(|mime: DataUrlMime| { + // Try to transform a data-url::mime::Mime into a mime::Mime + let mut mime_as_str = format!("{}/{}", mime.type_, mime.subtype); + for p in mime.parameters { + mime_as_str.push_str(format!("; {}={}", p.0, p.1).as_str()); + } + mime_as_str.parse().ok() + }) +} diff --git a/components/shared/net/pub_domains.rs b/components/shared/net/pub_domains.rs index cbbb2b465b2..6e6f883cd2f 100644 --- a/components/shared/net/pub_domains.rs +++ b/components/shared/net/pub_domains.rs @@ -19,9 +19,11 @@ use std::iter::FromIterator; use std::sync::LazyLock; use embedder_traits::resources::{self, Resource}; +use malloc_size_of::{MallocSizeOf, MallocSizeOfOps}; +use malloc_size_of_derive::MallocSizeOf; use servo_url::{Host, ImmutableOrigin, ServoUrl}; -#[derive(Clone, Debug, Default)] +#[derive(Clone, Debug, Default, MallocSizeOf)] pub struct PubDomainRules { rules: HashSet<String>, wildcards: HashSet<String>, @@ -30,6 +32,10 @@ pub struct PubDomainRules { static PUB_DOMAINS: LazyLock<PubDomainRules> = LazyLock::new(load_pub_domains); +pub fn public_suffix_list_size_of(ops: &mut MallocSizeOfOps) -> usize { + PUB_DOMAINS.size_of(ops) +} + impl<'a> FromIterator<&'a str> for PubDomainRules { fn from_iter<T>(iter: T) -> Self where diff --git a/components/shared/net/response.rs b/components/shared/net/response.rs index f91993ddccb..9a01fbbf965 100644 --- a/components/shared/net/response.rs +++ b/components/shared/net/response.rs @@ -7,7 +7,6 @@ use std::sync::Mutex; use std::sync::atomic::AtomicBool; -use headers::{ContentType, HeaderMapExt}; use http::HeaderMap; use hyper_serde::Serde; use malloc_size_of_derive::MallocSizeOf; @@ -15,6 +14,7 @@ use serde::{Deserialize, Serialize}; use servo_arc::Arc; use servo_url::ServoUrl; +use crate::fetch::headers::extract_mime_type_as_mime; use crate::http_status::HttpStatus; use crate::{ FetchMetadata, FilteredMetadata, Metadata, NetworkError, ReferrerPolicy, ResourceFetchTiming, @@ -300,13 +300,7 @@ impl Response { pub fn metadata(&self) -> Result<FetchMetadata, NetworkError> { fn init_metadata(response: &Response, url: &ServoUrl) -> Metadata { let mut metadata = Metadata::default(url.clone()); - metadata.set_content_type( - response - .headers - .typed_get::<ContentType>() - .map(|v| v.into()) - .as_ref(), - ); + metadata.set_content_type(extract_mime_type_as_mime(&response.headers).as_ref()); metadata.location_url.clone_from(&response.location_url); metadata.headers = Some(Serde(response.headers.clone())); metadata.status.clone_from(&response.status); diff --git a/components/shared/net/storage_thread.rs b/components/shared/net/storage_thread.rs index 0253603016e..2ba0aa12445 100644 --- a/components/shared/net/storage_thread.rs +++ b/components/shared/net/storage_thread.rs @@ -4,6 +4,7 @@ use ipc_channel::ipc::IpcSender; use malloc_size_of_derive::MallocSizeOf; +use profile_traits::mem::ReportsChan; use serde::{Deserialize, Serialize}; use servo_url::ServoUrl; @@ -45,4 +46,7 @@ pub enum StorageThreadMsg { /// send a reply when done cleaning up thread resources and then shut it down Exit(IpcSender<()>), + + /// Measure memory used by this thread and send the report over the provided channel. + CollectMemoryReport(ReportsChan), } diff --git a/components/webdriver_server/actions.rs b/components/webdriver_server/actions.rs index 43dd0e183dc..7965120b0fd 100644 --- a/components/webdriver_server/actions.rs +++ b/components/webdriver_server/actions.rs @@ -33,6 +33,9 @@ pub(crate) enum InputSourceState { } // https://w3c.github.io/webdriver/#dfn-pointer-input-source +// TODO: subtype is used for https://w3c.github.io/webdriver/#dfn-get-a-pointer-id +// Need to add pointer-id to the following struct +#[allow(dead_code)] pub(crate) struct PointerInputState { subtype: PointerType, pressed: HashSet<u64>, @@ -142,7 +145,22 @@ impl Handler { .or_insert(InputSourceState::Key(KeyInputState::new())); match action { KeyAction::Down(action) => { - self.dispatch_keydown_action(source_id, action) + self.dispatch_keydown_action(source_id, action); + // Step 9. If subtype is "keyDown", append a copy of action + // object with the subtype property changed to "keyUp" to + // input state's input cancel list. + self.session_mut().unwrap().input_cancel_list.push( + ActionSequence { + id: source_id.into(), + actions: ActionsType::Key { + actions: vec![KeyActionItem::Key(KeyAction::Up( + KeyUpAction { + value: action.value.clone(), + }, + ))], + }, + }, + ); }, KeyAction::Up(action) => { self.dispatch_keyup_action(source_id, action) @@ -172,7 +190,27 @@ impl Handler { match action { PointerAction::Cancel => (), PointerAction::Down(action) => { - self.dispatch_pointerdown_action(source_id, action) + self.dispatch_pointerdown_action(source_id, action); + + // Step 10. If subtype is "pointerDown", append a copy of action + // object with the subtype property changed to "pointerUp" to + // input state's input cancel list. + self.session_mut().unwrap().input_cancel_list.push( + ActionSequence { + id: source_id.into(), + actions: ActionsType::Pointer { + parameters: PointerActionParameters { + pointer_type: parameters.pointer_type, + }, + actions: vec![PointerActionItem::Pointer( + PointerAction::Up(PointerUpAction { + button: action.button, + ..Default::default() + }), + )], + }, + }, + ); }, PointerAction::Move(action) => self.dispatch_pointermove_action( source_id, @@ -215,26 +253,18 @@ impl Handler { // https://w3c.github.io/webdriver/#dfn-dispatch-a-keydown-action fn dispatch_keydown_action(&mut self, source_id: &str, action: &KeyDownAction) { - let session = self.session.as_mut().unwrap(); - + // Step 1 let raw_key = action.value.chars().next().unwrap(); - let key_input_state = match session.input_state_table.get_mut(source_id).unwrap() { - InputSourceState::Key(key_input_state) => key_input_state, - _ => unreachable!(), - }; - - session.input_cancel_list.push(ActionSequence { - id: source_id.into(), - actions: ActionsType::Key { - actions: vec![KeyActionItem::Key(KeyAction::Up(KeyUpAction { - value: action.value.clone(), - }))], - }, - }); + let key_input_state = self.get_key_input_state_mut(source_id); + // Step 2 - 11. Done by `keyboard-types` crate. let keyboard_event = key_input_state.dispatch_keydown(raw_key); - let cmd_msg = - WebDriverCommandMsg::KeyboardAction(session.browsing_context_id, keyboard_event); + + // Step 12 + let cmd_msg = WebDriverCommandMsg::KeyboardAction( + self.session().unwrap().browsing_context_id, + keyboard_event, + ); self.constellation_chan .send(EmbedderToConstellationMessage::WebDriverCommand(cmd_msg)) .unwrap(); @@ -242,71 +272,57 @@ impl Handler { // https://w3c.github.io/webdriver/#dfn-dispatch-a-keyup-action fn dispatch_keyup_action(&mut self, source_id: &str, action: &KeyUpAction) { - let session = self.session.as_mut().unwrap(); - + // Step 1 let raw_key = action.value.chars().next().unwrap(); - let key_input_state = match session.input_state_table.get_mut(source_id).unwrap() { - InputSourceState::Key(key_input_state) => key_input_state, - _ => unreachable!(), - }; - - session.input_cancel_list.push(ActionSequence { - id: source_id.into(), - actions: ActionsType::Key { - actions: vec![KeyActionItem::Key(KeyAction::Up(KeyUpAction { - value: action.value.clone(), - }))], - }, - }); + let key_input_state = self.get_key_input_state_mut(source_id); + // Step 2 - 11. Done by `keyboard-types` crate. if let Some(keyboard_event) = key_input_state.dispatch_keyup(raw_key) { - let cmd_msg = - WebDriverCommandMsg::KeyboardAction(session.browsing_context_id, keyboard_event); + // Step 12 + let cmd_msg = WebDriverCommandMsg::KeyboardAction( + self.session().unwrap().browsing_context_id, + keyboard_event, + ); self.constellation_chan .send(EmbedderToConstellationMessage::WebDriverCommand(cmd_msg)) .unwrap(); } } + fn get_pointer_input_state_mut(&mut self, source_id: &str) -> &mut PointerInputState { + let session = self.session_mut().unwrap(); + let pointer_input_state = match session.input_state_table.get_mut(source_id).unwrap() { + InputSourceState::Pointer(pointer_input_state) => pointer_input_state, + _ => unreachable!(), + }; + pointer_input_state + } + + fn get_key_input_state_mut(&mut self, source_id: &str) -> &mut KeyInputState { + let session = self.session_mut().unwrap(); + let key_input_state = match session.input_state_table.get_mut(source_id).unwrap() { + InputSourceState::Key(key_input_state) => key_input_state, + _ => unreachable!(), + }; + key_input_state + } + // https://w3c.github.io/webdriver/#dfn-dispatch-a-pointerdown-action pub(crate) fn dispatch_pointerdown_action( &mut self, source_id: &str, action: &PointerDownAction, ) { - let session = self.session.as_mut().unwrap(); - - let pointer_input_state = match session.input_state_table.get_mut(source_id).unwrap() { - InputSourceState::Pointer(pointer_input_state) => pointer_input_state, - _ => unreachable!(), - }; + let webview_id = self.session().unwrap().webview_id; + let pointer_input_state = self.get_pointer_input_state_mut(source_id); if pointer_input_state.pressed.contains(&action.button) { return; } pointer_input_state.pressed.insert(action.button); - session.input_cancel_list.push(ActionSequence { - id: source_id.into(), - actions: ActionsType::Pointer { - parameters: PointerActionParameters { - pointer_type: match pointer_input_state.subtype { - PointerType::Mouse => PointerType::Mouse, - PointerType::Pen => PointerType::Pen, - PointerType::Touch => PointerType::Touch, - }, - }, - actions: vec![PointerActionItem::Pointer(PointerAction::Up( - PointerUpAction { - button: action.button, - ..Default::default() - }, - ))], - }, - }); - let cmd_msg = WebDriverCommandMsg::MouseButtonAction( - session.webview_id, + webview_id, MouseButtonAction::Down, action.button.into(), pointer_input_state.x as f32, @@ -319,39 +335,16 @@ impl Handler { // https://w3c.github.io/webdriver/#dfn-dispatch-a-pointerup-action pub(crate) fn dispatch_pointerup_action(&mut self, source_id: &str, action: &PointerUpAction) { - let session = self.session.as_mut().unwrap(); - - let pointer_input_state = match session.input_state_table.get_mut(source_id).unwrap() { - InputSourceState::Pointer(pointer_input_state) => pointer_input_state, - _ => unreachable!(), - }; + let webview_id = self.session().unwrap().webview_id; + let pointer_input_state = self.get_pointer_input_state_mut(source_id); if !pointer_input_state.pressed.contains(&action.button) { return; } pointer_input_state.pressed.remove(&action.button); - session.input_cancel_list.push(ActionSequence { - id: source_id.into(), - actions: ActionsType::Pointer { - parameters: PointerActionParameters { - pointer_type: match pointer_input_state.subtype { - PointerType::Mouse => PointerType::Mouse, - PointerType::Pen => PointerType::Pen, - PointerType::Touch => PointerType::Touch, - }, - }, - actions: vec![PointerActionItem::Pointer(PointerAction::Down( - PointerDownAction { - button: action.button, - ..Default::default() - }, - ))], - }, - }); - let cmd_msg = WebDriverCommandMsg::MouseButtonAction( - session.webview_id, + webview_id, MouseButtonAction::Up, action.button.into(), pointer_input_state.x as f32, @@ -432,12 +425,9 @@ impl Handler { target_y: f64, tick_start: Instant, ) { - let session = self.session.as_mut().unwrap(); - let pointer_input_state = match session.input_state_table.get_mut(source_id).unwrap() { - InputSourceState::Pointer(pointer_input_state) => pointer_input_state, - _ => unreachable!(), - }; - + let webview_id = self.session().unwrap().webview_id; + let constellation_chan = self.constellation_chan.clone(); + let pointer_input_state = self.get_pointer_input_state_mut(source_id); loop { // Step 1 let time_delta = tick_start.elapsed().as_millis(); @@ -469,10 +459,9 @@ impl Handler { // Step 7 if x != current_x || y != current_y { // Step 7.2 - let cmd_msg = - WebDriverCommandMsg::MouseMoveAction(session.webview_id, x as f32, y as f32); + let cmd_msg = WebDriverCommandMsg::MouseMoveAction(webview_id, x as f32, y as f32); //TODO: Need Synchronization here before updating `pointer_input_state` - self.constellation_chan + constellation_chan .send(EmbedderToConstellationMessage::WebDriverCommand(cmd_msg)) .unwrap(); // Step 7.3 @@ -567,7 +556,7 @@ impl Handler { mut curr_delta_y: i64, tick_start: Instant, ) { - let session = self.session.as_mut().unwrap(); + let session = self.session_mut().unwrap(); // Step 1 let time_delta = tick_start.elapsed().as_millis(); |