diff options
author | Martin Robinson <mrobinson@igalia.com> | 2024-04-15 22:24:37 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-04-15 20:24:37 +0000 |
commit | 8bcb316c92d680d4873152e5ec15a63bb4f33220 (patch) | |
tree | ae65daeb401b6c7bbc7655178a179a55dfeccac3 /components | |
parent | f3790415974bd1318ff2c2a66ab9b169c5b4c2fe (diff) | |
download | servo-8bcb316c92d680d4873152e5ec15a63bb4f33220.tar.gz servo-8bcb316c92d680d4873152e5ec15a63bb4f33220.zip |
layout: Add support for `background-attachment: fixed` (#32068)
Co-authored-by: Oriol Brufau <obrufau@igalia.com>
Diffstat (limited to 'components')
-rw-r--r-- | components/layout_2020/display_list/background.rs | 124 | ||||
-rw-r--r-- | components/layout_2020/display_list/mod.rs | 144 | ||||
-rw-r--r-- | components/layout_2020/display_list/stacking_context.rs | 36 |
3 files changed, 217 insertions, 87 deletions
diff --git a/components/layout_2020/display_list/background.rs b/components/layout_2020/display_list/background.rs index 649662ab083..c6608d4e09a 100644 --- a/components/layout_2020/display_list/background.rs +++ b/components/layout_2020/display_list/background.rs @@ -3,7 +3,8 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ use app_units::Au; -use euclid::{Size2D, Vector2D}; +use euclid::{Point2D, Size2D, Vector2D}; +use style::computed_values::background_attachment::SingleComputedValue as BackgroundAttachment; use style::computed_values::background_clip::single_value::T as Clip; use style::computed_values::background_origin::single_value::T as Origin; use style::properties::ComputedValues; @@ -13,6 +14,8 @@ use style::values::specified::background::{ BackgroundRepeat as RepeatXY, BackgroundRepeatKeyword as Repeat, }; use webrender_api::{self as wr, units}; +use wr::units::LayoutSize; +use wr::ClipChainId; use crate::replaced::IntrinsicSizes; @@ -43,49 +46,119 @@ pub(super) struct BackgroundPainter<'a> { } impl<'a> BackgroundPainter<'a> { + /// Get the painting area for this background, which is the actual rectangle in the + /// current coordinate system that the background will be painted. pub(super) fn painting_area( &self, fragment_builder: &'a super::BuilderForBoxFragment, builder: &mut super::DisplayListBuilder, layer_index: usize, - ) -> (&units::LayoutRect, wr::CommonItemProperties) { + ) -> units::LayoutRect { let fb = fragment_builder; - let (painting_area, clip) = match self.painting_area_override { - Some(ref painting_area) => (painting_area, None), - None if self.positioning_area_override.is_none() => { - let b = self.style.get_background(); - match get_cyclic(&b.background_clip.0, layer_index) { - Clip::ContentBox => (fb.content_rect(), fb.content_edge_clip(builder)), - Clip::PaddingBox => (fb.padding_rect(), fb.padding_edge_clip(builder)), - Clip::BorderBox => (&fb.border_rect, fb.border_edge_clip(builder)), - } - }, - None => (&fb.border_rect, fb.border_edge_clip(builder)), - }; + if let Some(painting_area_override) = self.painting_area_override.as_ref() { + return *painting_area_override; + } + if self.positioning_area_override.is_some() { + return fb.border_rect; + } + + let background = self.style.get_background(); + if &BackgroundAttachment::Fixed == + get_cyclic(&background.background_attachment.0, layer_index) + { + let viewport_size = builder.display_list.compositor_info.viewport_size; + return units::LayoutRect::from_origin_and_size(Point2D::origin(), viewport_size); + } + + match get_cyclic(&background.background_clip.0, layer_index) { + Clip::ContentBox => *fragment_builder.content_rect(), + Clip::PaddingBox => *fragment_builder.padding_rect(), + Clip::BorderBox => fragment_builder.border_rect, + } + } + + fn clip( + &self, + fragment_builder: &'a super::BuilderForBoxFragment, + builder: &mut super::DisplayListBuilder, + layer_index: usize, + ) -> Option<ClipChainId> { + if self.painting_area_override.is_some() { + return None; + } + + if self.positioning_area_override.is_some() { + return fragment_builder.border_edge_clip(builder, false); + } // The 'backgound-clip' property maps directly to `clip_rect` in `CommonItemProperties`: - let mut common = builder.common_properties(*painting_area, &fb.fragment.style); + let background = self.style.get_background(); + let force_clip_creation = get_cyclic(&background.background_attachment.0, layer_index) == + &BackgroundAttachment::Fixed; + match get_cyclic(&background.background_clip.0, layer_index) { + Clip::ContentBox => fragment_builder.content_edge_clip(builder, force_clip_creation), + Clip::PaddingBox => fragment_builder.padding_edge_clip(builder, force_clip_creation), + Clip::BorderBox => fragment_builder.border_edge_clip(builder, force_clip_creation), + } + } + + /// Get the [`wr::CommonItemProperties`] for this background. This includes any clipping + /// established by border radii as well as special clipping and spatial node assignment + /// necessary for `background-attachment`. + pub(super) fn common_properties( + &self, + fragment_builder: &'a super::BuilderForBoxFragment, + builder: &mut super::DisplayListBuilder, + layer_index: usize, + painting_area: units::LayoutRect, + ) -> wr::CommonItemProperties { + let clip = self.clip(fragment_builder, builder, layer_index); + let style = &fragment_builder.fragment.style; + let mut common = builder.common_properties(painting_area, style); if let Some(clip_chain_id) = clip { common.clip_chain_id = clip_chain_id; } - (painting_area, common) + 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 } + /// Get the positioning area of the background which is the rectangle that defines where + /// the origin of the background content is, regardless of where the background is actual + /// painted. pub(super) fn positioning_area( &self, fragment_builder: &'a super::BuilderForBoxFragment, layer_index: usize, - ) -> &units::LayoutRect { - self.positioning_area_override.as_ref().unwrap_or_else(|| { - match get_cyclic( + ) -> units::LayoutRect { + if let Some(positioning_area_override) = self.positioning_area_override { + return positioning_area_override; + } + + match get_cyclic( + &self.style.get_background().background_attachment.0, + layer_index, + ) { + BackgroundAttachment::Scroll => match get_cyclic( &self.style.get_background().background_origin.0, layer_index, ) { - Origin::ContentBox => fragment_builder.content_rect(), - Origin::PaddingBox => fragment_builder.padding_rect(), - Origin::BorderBox => &fragment_builder.border_rect, - } - }) + Origin::ContentBox => *fragment_builder.content_rect(), + Origin::PaddingBox => *fragment_builder.padding_rect(), + Origin::BorderBox => fragment_builder.border_rect, + }, + BackgroundAttachment::Fixed => { + // This isn't the viewport size because that rects larger than the viewport might be + // transformed down into areas smaller than the viewport. + units::LayoutRect::from_origin_and_size( + Point2D::origin(), + LayoutSize::new(f32::MAX, f32::MAX), + ) + }, + } } } @@ -96,8 +169,9 @@ pub(super) fn layout_layer( layer_index: usize, intrinsic: IntrinsicSizes, ) -> Option<BackgroundLayer> { - let (painting_area, common) = painter.painting_area(fragment_builder, builder, layer_index); + let painting_area = painter.painting_area(fragment_builder, builder, layer_index); let positioning_area = painter.positioning_area(fragment_builder, layer_index); + let common = painter.common_properties(fragment_builder, builder, layer_index, painting_area); // https://drafts.csswg.org/css-backgrounds/#background-size enum ContainOrCover { diff --git a/components/layout_2020/display_list/mod.rs b/components/layout_2020/display_list/mod.rs index 19b60ba1691..bfa8e9c266f 100644 --- a/components/layout_2020/display_list/mod.rs +++ b/components/layout_2020/display_list/mod.rs @@ -2,7 +2,7 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -use std::cell::OnceCell; +use std::cell::{OnceCell, RefCell}; use std::sync::Arc; use embedder_traits::Cursor; @@ -122,6 +122,11 @@ pub(crate) struct DisplayListBuilder<'a> { /// list building functions. current_scroll_node_id: ScrollTreeNodeId, + /// The current [ScrollNodeTreeId] for this [DisplayListBuilder]. This is necessary in addition + /// to the [Self::current_scroll_node_id], because some pieces of fragments as backgrounds with + /// `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 /// only passing the builder instead passing the containing /// [stacking_context::StackingContextContent::Fragment] as an argument to display @@ -160,6 +165,7 @@ impl DisplayList { ) -> (FnvHashMap<BrowsingContextId, Size2D<f32, CSSPixel>>, bool) { 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, element_for_canvas_background: fragment_tree.canvas_background.from_element, is_contentful: false, @@ -473,9 +479,9 @@ struct BuilderForBoxFragment<'a> { padding_rect: OnceCell<units::LayoutRect>, content_rect: OnceCell<units::LayoutRect>, border_radius: wr::BorderRadius, - border_edge_clip_chain_id: OnceCell<Option<ClipChainId>>, - padding_edge_clip_chain_id: OnceCell<Option<ClipChainId>>, - content_edge_clip_chain_id: OnceCell<Option<ClipChainId>>, + border_edge_clip_chain_id: RefCell<Option<ClipChainId>>, + padding_edge_clip_chain_id: RefCell<Option<ClipChainId>>, + content_edge_clip_chain_id: RefCell<Option<ClipChainId>>, } impl<'a> BuilderForBoxFragment<'a> { @@ -530,9 +536,9 @@ impl<'a> BuilderForBoxFragment<'a> { border_radius, padding_rect: OnceCell::new(), content_rect: OnceCell::new(), - border_edge_clip_chain_id: OnceCell::new(), - padding_edge_clip_chain_id: OnceCell::new(), - content_edge_clip_chain_id: OnceCell::new(), + border_edge_clip_chain_id: RefCell::new(None), + padding_edge_clip_chain_id: RefCell::new(None), + content_edge_clip_chain_id: RefCell::new(None), } } @@ -556,41 +562,66 @@ impl<'a> BuilderForBoxFragment<'a> { }) } - fn border_edge_clip(&self, builder: &mut DisplayListBuilder) -> Option<ClipChainId> { - *self - .border_edge_clip_chain_id - .get_or_init(|| clip_for_radii(self.border_radius, self.border_rect, builder)) + fn border_edge_clip( + &self, + builder: &mut DisplayListBuilder, + force_clip_creation: bool, + ) -> Option<ClipChainId> { + if let Some(clip) = *self.border_edge_clip_chain_id.borrow() { + return Some(clip); + } + + let maybe_clip = create_clip_chain( + self.border_radius, + self.border_rect, + builder, + force_clip_creation, + ); + *self.border_edge_clip_chain_id.borrow_mut() = maybe_clip; + maybe_clip } - fn padding_edge_clip(&self, builder: &mut DisplayListBuilder) -> Option<ClipChainId> { - *self.padding_edge_clip_chain_id.get_or_init(|| { - clip_for_radii( - inner_radii( - self.border_radius, - self.fragment - .border - .to_physical(self.fragment.style.writing_mode) - .to_webrender(), - ), - *self.padding_rect(), - builder, - ) - }) + fn padding_edge_clip( + &self, + builder: &mut DisplayListBuilder, + force_clip_creation: bool, + ) -> Option<ClipChainId> { + if let Some(clip) = *self.padding_edge_clip_chain_id.borrow() { + return Some(clip); + } + + let radii = inner_radii( + self.border_radius, + self.fragment + .border + .to_physical(self.fragment.style.writing_mode) + .to_webrender(), + ); + let maybe_clip = + create_clip_chain(radii, *self.padding_rect(), builder, force_clip_creation); + *self.padding_edge_clip_chain_id.borrow_mut() = maybe_clip; + maybe_clip } - fn content_edge_clip(&self, builder: &mut DisplayListBuilder) -> Option<ClipChainId> { - *self.content_edge_clip_chain_id.get_or_init(|| { - clip_for_radii( - inner_radii( - self.border_radius, - (&self.fragment.border + &self.fragment.padding) - .to_physical(self.fragment.style.writing_mode) - .to_webrender(), - ), - *self.content_rect(), - builder, - ) - }) + fn content_edge_clip( + &self, + builder: &mut DisplayListBuilder, + force_clip_creation: bool, + ) -> Option<ClipChainId> { + if let Some(clip) = *self.content_edge_clip_chain_id.borrow() { + return Some(clip); + } + + let radii = inner_radii( + self.border_radius, + (&self.fragment.border + &self.fragment.padding) + .to_physical(self.fragment.style.writing_mode) + .to_webrender(), + ); + let maybe_clip = + create_clip_chain(radii, *self.content_rect(), builder, force_clip_creation); + *self.content_edge_clip_chain_id.borrow_mut() = maybe_clip; + maybe_clip } fn build(&mut self, builder: &mut DisplayListBuilder, section: StackingContextSection) { @@ -616,7 +647,7 @@ impl<'a> BuilderForBoxFragment<'a> { }; let mut common = builder.common_properties(self.border_rect, &self.fragment.style); - if let Some(clip_chain_id) = self.border_edge_clip(builder) { + if let Some(clip_chain_id) = self.border_edge_clip(builder, false) { common.clip_chain_id = clip_chain_id; } builder.wr().push_hit_test( @@ -640,10 +671,11 @@ impl<'a> BuilderForBoxFragment<'a> { // “The background color is clipped according to the background-clip // value associated with the bottom-most background image layer.” let layer_index = b.background_image.0.len() - 1; - let (bounds, common) = painter.painting_area(self, builder, layer_index); + let bounds = painter.painting_area(self, builder, layer_index); + let common = painter.common_properties(self, builder, layer_index, bounds); builder .wr() - .push_rect(&common, *bounds, rgba(background_color)) + .push_rect(&common, bounds, rgba(background_color)) } self.build_background_image(builder, painter); @@ -1044,28 +1076,34 @@ fn offset_radii(mut radii: wr::BorderRadius, offset: f32) -> wr::BorderRadius { radii } -fn clip_for_radii( +fn create_clip_chain( radii: wr::BorderRadius, rect: units::LayoutRect, builder: &mut DisplayListBuilder, + force_clip_creation: bool, ) -> Option<ClipChainId> { - if radii.is_zero() { - None + 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 { - let spatial_id = builder.current_scroll_node_id.spatial_id; - let parent_clip_chain_id = builder.current_clip_chain_id; - let new_clip_id = builder.wr().define_clip_rounded_rect( + 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]), ) - } + }; + + Some( + builder + .display_list + .define_clip_chain(parent_clip_chain_id, [new_clip_id]), + ) } diff --git a/components/layout_2020/display_list/stacking_context.rs b/components/layout_2020/display_list/stacking_context.rs index 504725ae7c5..407d2fb4adc 100644 --- a/components/layout_2020/display_list/stacking_context.rs +++ b/components/layout_2020/display_list/stacking_context.rs @@ -21,6 +21,7 @@ use style::values::computed::{ClipRectOrAuto, Length}; use style::values::generics::box_::Perspective; use style::values::generics::transform; use style::values::specified::box_::DisplayOutside; +use style::Zero; use webrender_api as wr; use webrender_api::units::{LayoutPoint, LayoutRect, LayoutTransform, LayoutVector2D}; use wr::units::{LayoutPixel, LayoutSize}; @@ -242,6 +243,7 @@ pub(crate) enum StackingContextContent { /// A fragment that does not generate a stacking context or stacking container. Fragment { scroll_node_id: ScrollTreeNodeId, + reference_frame_scroll_node_id: ScrollTreeNodeId, clip_chain_id: wr::ClipChainId, section: StackingContextSection, containing_block: PhysicalRect<Length>, @@ -270,12 +272,14 @@ impl StackingContextContent { match self { Self::Fragment { scroll_node_id, + reference_frame_scroll_node_id, clip_chain_id, section, containing_block, fragment, } => { 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; fragment .borrow() @@ -879,6 +883,9 @@ impl Fragment { .push(StackingContextContent::Fragment { section: StackingContextSection::Foreground, scroll_node_id: containing_block.scroll_node_id, + reference_frame_scroll_node_id: containing_block_info + .for_absolute_and_fixed_descendants + .scroll_node_id, clip_chain_id: containing_block.clip_chain_id, containing_block: containing_block.rect, fragment: fragment_ref.clone(), @@ -1104,21 +1111,38 @@ impl BoxFragment { new_clip_chain_id = clip_chain_id; } + let establishes_containing_block_for_all_descendants = self + .style + .establishes_containing_block_for_all_descendants(self.base.flags); + let establishes_containing_block_for_absolute_descendants = self + .style + .establishes_containing_block_for_absolute_descendants(self.base.flags); + + let reference_frame_scroll_node_id_for_fragments = + if establishes_containing_block_for_all_descendants { + new_scroll_node_id + } else { + containing_block_info + .for_absolute_and_fixed_descendants + .scroll_node_id + }; stacking_context .contents .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, section: self.get_stacking_context_section(), containing_block: containing_block.rect, fragment: fragment.clone(), }); - use style::Zero; + if !self.style.get_outline().outline_width.is_zero() { stacking_context .contents .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, section: StackingContextSection::Outline, containing_block: containing_block.rect, @@ -1166,18 +1190,12 @@ impl BoxFragment { // Create a new `ContainingBlockInfo` for descendants depending on // whether or not this fragment establishes a containing block for // absolute and fixed descendants. - let new_containing_block_info = if self - .style - .establishes_containing_block_for_all_descendants(self.base.flags) - { + let new_containing_block_info = if establishes_containing_block_for_all_descendants { containing_block_info.new_for_absolute_and_fixed_descendants( &for_non_absolute_descendants, &for_absolute_descendants, ) - } else if self - .style - .establishes_containing_block_for_absolute_descendants(self.base.flags) - { + } else if establishes_containing_block_for_absolute_descendants { containing_block_info.new_for_absolute_descendants( &for_non_absolute_descendants, &for_absolute_descendants, |