blob: 4b34bd2d885dd1326d7982ec5c70897983a4c51a [file] [log] [blame] [edit]
/* 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 core::f32;
use std::cell::{Cell, RefCell};
use std::mem;
use std::sync::Arc;
use app_units::Au;
use base::id::ScrollTreeNodeId;
use base::print_tree::PrintTree;
use compositing_traits::display_list::{
AxesScrollSensitivity, CompositorDisplayListInfo, ReferenceFrameNodeInfo, ScrollableNodeInfo,
SpatialTreeNodeInfo, StickyNodeInfo,
};
use embedder_traits::ViewportDetails;
use euclid::SideOffsets2D;
use euclid::default::{Point2D, Rect, Size2D};
use log::warn;
use malloc_size_of_derive::MallocSizeOf;
use servo_config::opts::DebugOptions;
use style::Zero;
use style::color::AbsoluteColor;
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::computed_values::text_decoration_style::T as TextDecorationStyle;
use style::values::computed::angle::Angle;
use style::values::computed::basic_shape::ClipPath;
use style::values::computed::{ClipRectOrAuto, Length, TextDecorationLine};
use style::values::generics::box_::Perspective;
use style::values::generics::transform::{self, GenericRotate, GenericScale, GenericTranslate};
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 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::{
BoxFragment, ContainingBlockManager, Fragment, FragmentFlags, FragmentTree,
PositioningFragment, SpecificLayoutInfo,
};
use crate::geom::{AuOrAuto, PhysicalRect, PhysicalSides};
use crate::style_ext::{ComputedValuesExt, TransformExt};
#[derive(Clone)]
pub(crate) struct ContainingBlock {
/// The SpatialId of the spatial node that contains the children
/// of this containing block.
scroll_node_id: ScrollTreeNodeId,
/// The size of the parent scroll frame of this containing block, used for resolving
/// sticky margins. If this is None, then this is a direct descendant of a reference
/// frame and sticky positioning isn't taken into account.
scroll_frame_size: Option<LayoutSize>,
/// The [`ClipId`] to use for the children of this containing block.
clip_id: ClipId,
/// The physical rect of this containing block.
rect: PhysicalRect<Au>,
}
impl ContainingBlock {
pub(crate) fn new(
rect: PhysicalRect<Au>,
scroll_node_id: ScrollTreeNodeId,
scroll_frame_size: Option<LayoutSize>,
clip_id: ClipId,
) -> Self {
ContainingBlock {
scroll_node_id,
scroll_frame_size,
clip_id,
rect,
}
}
pub(crate) fn new_replacing_rect(&self, rect: &PhysicalRect<Au>) -> Self {
ContainingBlock {
rect: *rect,
..*self
}
}
}
pub(crate) type ContainingBlockInfo<'a> = ContainingBlockManager<'a, ContainingBlock>;
#[derive(Clone, Copy, Debug, Eq, Ord, MallocSizeOf, PartialEq, PartialOrd)]
pub(crate) enum StackingContextSection {
OwnBackgroundsAndBorders,
DescendantBackgroundsAndBorders,
Foreground,
Outline,
}
#[derive(MallocSizeOf)]
pub(crate) struct StackingContextTree {
/// The root stacking context of this [`StackingContextTree`].
pub root_stacking_context: StackingContext,
/// 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_details: ViewportDetails,
pipeline_id: wr::PipelineId,
first_reflow: bool,
debug: &DebugOptions,
) -> Self {
let scrollable_overflow = fragment_tree.scrollable_overflow();
let scrollable_overflow = LayoutSize::from_untyped(Size2D::new(
scrollable_overflow.size.width.to_f32_px(),
scrollable_overflow.size.height.to_f32_px(),
));
let viewport_size = viewport_details.layout_size();
let compositor_info = CompositorDisplayListInfo::new(
viewport_details,
scrollable_overflow,
pipeline_id,
// This epoch is set when the WebRender display list is built. For now use a dummy value.
wr::Epoch(0),
fragment_tree.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,
root_scroll_node_id,
Some(viewport_size),
ClipId::INVALID,
);
let cb_for_fixed_descendants = ContainingBlock::new(
fragment_tree.initial_containing_block,
compositor_info.root_reference_frame_id,
None,
ClipId::INVALID,
);
// We need to specify all three containing blocks here, because absolute
// descdendants of the root cannot share the containing block we specify
// for fixed descendants. In this case, they need to have the spatial
// id of the root scroll frame, whereas fixed descendants need the
// spatial id of the root reference frame so that they do not scroll with
// page content.
let containing_block_info = ContainingBlockInfo {
for_non_absolute_descendants: &cb_for_non_fixed_descendants,
for_absolute_descendants: Some(&cb_for_non_fixed_descendants),
for_absolute_and_fixed_descendants: &cb_for_fixed_descendants,
};
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);
let text_decorations = Default::default();
for fragment in &fragment_tree.root_fragments {
fragment.build_stacking_context_tree(
&mut stacking_context_tree,
&containing_block_info,
&mut root_stacking_context,
StackingContextBuildMode::SkipHoisted,
&text_decorations,
);
}
root_stacking_context.sort();
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(
&mut self,
origin: LayoutPoint,
frame_origin_for_query: LayoutPoint,
parent_scroll_node_id: &ScrollTreeNodeId,
transform_style: wr::TransformStyle,
transform: LayoutTransform,
kind: wr::ReferenceFrameKind,
) -> ScrollTreeNodeId {
self.compositor_info.scroll_tree.add_scroll_tree_node(
Some(parent_scroll_node_id),
SpatialTreeNodeInfo::ReferenceFrame(ReferenceFrameNodeInfo {
origin,
frame_origin_for_query,
transform_style,
transform: transform.into(),
kind,
}),
)
}
fn define_scroll_frame(
&mut self,
parent_scroll_node_id: &ScrollTreeNodeId,
external_id: wr::ExternalScrollId,
content_rect: LayoutRect,
clip_rect: LayoutRect,
scroll_sensitivity: AxesScrollSensitivity,
) -> ScrollTreeNodeId {
self.compositor_info.scroll_tree.add_scroll_tree_node(
Some(parent_scroll_node_id),
SpatialTreeNodeInfo::Scroll(ScrollableNodeInfo {
external_id,
content_rect,
clip_rect,
scroll_sensitivity,
offset: LayoutVector2D::zero(),
offset_changed: Cell::new(false),
}),
)
}
fn define_sticky_frame(
&mut self,
parent_scroll_node_id: &ScrollTreeNodeId,
frame_rect: LayoutRect,
margins: SideOffsets2D<Option<f32>, LayoutPixel>,
vertical_offset_bounds: StickyOffsetBounds,
horizontal_offset_bounds: StickyOffsetBounds,
) -> ScrollTreeNodeId {
self.compositor_info.scroll_tree.add_scroll_tree_node(
Some(parent_scroll_node_id),
SpatialTreeNodeInfo::Sticky(StickyNodeInfo {
frame_rect,
margins,
vertical_offset_bounds,
horizontal_offset_bounds,
}),
)
}
}
/// The text decorations for a Fragment, collecting during [`StackingContextTree`] construction.
#[derive(Clone, Debug, MallocSizeOf)]
pub(crate) struct FragmentTextDecoration {
pub line: TextDecorationLine,
pub color: AbsoluteColor,
pub style: TextDecorationStyle,
}
/// A piece of content that directly belongs to a section of a stacking context.
///
/// This is generally part of a fragment, like its borders or foreground, but it
/// can also be a stacking container that needs to be painted in fragment order.
#[derive(MallocSizeOf)]
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_id: ClipId,
section: StackingContextSection,
containing_block: PhysicalRect<Au>,
fragment: Fragment,
is_hit_test_for_scrollable_overflow: bool,
is_collapsed_table_borders: bool,
#[conditional_malloc_size_of]
text_decorations: Arc<Vec<FragmentTextDecoration>>,
},
/// An index into [StackingContext::atomic_inline_stacking_containers].
///
/// There is no section field, because these are always in [StackingContextSection::Foreground].
AtomicInlineStackingContainer { index: usize },
}
impl StackingContextContent {
pub(crate) fn section(&self) -> StackingContextSection {
match self {
Self::Fragment { section, .. } => *section,
Self::AtomicInlineStackingContainer { .. } => StackingContextSection::Foreground,
}
}
fn build_display_list(
&self,
builder: &mut DisplayListBuilder,
inline_stacking_containers: &[StackingContext],
) {
match self {
Self::Fragment {
scroll_node_id,
reference_frame_scroll_node_id,
clip_id,
section,
containing_block,
fragment,
is_hit_test_for_scrollable_overflow,
is_collapsed_table_borders,
text_decorations,
} => {
builder.current_scroll_node_id = *scroll_node_id;
builder.current_reference_frame_scroll_node_id = *reference_frame_scroll_node_id;
builder.current_clip_id = *clip_id;
fragment.build_display_list(
builder,
containing_block,
*section,
*is_hit_test_for_scrollable_overflow,
*is_collapsed_table_borders,
text_decorations,
);
},
Self::AtomicInlineStackingContainer { index } => {
inline_stacking_containers[*index].build_display_list(builder);
},
}
}
}
#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq)]
pub(crate) enum StackingContextType {
RealStackingContext,
PositionedStackingContainer,
FloatStackingContainer,
AtomicInlineStackingContainer,
}
/// Either a stacking context or a stacking container, per the definitions in
/// <https://drafts.csswg.org/css-position-4/#painting-order>.
///
/// We use the term “real stacking context” in situations that call for a
/// stacking context but not a stacking container.
#[derive(MallocSizeOf)]
pub struct StackingContext {
/// The spatial id of this fragment. This is used to properly handle
/// things like preserve-3d.
scroll_tree_node_id: ScrollTreeNodeId,
/// The clip chain id of this stacking context if it has one. Used for filter clipping.
clip_id: Option<ClipId>,
/// 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,
/// The contents that need to be painted in fragment order.
pub(super) contents: Vec<StackingContextContent>,
/// Stacking contexts that need to be stolen by the parent stacking context
/// if this is a stacking container, that is, real stacking contexts and
/// positioned stacking containers (where ‘z-index’ is auto).
/// <https://drafts.csswg.org/css-position-4/#paint-a-stacking-container>
/// > To paint a stacking container, given a box root and a canvas canvas:
/// > 1. Paint a stacking context given root and canvas, treating root as
/// > if it created a new stacking context, but omitting any positioned
/// > descendants or descendants that actually create a stacking context
/// > (letting the parent stacking context paint them, instead).
pub(super) real_stacking_contexts_and_positioned_stacking_containers: Vec<StackingContext>,
/// Float stacking containers.
/// Separate from real_stacking_contexts_or_positioned_stacking_containers
/// because they should never be stolen by the parent stacking context.
/// <https://drafts.csswg.org/css-position-4/#paint-a-stacking-container>
pub(super) float_stacking_containers: Vec<StackingContext>,
/// Atomic inline stacking containers.
/// Separate from real_stacking_contexts_or_positioned_stacking_containers
/// because they should never be stolen by the parent stacking context, and
/// separate from float_stacking_containers so that [StackingContextContent]
/// can index into this vec to paint them in fragment order.
/// <https://drafts.csswg.org/css-position-4/#paint-a-stacking-container>
/// <https://drafts.csswg.org/css-position-4/#paint-a-box-in-a-line-box>
pub(super) atomic_inline_stacking_containers: Vec<StackingContext>,
/// Information gathered about the painting order, for [Self::debug_print].
debug_print_items: Option<RefCell<Vec<DebugPrintItem>>>,
}
/// Refers to one of the child contents or stacking contexts of a [StackingContext].
#[derive(Clone, Copy, MallocSizeOf)]
pub struct DebugPrintItem {
field: DebugPrintField,
index: usize,
}
/// Refers to one of the vecs of a [StackingContext].
#[derive(Clone, Copy, MallocSizeOf)]
pub enum DebugPrintField {
Contents,
RealStackingContextsAndPositionedStackingContainers,
FloatStackingContainers,
}
impl StackingContext {
fn create_descendant(
&self,
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<ClipId>` representation here. Just passing Some(ClipChainId::INVALID)
// leads to a crash.
let clip_id = match clip_id {
ClipId::INVALID => None,
clip_id => Some(clip_id),
};
Self {
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![],
float_stacking_containers: vec![],
atomic_inline_stacking_containers: vec![],
debug_print_items: self.debug_print_items.is_some().then(|| vec![].into()),
}
}
fn create_root(root_scroll_node_id: ScrollTreeNodeId, debug: &DebugOptions) -> Self {
Self {
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![],
float_stacking_containers: vec![],
atomic_inline_stacking_containers: vec![],
debug_print_items: debug.dump_stacking_context_tree.then(|| vec![].into()),
}
}
/// Add a child stacking context to this stacking context.
fn add_stacking_context(&mut self, stacking_context: StackingContext) {
match stacking_context.context_type {
StackingContextType::RealStackingContext => {
&mut self.real_stacking_contexts_and_positioned_stacking_containers
},
StackingContextType::PositionedStackingContainer => {
&mut self.real_stacking_contexts_and_positioned_stacking_containers
},
StackingContextType::FloatStackingContainer => &mut self.float_stacking_containers,
StackingContextType::AtomicInlineStackingContainer => {
&mut self.atomic_inline_stacking_containers
},
}
.push(stacking_context)
}
pub(crate) fn z_index(&self) -> i32 {
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) {
self.contents.sort_by_key(|a| a.section());
self.real_stacking_contexts_and_positioned_stacking_containers
.sort_by_key(|a| a.z_index());
debug_assert!(
self.real_stacking_contexts_and_positioned_stacking_containers
.iter()
.all(|c| matches!(
c.context_type,
StackingContextType::RealStackingContext |
StackingContextType::PositionedStackingContainer
))
);
debug_assert!(
self.float_stacking_containers
.iter()
.all(
|c| c.context_type == StackingContextType::FloatStackingContainer &&
c.z_index() == 0
)
);
debug_assert!(
self.atomic_inline_stacking_containers
.iter()
.all(
|c| c.context_type == StackingContextType::AtomicInlineStackingContainer &&
c.z_index() == 0
)
);
}
fn push_webrender_stacking_context_if_necessary(
&self,
builder: &mut DisplayListBuilder,
) -> bool {
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 &&
effects.mix_blend_mode == ComputedMixBlendMode::Normal &&
!style.has_effective_transform_or_perspective(FragmentFlags::empty()) &&
style.clone_clip_path() == ClipPath::None
{
return false;
}
// Create the filter pipeline.
let current_color = style.clone_color();
let mut filters: Vec<wr::FilterOp> = effects
.filter
.0
.iter()
.map(|filter| FilterToWebRender::to_webrender(filter, &current_color))
.collect();
if effects.opacity != 1.0 {
filters.push(wr::FilterOp::Opacity(
effects.opacity.into(),
effects.opacity,
));
}
// TODO(jdm): WebRender now requires us to create stacking context items
// with the IS_BLEND_CONTAINER flag enabled if any children
// of the stacking context have a blend mode applied.
// 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
spatial_id,
style.get_webrender_primitive_flags(),
clip_chain_id,
style.get_used_transform_style().to_webrender(),
effects.mix_blend_mode.to_webrender(),
&filters,
&[], // filter_datas
&[], // filter_primitives
wr::RasterSpace::Screen,
wr::StackingContextFlags::empty(),
None, // snapshot
);
true
}
/// <https://drafts.csswg.org/css-backgrounds/#special-backgrounds>
///
/// This is only called for the root `StackingContext`
pub(crate) fn build_canvas_background_display_list(
&self,
builder: &mut DisplayListBuilder,
fragment_tree: &crate::fragment_tree::FragmentTree,
) {
let Some(root_fragment) = fragment_tree.root_fragments.iter().find(|fragment| {
fragment
.base()
.is_some_and(|base| base.flags.intersects(FragmentFlags::IS_ROOT_ELEMENT))
}) else {
return;
};
let root_fragment = match root_fragment {
Fragment::Box(box_fragment) | Fragment::Float(box_fragment) => box_fragment,
_ => return,
}
.borrow();
let source_style = {
// > For documents whose root element is an HTML HTML element or an XHTML html element
// > [HTML]: if the computed value of background-image on the root element is none and its
// > background-color is transparent, user agents must instead propagate the computed
// > values of the background properties from that element’s first HTML BODY or XHTML body
// > child element.
if root_fragment.style.background_is_transparent() {
let body_fragment = fragment_tree.body_fragment();
builder.paint_body_background = body_fragment.is_none();
body_fragment
.map(|body_fragment| body_fragment.borrow().style.clone())
.unwrap_or(root_fragment.style.clone())
} else {
root_fragment.style.clone()
}
};
// This can happen if the root fragment does not have a `<body>` child (either because it is
// `display: none` or `display: contents`) or if the `<body>`'s background is transparent.
if source_style.background_is_transparent() {
return;
}
// The painting area is theoretically the infinite 2D plane,
// but we need a rectangle with finite coordinates.
//
// If the document is smaller than the viewport (and doesn’t scroll),
// we still want to paint the rest of the viewport.
// If it’s larger, we also want to paint areas reachable after scrolling.
let painting_area = fragment_tree
.initial_containing_block
.union(&fragment_tree.scrollable_overflow())
.to_webrender();
let background_color =
source_style.resolve_color(&source_style.get_background().background_color);
if background_color.alpha > 0.0 {
let common = builder.common_properties(painting_area, &source_style);
let color = super::rgba(background_color);
builder.wr().push_rect(&common, painting_area, color)
}
let mut fragment_builder = BuilderForBoxFragment::new(
&root_fragment,
&fragment_tree.initial_containing_block,
false, /* is_hit_test_for_scrollable_overflow */
false, /* is_collapsed_table_borders */
);
let painter = super::background::BackgroundPainter {
style: &source_style,
painting_area_override: Some(painting_area),
positioning_area_override: None,
};
fragment_builder.build_background_image(builder, &painter);
}
/// Build a display list from a a [`StackingContext`]. Note that this is the forward
/// version of the reversed stacking context walk algorithm in `hit_test.rs`. Any
/// changes made here should be reflected in the reverse version in that file.
pub(crate) fn build_display_list(&self, builder: &mut DisplayListBuilder) {
let pushed_context = self.push_webrender_stacking_context_if_necessary(builder);
// Properly order display items that make up a stacking context.
// “Steps” here refer to the steps in CSS 2.1 Appendix E.
// Note that “positioned descendants” is generalised to include all descendants that
// generate stacking contexts (csswg-drafts#2717), except in the phrase “any positioned
// descendants or descendants that actually create a stacking context”, where the term
// means positioned descendants that do not generate stacking contexts.
// Steps 1 and 2: Borders and background for the root
let mut contents = self.contents.iter().enumerate().peekable();
while contents.peek().is_some_and(|(_, child)| {
child.section() == StackingContextSection::OwnBackgroundsAndBorders
}) {
let (i, child) = contents.next().unwrap();
self.debug_push_print_item(DebugPrintField::Contents, i);
child.build_display_list(builder, &self.atomic_inline_stacking_containers);
}
// Step 3: Stacking contexts with negative ‘z-index’
let mut real_stacking_contexts_and_positioned_stacking_containers = self
.real_stacking_contexts_and_positioned_stacking_containers
.iter()
.enumerate()
.peekable();
while real_stacking_contexts_and_positioned_stacking_containers
.peek()
.is_some_and(|(_, child)| child.z_index() < 0)
{
let (i, child) = real_stacking_contexts_and_positioned_stacking_containers
.next()
.unwrap();
self.debug_push_print_item(
DebugPrintField::RealStackingContextsAndPositionedStackingContainers,
i,
);
child.build_display_list(builder);
}
// Step 4: Block backgrounds and borders
while contents.peek().is_some_and(|(_, child)| {
child.section() == StackingContextSection::DescendantBackgroundsAndBorders
}) {
let (i, child) = contents.next().unwrap();
self.debug_push_print_item(DebugPrintField::Contents, i);
child.build_display_list(builder, &self.atomic_inline_stacking_containers);
}
// Step 5: Float stacking containers
for (i, child) in self.float_stacking_containers.iter().enumerate() {
self.debug_push_print_item(DebugPrintField::FloatStackingContainers, i);
child.build_display_list(builder);
}
// Steps 6 and 7: Fragments and inline stacking containers
while contents
.peek()
.is_some_and(|(_, child)| child.section() == StackingContextSection::Foreground)
{
let (i, child) = contents.next().unwrap();
self.debug_push_print_item(DebugPrintField::Contents, i);
child.build_display_list(builder, &self.atomic_inline_stacking_containers);
}
// Steps 8 and 9: Stacking contexts with non-negative ‘z-index’, and
// positioned stacking containers (where ‘z-index’ is auto)
for (i, child) in real_stacking_contexts_and_positioned_stacking_containers {
self.debug_push_print_item(
DebugPrintField::RealStackingContextsAndPositionedStackingContainers,
i,
);
child.build_display_list(builder);
}
// Step 10: Outline
while contents
.peek()
.is_some_and(|(_, child)| child.section() == StackingContextSection::Outline)
{
let (i, child) = contents.next().unwrap();
self.debug_push_print_item(DebugPrintField::Contents, i);
child.build_display_list(builder, &self.atomic_inline_stacking_containers);
}
if pushed_context {
builder.wr().pop_stacking_context();
}
}
/// Store the fact that something was painted, if [Self::debug_print_items] is not None.
///
/// This is used to help reconstruct the original painting order in [Self::debug_print] without
/// duplicating our painting order logic, since that could fall out of sync with the real logic.
fn debug_push_print_item(&self, field: DebugPrintField, index: usize) {
if let Some(items) = self.debug_print_items.as_ref() {
items.borrow_mut().push(DebugPrintItem { field, index });
}
}
/// Print the stacking context tree.
pub fn debug_print(&self) {
if self.debug_print_items.is_none() {
warn!("failed to print stacking context tree: debug_print_items was None");
return;
}
let mut tree = PrintTree::new("Stacking context tree".to_owned());
self.debug_print_with_tree(&mut tree);
}
/// Print a subtree with the given [PrintTree], or panic if [Self::debug_print_items] is None.
fn debug_print_with_tree(&self, tree: &mut PrintTree) {
match self.context_type {
StackingContextType::RealStackingContext => {
tree.new_level(format!("{:?} z={}", self.context_type, self.z_index()));
},
StackingContextType::AtomicInlineStackingContainer => {
// do nothing; we print the heading with its index in DebugPrintField::Contents
},
_ => {
tree.new_level(format!("{:?}", self.context_type));
},
}
for DebugPrintItem { field, index } in
self.debug_print_items.as_ref().unwrap().borrow().iter()
{
match field {
DebugPrintField::Contents => match self.contents[*index] {
StackingContextContent::Fragment { section, .. } => {
tree.add_item(format!("{section:?}"));
},
StackingContextContent::AtomicInlineStackingContainer { index } => {
tree.new_level(format!("AtomicInlineStackingContainer #{index}"));
self.atomic_inline_stacking_containers[index].debug_print_with_tree(tree);
tree.end_level();
},
},
DebugPrintField::RealStackingContextsAndPositionedStackingContainers => {
self.real_stacking_contexts_and_positioned_stacking_containers[*index]
.debug_print_with_tree(tree);
},
DebugPrintField::FloatStackingContainers => {
self.float_stacking_containers[*index].debug_print_with_tree(tree);
},
}
}
match self.context_type {
StackingContextType::AtomicInlineStackingContainer => {
// do nothing; we print the heading with its index in DebugPrintField::Contents
},
_ => {
tree.end_level();
},
}
}
}
#[derive(PartialEq)]
pub(crate) enum StackingContextBuildMode {
IncludeHoisted,
SkipHoisted,
}
impl Fragment {
pub(crate) fn build_stacking_context_tree(
&self,
stacking_context_tree: &mut StackingContextTree,
containing_block_info: &ContainingBlockInfo,
stacking_context: &mut StackingContext,
mode: StackingContextBuildMode,
text_decorations: &Arc<Vec<FragmentTextDecoration>>,
) {
if self
.base()
.is_some_and(|base| base.flags.contains(FragmentFlags::IS_COLLAPSED))
{
return;
}
let containing_block = containing_block_info.get_containing_block_for_fragment(self);
let fragment_clone = self.clone();
match self {
Fragment::Box(fragment) | Fragment::Float(fragment) => {
let fragment = fragment.borrow();
if mode == StackingContextBuildMode::SkipHoisted &&
fragment.style.clone_position().is_absolutely_positioned()
{
return;
}
let text_decorations = match self {
Fragment::Float(..) => &Default::default(),
_ => text_decorations,
};
fragment.build_stacking_context_tree(
fragment_clone,
stacking_context_tree,
containing_block,
containing_block_info,
stacking_context,
text_decorations,
);
},
Fragment::AbsoluteOrFixedPositioned(fragment) => {
let shared_fragment = fragment.borrow();
let fragment_ref = match shared_fragment.fragment.as_ref() {
Some(fragment_ref) => fragment_ref,
None => unreachable!("Found hoisted box with missing fragment."),
};
fragment_ref.build_stacking_context_tree(
stacking_context_tree,
containing_block_info,
stacking_context,
StackingContextBuildMode::IncludeHoisted,
&Default::default(),
);
},
Fragment::Positioning(fragment) => {
let fragment = fragment.borrow();
fragment.build_stacking_context_tree(
stacking_context_tree,
containing_block,
containing_block_info,
stacking_context,
text_decorations,
);
},
Fragment::Text(_) | Fragment::Image(_) | Fragment::IFrame(_) => {
stacking_context
.contents
.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_id: containing_block.clip_id,
containing_block: containing_block.rect,
fragment: fragment_clone,
is_hit_test_for_scrollable_overflow: false,
is_collapsed_table_borders: false,
text_decorations: text_decorations.clone(),
});
},
}
}
}
struct ReferenceFrameData {
origin: crate::geom::PhysicalPoint<Au>,
transform: LayoutTransform,
kind: wr::ReferenceFrameKind,
}
struct ScrollFrameData {
scroll_tree_node_id: ScrollTreeNodeId,
scroll_frame_rect: LayoutRect,
}
struct OverflowFrameData {
clip_id: ClipId,
scroll_frame_data: Option<ScrollFrameData>,
}
impl BoxFragment {
fn get_stacking_context_type(&self) -> Option<StackingContextType> {
let flags = self.base.flags;
if self.style.establishes_stacking_context(flags) {
return Some(StackingContextType::RealStackingContext);
}
let box_style = &self.style.get_box();
if box_style.position != ComputedPosition::Static {
return Some(StackingContextType::PositionedStackingContainer);
}
if box_style.float != ComputedFloat::None {
return Some(StackingContextType::FloatStackingContainer);
}
// Flex and grid items are painted like inline blocks.
// <https://drafts.csswg.org/css-flexbox-1/#painting>
// <https://drafts.csswg.org/css-grid/#z-order>
if self.is_atomic_inline_level() || flags.contains(FragmentFlags::IS_FLEX_OR_GRID_ITEM) {
return Some(StackingContextType::AtomicInlineStackingContainer);
}
None
}
fn get_stacking_context_section(&self) -> StackingContextSection {
if self.get_stacking_context_type().is_some() {
return StackingContextSection::OwnBackgroundsAndBorders;
}
if self.style.get_box().display.outside() == DisplayOutside::Inline {
return StackingContextSection::Foreground;
}
StackingContextSection::DescendantBackgroundsAndBorders
}
fn build_stacking_context_tree(
&self,
fragment: Fragment,
stacking_context_tree: &mut StackingContextTree,
containing_block: &ContainingBlock,
containing_block_info: &ContainingBlockInfo,
parent_stacking_context: &mut StackingContext,
text_decorations: &Arc<Vec<FragmentTextDecoration>>,
) {
self.build_stacking_context_tree_maybe_creating_reference_frame(
fragment,
stacking_context_tree,
containing_block,
containing_block_info,
parent_stacking_context,
text_decorations,
);
}
fn build_stacking_context_tree_maybe_creating_reference_frame(
&self,
fragment: Fragment,
stacking_context_tree: &mut StackingContextTree,
containing_block: &ContainingBlock,
containing_block_info: &ContainingBlockInfo,
parent_stacking_context: &mut StackingContext,
text_decorations: &Arc<Vec<FragmentTextDecoration>>,
) {
let reference_frame_data =
match self.reference_frame_data_if_necessary(&containing_block.rect) {
Some(reference_frame_data) => reference_frame_data,
None => {
return self.build_stacking_context_tree_maybe_creating_stacking_context(
fragment,
stacking_context_tree,
containing_block,
containing_block_info,
parent_stacking_context,
text_decorations,
);
},
};
// <https://drafts.csswg.org/css-transforms/#transform-function-lists>
// > If a transform function causes the current transformation matrix of an object
// > to be non-invertible, the object and its content do not get displayed.
if !reference_frame_data.transform.is_invertible() {
self.clear_spatial_tree_node_including_descendants();
return;
}
let frame_origin_for_query = self.cumulative_border_box_rect().origin.to_webrender();
let new_spatial_id = stacking_context_tree.push_reference_frame(
reference_frame_data.origin.to_webrender(),
frame_origin_for_query,
&containing_block.scroll_node_id,
self.style.get_box().transform_style.to_webrender(),
reference_frame_data.transform,
reference_frame_data.kind,
);
// WebRender reference frames establish a new coordinate system at their
// origin (the border box of the fragment). We need to ensure that any
// coordinates we give to WebRender in this reference frame are relative
// to the fragment border box. We do this by adjusting the containing
// block origin. Note that the `for_absolute_descendants` and
// `for_all_absolute_and_fixed_descendants` properties are now bogus,
// but all fragments that establish reference frames also establish
// containing blocks for absolute and fixed descendants, so those
// properties will be replaced before recursing into children.
assert!(
self.style
.establishes_containing_block_for_all_descendants(self.base.flags)
);
let adjusted_containing_block = ContainingBlock::new(
containing_block
.rect
.translate(-reference_frame_data.origin.to_vector()),
new_spatial_id,
None,
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,
stacking_context_tree,
&adjusted_containing_block,
&new_containing_block_info,
parent_stacking_context,
text_decorations,
);
}
fn build_stacking_context_tree_maybe_creating_stacking_context(
&self,
fragment: Fragment,
stacking_context_tree: &mut StackingContextTree,
containing_block: &ContainingBlock,
containing_block_info: &ContainingBlockInfo,
parent_stacking_context: &mut StackingContext,
text_decorations: &Arc<Vec<FragmentTextDecoration>>,
) {
let context_type = match self.get_stacking_context_type() {
Some(context_type) => context_type,
None => {
self.build_stacking_context_tree_for_children(
fragment,
stacking_context_tree,
containing_block,
containing_block_info,
parent_stacking_context,
text_decorations,
);
return;
},
};
if context_type == StackingContextType::AtomicInlineStackingContainer {
// Push a dummy fragment that indicates when the new stacking context should be painted.
parent_stacking_context.contents.push(
StackingContextContent::AtomicInlineStackingContainer {
index: parent_stacking_context
.atomic_inline_stacking_containers
.len(),
},
);
}
// `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_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,
stacking_context_clip_id,
box_fragment,
context_type,
);
self.build_stacking_context_tree_for_children(
fragment,
stacking_context_tree,
containing_block,
containing_block_info,
&mut child_stacking_context,
text_decorations,
);
let mut stolen_children = vec![];
if context_type != StackingContextType::RealStackingContext {
stolen_children = mem::replace(
&mut child_stacking_context
.real_stacking_contexts_and_positioned_stacking_containers,
stolen_children,
);
}
child_stacking_context.sort();
parent_stacking_context.add_stacking_context(child_stacking_context);
parent_stacking_context
.real_stacking_contexts_and_positioned_stacking_containers
.append(&mut stolen_children);
}
fn build_stacking_context_tree_for_children(
&self,
fragment: Fragment,
stacking_context_tree: &mut StackingContextTree,
containing_block: &ContainingBlock,
containing_block_info: &ContainingBlockInfo,
stacking_context: &mut StackingContext,
text_decorations: &Arc<Vec<FragmentTextDecoration>>,
) {
let mut new_scroll_node_id = containing_block.scroll_node_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(
stacking_context_tree,
&new_scroll_node_id,
&containing_block.rect,
&new_scroll_frame_size,
) {
new_scroll_node_id = scroll_node_id;
}
if let Some(clip_id) = self.build_clip_frame_if_necessary(
stacking_context_tree,
&new_scroll_node_id,
new_clip_id,
&containing_block.rect,
) {
new_clip_id = clip_id;
}
if let Some(clip_id) = stacking_context_tree.clip_store.add_for_clip_path(
self.style.clone_clip_path(),
&new_scroll_node_id,
&new_clip_id,
BuilderForBoxFragment::new(
self,
&containing_block.rect,
false, /* is_hit_test_for_scrollable_overflow */
false, /* is_collapsed_table_borders */
),
) {
new_clip_id = clip_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
};
let mut add_fragment = |section| {
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_id: new_clip_id,
section,
containing_block: containing_block.rect,
fragment: fragment.clone(),
is_hit_test_for_scrollable_overflow: false,
is_collapsed_table_borders: false,
text_decorations: text_decorations.clone(),
});
};
let section = self.get_stacking_context_section();
add_fragment(section);
if !self.style.get_outline().outline_width.is_zero() {
add_fragment(StackingContextSection::Outline);
}
// Spatial tree node that will affect the transform of the fragment. Note that the next frame,
// scroll frame, does not affect the transform of the fragment but affect the transform of it
// children.
*self.spatial_tree_node.borrow_mut() = Some(new_scroll_node_id);
// 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(
stacking_context_tree,
&new_scroll_node_id,
new_clip_id,
&containing_block.rect,
) {
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());
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_id: new_clip_id,
section,
containing_block: containing_block.rect,
fragment: fragment.clone(),
is_hit_test_for_scrollable_overflow: true,
is_collapsed_table_borders: false,
text_decorations: text_decorations.clone(),
});
}
}
let padding_rect = self
.padding_rect()
.translate(containing_block.rect.origin.to_vector());
let content_rect = self
.content_rect
.translate(containing_block.rect.origin.to_vector());
let for_absolute_descendants = ContainingBlock::new(
padding_rect,
new_scroll_node_id,
new_scroll_frame_size,
new_clip_id,
);
let for_non_absolute_descendants = ContainingBlock::new(
content_rect,
new_scroll_node_id,
new_scroll_frame_size,
new_clip_id,
);
// 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 establishes_containing_block_for_all_descendants {
containing_block_info.new_for_absolute_and_fixed_descendants(
&for_non_absolute_descendants,
&for_absolute_descendants,
)
} else if establishes_containing_block_for_absolute_descendants {
containing_block_info.new_for_absolute_descendants(
&for_non_absolute_descendants,
&for_absolute_descendants,
)
} else {
containing_block_info.new_for_non_absolute_descendants(&for_non_absolute_descendants)
};
// Text decorations are not propagated to atomic inline-level descendants.
// From https://drafts.csswg.org/css2/#lining-striking-props:
// > Note that text decorations are not propagated to floating and absolutely
// > positioned descendants, nor to the contents of atomic inline-level descendants
// > such as inline blocks and inline tables.
let text_decorations = match self.is_atomic_inline_level() ||
self.base
.flags
.contains(FragmentFlags::IS_OUTSIDE_LIST_ITEM_MARKER)
{
true => &Default::default(),
false => text_decorations,
};
let new_text_decoration;
let text_decorations = match self.style.clone_text_decoration_line() {
TextDecorationLine::NONE => text_decorations,
line => {
let mut new_vector = (**text_decorations).clone();
let color = &self.style.get_inherited_text().color;
new_vector.push(FragmentTextDecoration {
line,
color: self
.style
.clone_text_decoration_color()
.resolve_to_absolute(color),
style: self.style.clone_text_decoration_style(),
});
new_text_decoration = Arc::new(new_vector);
&new_text_decoration
},
};
for child in &self.children {
child.build_stacking_context_tree(
stacking_context_tree,
&new_containing_block_info,
stacking_context,
StackingContextBuildMode::SkipHoisted,
text_decorations,
);
}
if matches!(&fragment, Fragment::Box(box_fragment) if matches!(
box_fragment.borrow().specific_layout_info(),
Some(SpecificLayoutInfo::TableGridWithCollapsedBorders(_))
)) {
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_id: new_clip_id,
section,
containing_block: containing_block.rect,
fragment: fragment.clone(),
is_hit_test_for_scrollable_overflow: false,
is_collapsed_table_borders: true,
text_decorations: text_decorations.clone(),
});
}
}
fn build_clip_frame_if_necessary(
&self,
stacking_context_tree: &mut StackingContextTree,
parent_scroll_node_id: &ScrollTreeNodeId,
parent_clip_id: ClipId,
containing_block_rect: &PhysicalRect<Au>,
) -> Option<ClipId> {
let position = self.style.get_box().position;
// https://drafts.csswg.org/css2/#clipping
// The clip property applies only to absolutely positioned elements
if !position.is_absolutely_positioned() {
return None;
}
// Only rectangles are supported for now.
let clip_rect = match self.style.get_effects().clip {
ClipRectOrAuto::Rect(rect) => rect,
_ => return None,
};
let border_rect = self.border_rect();
let clip_rect = clip_rect
.for_border_rect(border_rect)
.translate(containing_block_rect.origin.to_vector())
.to_webrender();
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,
stacking_context_tree: &mut StackingContextTree,
parent_scroll_node_id: &ScrollTreeNodeId,
parent_clip_id: ClipId,
containing_block_rect: &PhysicalRect<Au>,
) -> Option<OverflowFrameData> {
let overflow = self.style.effective_overflow(self.base.flags);
if overflow.x == ComputedOverflow::Visible && overflow.y == ComputedOverflow::Visible {
return None;
}
// Non-scrollable overflow path
if overflow.x == ComputedOverflow::Clip || overflow.y == ComputedOverflow::Clip {
// TODO: The spec allows `overflow-clip-rect` to specify which box edge to use
// as the overflow clip edge origin, but Stylo doesn't currently support that.
// It will need to be handled here, for now always use the padding rect.
let mut overflow_clip_rect = self
.padding_rect()
.translate(containing_block_rect.origin.to_vector())
.to_webrender();
// Adjust by the overflow clip margin.
// https://drafts.csswg.org/css-overflow-3/#overflow-clip-margin
let clip_margin = self.style.get_margin().overflow_clip_margin.px();
overflow_clip_rect = overflow_clip_rect.inflate(clip_margin, clip_margin);
// The clipping region only gets rounded corners if both axes have `overflow: clip`.
// https://drafts.csswg.org/css-overflow-3/#corner-clipping
let radii;
if overflow.x == ComputedOverflow::Clip && overflow.y == ComputedOverflow::Clip {
let builder = BuilderForBoxFragment::new(self, containing_block_rect, false, false);
radii = offset_radii(builder.border_radius, clip_margin);
} else if overflow.x != ComputedOverflow::Clip {
overflow_clip_rect.min.x = f32::MIN;
overflow_clip_rect.max.x = f32::MAX;
radii = BorderRadius::zero();
} else {
overflow_clip_rect.min.y = f32::MIN;
overflow_clip_rect.max.y = f32::MAX;
radii = BorderRadius::zero();
}
let clip_id = stacking_context_tree.clip_store.add(
radii,
overflow_clip_rect,
*parent_scroll_node_id,
parent_clip_id,
);
return Some(OverflowFrameData {
clip_id,
scroll_frame_data: None,
});
}
let scroll_frame_rect = self
.padding_rect()
.translate(containing_block_rect.origin.to_vector())
.to_webrender();
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_scroll_id = wr::ExternalScrollId(
tag.to_display_list_fragment_id(),
stacking_context_tree.compositor_info.pipeline_id,
);
let sensitivity = AxesScrollSensitivity {
x: overflow.x.into(),
y: overflow.y.into(),
};
let scroll_tree_node_id = stacking_context_tree.define_scroll_frame(
parent_scroll_node_id,
external_scroll_id,
self.scrollable_overflow().to_webrender(),
scroll_frame_rect,
sensitivity,
);
Some(OverflowFrameData {
clip_id,
scroll_frame_data: Some(ScrollFrameData {
scroll_tree_node_id,
scroll_frame_rect,
}),
})
}
fn build_sticky_frame_if_necessary(
&self,
stacking_context_tree: &mut StackingContextTree,
parent_scroll_node_id: &ScrollTreeNodeId,
containing_block_rect: &PhysicalRect<Au>,
scroll_frame_size: &Option<LayoutSize>,
) -> Option<ScrollTreeNodeId> {
if self.style.get_box().position != ComputedPosition::Sticky {
return None;
}
let scroll_frame_size_for_resolve = match scroll_frame_size {
Some(size) => size,
None => {
// This is a direct descendant of a reference frame.
&stacking_context_tree
.compositor_info
.viewport_details
.layout_size()
},
};
// Percentages sticky positions offsets are resovled against the size of the
// nearest scroll frame instead of the containing block like for other types
// of positioning.
let scroll_frame_height = Au::from_f32_px(scroll_frame_size_for_resolve.height);
let scroll_frame_width = Au::from_f32_px(scroll_frame_size_for_resolve.width);
let offsets = self.style.physical_box_offsets();
let offsets = PhysicalSides::<AuOrAuto>::new(
offsets.top.map(|v| v.to_used_value(scroll_frame_height)),
offsets.right.map(|v| v.to_used_value(scroll_frame_width)),
offsets.bottom.map(|v| v.to_used_value(scroll_frame_height)),
offsets.left.map(|v| v.to_used_value(scroll_frame_width)),
);
*self.resolved_sticky_insets.borrow_mut() = Some(offsets);
if scroll_frame_size.is_none() {
return None;
}
if offsets.top.is_auto() &&
offsets.right.is_auto() &&
offsets.bottom.is_auto() &&
offsets.left.is_auto()
{
return None;
}
let frame_rect = self
.border_rect()
.translate(containing_block_rect.origin.to_vector())
.to_webrender();
// Position:sticky elements are always restricted based on the size and position of their
// containing block.
let containing_block_rect = containing_block_rect.to_webrender();
// This is the minimum negative offset and then the maximum positive offset. We just
// specify every edge, but if the corresponding margin is None, that offset has no effect.
let vertical_offset_bounds = wr::StickyOffsetBounds::new(
containing_block_rect.min.y - frame_rect.min.y,
containing_block_rect.max.y - frame_rect.max.y,
);
let horizontal_offset_bounds = wr::StickyOffsetBounds::new(
containing_block_rect.min.x - frame_rect.min.x,
containing_block_rect.max.x - frame_rect.max.x,
);
let margins = SideOffsets2D::new(
offsets.top.non_auto().map(|v| v.to_f32_px()),
offsets.right.non_auto().map(|v| v.to_f32_px()),
offsets.bottom.non_auto().map(|v| v.to_f32_px()),
offsets.left.non_auto().map(|v| v.to_f32_px()),
);
let sticky_node_id = stacking_context_tree.define_sticky_frame(
parent_scroll_node_id,
frame_rect,
margins,
vertical_offset_bounds,
horizontal_offset_bounds,
);
Some(sticky_node_id)
}
/// Optionally returns the data for building a reference frame, without yet building it.
fn reference_frame_data_if_necessary(
&self,
containing_block_rect: &PhysicalRect<Au>,
) -> Option<ReferenceFrameData> {
if !self
.style
.has_effective_transform_or_perspective(self.base.flags)
{
return None;
}
let relative_border_rect = self.border_rect();
let border_rect = relative_border_rect.translate(containing_block_rect.origin.to_vector());
let untyped_border_rect = border_rect.to_untyped();
let transform = self.calculate_transform_matrix(&untyped_border_rect);
let perspective = self.calculate_perspective_matrix(&untyped_border_rect);
let (reference_frame_transform, reference_frame_kind) = match (transform, perspective) {
(None, Some(perspective)) => (
perspective,
wr::ReferenceFrameKind::Perspective {
scrolling_relative_to: None,
},
),
(Some(transform), None) => (
transform,
wr::ReferenceFrameKind::Transform {
is_2d_scale_translation: false,
should_snap: false,
paired_with_perspective: false,
},
),
(Some(transform), Some(perspective)) => (
perspective.then(&transform),
wr::ReferenceFrameKind::Perspective {
scrolling_relative_to: None,
},
),
(None, None) => unreachable!(),
};
Some(ReferenceFrameData {
origin: border_rect.origin,
transform: reference_frame_transform,
kind: reference_frame_kind,
})
}
/// Returns the 4D matrix representing this fragment's transform.
pub fn calculate_transform_matrix(&self, border_rect: &Rect<Au>) -> Option<LayoutTransform> {
let list = &self.style.get_box().transform;
let length_rect = au_rect_to_length_rect(border_rect);
// https://drafts.csswg.org/css-transforms-2/#individual-transforms
let rotate = match self.style.clone_rotate() {
GenericRotate::Rotate(angle) => (0., 0., 1., angle),
GenericRotate::Rotate3D(x, y, z, angle) => (x, y, z, angle),
GenericRotate::None => (0., 0., 1., Angle::zero()),
};
let scale = match self.style.clone_scale() {
GenericScale::Scale(sx, sy, sz) => (sx, sy, sz),
GenericScale::None => (1., 1., 1.),
};
let translation = match self.style.clone_translate() {
GenericTranslate::Translate(x, y, z) => LayoutTransform::translation(
x.resolve(length_rect.size.width).px(),
y.resolve(length_rect.size.height).px(),
z.px(),
),
GenericTranslate::None => LayoutTransform::identity(),
};
let angle = euclid::Angle::radians(rotate.3.radians());
let transform_base = list.to_transform_3d_matrix(Some(&length_rect)).ok()?;
let transform = LayoutTransform::from_untyped(&transform_base.0)
.then_rotate(rotate.0, rotate.1, rotate.2, angle)
.then_scale(scale.0, scale.1, scale.2)
.then(&translation);
let transform_origin = &self.style.get_box().transform_origin;
let transform_origin_x = transform_origin
.horizontal
.to_used_value(border_rect.size.width)
.to_f32_px();
let transform_origin_y = transform_origin
.vertical
.to_used_value(border_rect.size.height)
.to_f32_px();
let transform_origin_z = transform_origin.depth.px();
Some(transform.change_basis(transform_origin_x, transform_origin_y, transform_origin_z))
}
/// Returns the 4D matrix representing this fragment's perspective.
pub fn calculate_perspective_matrix(&self, border_rect: &Rect<Au>) -> Option<LayoutTransform> {
match self.style.get_box().perspective {
Perspective::Length(length) => {
let perspective_origin = &self.style.get_box().perspective_origin;
let perspective_origin = LayoutPoint::new(
perspective_origin
.horizontal
.percentage_relative_to(border_rect.size.width.into())
.px(),
perspective_origin
.vertical
.percentage_relative_to(border_rect.size.height.into())
.px(),
);
let perspective_matrix = LayoutTransform::from_untyped(
&transform::create_perspective_matrix(length.px()),
);
Some(perspective_matrix.change_basis(
perspective_origin.x,
perspective_origin.y,
0.0,
))
},
Perspective::None => None,
}
}
fn clear_spatial_tree_node_including_descendants(&self) {
fn assign_spatial_tree_node_on_fragments(fragments: &[Fragment]) {
for fragment in fragments.iter() {
match fragment {
Fragment::Box(box_fragment) | Fragment::Float(box_fragment) => {
box_fragment
.borrow()
.clear_spatial_tree_node_including_descendants();
},
Fragment::Positioning(positioning_fragment) => {
assign_spatial_tree_node_on_fragments(
&positioning_fragment.borrow().children,
);
},
_ => {},
}
}
}
*self.spatial_tree_node.borrow_mut() = None;
assign_spatial_tree_node_on_fragments(&self.children);
}
}
impl PositioningFragment {
fn build_stacking_context_tree(
&self,
stacking_context_tree: &mut StackingContextTree,
containing_block: &ContainingBlock,
containing_block_info: &ContainingBlockInfo,
stacking_context: &mut StackingContext,
text_decorations: &Arc<Vec<FragmentTextDecoration>>,
) {
let rect = self
.rect
.translate(containing_block.rect.origin.to_vector());
let new_containing_block = containing_block.new_replacing_rect(&rect);
let new_containing_block_info =
containing_block_info.new_for_non_absolute_descendants(&new_containing_block);
for child in &self.children {
child.build_stacking_context_tree(
stacking_context_tree,
&new_containing_block_info,
stacking_context,
StackingContextBuildMode::SkipHoisted,
text_decorations,
);
}
}
}
pub fn au_rect_to_length_rect(rect: &Rect<Au>) -> Rect<Length> {
Rect::new(
Point2D::new(rect.origin.x.into(), rect.origin.y.into()),
Size2D::new(rect.size.width.into(), rect.size.height.into()),
)
}