From 4a2787b974833fe4957fa8994700d30916bf3c6e Mon Sep 17 00:00:00 2001 From: Martin Robinson Date: Thu, 13 Feb 2020 17:22:22 +0100 Subject: [PATCH] Add initial stacking context paint order support to layout_2020 This adds very rudimentary support for paint order in stacking context. In particular z-index is now handled properly, apart from issues with hoisted fragments. --- components/layout_2020/display_list/mod.rs | 2 +- .../display_list/stacking_context.rs | 252 ++++++++++++++++-- components/layout_2020/flow/root.rs | 4 +- .../properties/longhands/position.mako.rs | 1 - .../css/CSS2/zindex/z-index-001.xht.ini | 2 - .../css/CSS2/zindex/z-index-002.xht.ini | 2 - .../css/CSS2/zindex/z-index-003.xht.ini | 2 - .../css/CSS2/zindex/z-index-007.xht.ini | 2 - .../css/CSS2/zindex/z-index-008.xht.ini | 2 - .../css/CSS2/zindex/z-index-009.xht.ini | 2 - .../css/CSS2/zindex/z-index-010.xht.ini | 2 - .../css/CSS2/zindex/z-index-011.xht.ini | 2 - .../css/CSS2/zindex/z-index-012.xht.ini | 2 - .../css/CSS2/zindex/z-index-014.xht.ini | 2 - .../CSS2/zindex/z-index-dynamic-001.xht.ini | 2 - ...css3-background-origin-border-box.html.ini | 2 - .../position-sticky-stacking-context.html.ini | 2 + .../css/opacity_stacking_context_a.html.ini | 2 + ...osition_relative_painting_order_a.html.ini | 2 - ...ition_relative_stacking_context_a.html.ini | 2 - .../css/transform_stacking_context_a.html.ini | 2 + 21 files changed, 235 insertions(+), 58 deletions(-) delete mode 100644 tests/wpt/metadata-layout-2020/css/CSS2/zindex/z-index-001.xht.ini delete mode 100644 tests/wpt/metadata-layout-2020/css/CSS2/zindex/z-index-002.xht.ini delete mode 100644 tests/wpt/metadata-layout-2020/css/CSS2/zindex/z-index-003.xht.ini delete mode 100644 tests/wpt/metadata-layout-2020/css/CSS2/zindex/z-index-007.xht.ini delete mode 100644 tests/wpt/metadata-layout-2020/css/CSS2/zindex/z-index-008.xht.ini delete mode 100644 tests/wpt/metadata-layout-2020/css/CSS2/zindex/z-index-009.xht.ini delete mode 100644 tests/wpt/metadata-layout-2020/css/CSS2/zindex/z-index-010.xht.ini delete mode 100644 tests/wpt/metadata-layout-2020/css/CSS2/zindex/z-index-011.xht.ini delete mode 100644 tests/wpt/metadata-layout-2020/css/CSS2/zindex/z-index-012.xht.ini delete mode 100644 tests/wpt/metadata-layout-2020/css/CSS2/zindex/z-index-014.xht.ini delete mode 100644 tests/wpt/metadata-layout-2020/css/CSS2/zindex/z-index-dynamic-001.xht.ini delete mode 100644 tests/wpt/metadata-layout-2020/css/css-backgrounds/css3-background-origin-border-box.html.ini create mode 100644 tests/wpt/mozilla/meta-layout-2020/css/css-position-3/position-sticky-stacking-context.html.ini create mode 100644 tests/wpt/mozilla/meta-layout-2020/css/opacity_stacking_context_a.html.ini delete mode 100644 tests/wpt/mozilla/meta-layout-2020/css/position_relative_painting_order_a.html.ini delete mode 100644 tests/wpt/mozilla/meta-layout-2020/css/position_relative_stacking_context_a.html.ini create mode 100644 tests/wpt/mozilla/meta-layout-2020/css/transform_stacking_context_a.html.ini diff --git a/components/layout_2020/display_list/mod.rs b/components/layout_2020/display_list/mod.rs index 040c9aed806b..eecedd793483 100644 --- a/components/layout_2020/display_list/mod.rs +++ b/components/layout_2020/display_list/mod.rs @@ -21,7 +21,7 @@ use webrender_api::{self as wr, units}; mod background; mod gradient; -mod stacking_context; +pub mod stacking_context; #[derive(Clone, Copy)] pub struct WebRenderImageInfo { diff --git a/components/layout_2020/display_list/stacking_context.rs b/components/layout_2020/display_list/stacking_context.rs index ee73d4afa1c3..7715a2f198de 100644 --- a/components/layout_2020/display_list/stacking_context.rs +++ b/components/layout_2020/display_list/stacking_context.rs @@ -6,9 +6,13 @@ use crate::display_list::DisplayListBuilder; use crate::fragments::{AnonymousFragment, BoxFragment, Fragment}; use crate::geom::{PhysicalRect, ToWebRender}; use gfx_traits::{combine_id_with_fragment_type, FragmentType}; -use std::default::Default; +use std::cmp::Ordering; +use std::mem; +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::transform_style::T as ComputedTransformStyle; use style::values::computed::Length; use webrender_api::units::LayoutVector2D; use webrender_api::{ExternalScrollId, ScrollSensitivity, SpaceAndClipInfo, SpatialId}; @@ -19,9 +23,96 @@ pub(crate) struct StackingContextFragment<'a> { fragment: &'a Fragment, } -#[derive(Default)] +#[derive(Clone, Copy, Eq, PartialEq)] +pub(crate) enum StackingContextType { + Real, + PseudoPositioned, + PseudoFloat, +} + pub(crate) struct StackingContext<'a> { + /// The type of this StackingContext. Used for collecting and sorting. + context_type: StackingContextType, + + /// The `z-index` for this stacking context. + pub z_index: i32, + + /// Fragments that make up the content of this stacking context. fragments: Vec>, + + /// All non-float stacking context and pseudo stacking context children + /// of this stacking context. + stacking_contexts: Vec>, + + /// All float pseudo stacking context children of this stacking context. + float_stacking_contexts: Vec>, +} + +impl<'a> StackingContext<'a> { + pub(crate) fn new(context_type: StackingContextType, z_index: i32) -> Self { + Self { + context_type, + z_index, + fragments: vec![], + stacking_contexts: vec![], + float_stacking_contexts: vec![], + } + } + + pub(crate) fn sort_stacking_contexts(&mut self) { + self.stacking_contexts.sort_by(|a, b| { + if a.z_index != 0 || b.z_index != 0 { + return a.z_index.cmp(&b.z_index); + } + + match (a.context_type, b.context_type) { + (StackingContextType::PseudoFloat, StackingContextType::PseudoFloat) => { + Ordering::Equal + }, + (StackingContextType::PseudoFloat, _) => Ordering::Less, + (_, StackingContextType::PseudoFloat) => Ordering::Greater, + (_, _) => Ordering::Equal, + } + }); + } + + pub(crate) fn build_display_list(&'a self, builder: &'a mut DisplayListBuilder) { + // Properly order display items that make up a stacking context. "Steps" here + // refer to the steps in CSS 2.1 Appendix E. + // + // TODO(mrobinson): The fragment content of the stacking context needs to be + // organized or sorted into the different sections according to the appropriate + // paint order. + + // Step 3: Positioned descendants with negative z-indices. + let mut child_stacking_contexts = self.stacking_contexts.iter().peekable(); + while child_stacking_contexts + .peek() + .map_or(false, |child| child.z_index < 0) + { + let child_context = child_stacking_contexts.next().unwrap(); + child_context.build_display_list(builder); + } + + // Step 4: Block backgrounds and borders. + for child in &self.fragments { + builder.current_space_and_clip = child.space_and_clip; + child + .fragment + .build_display_list(builder, &child.containing_block); + } + + // Step 5: Floats. + for child_context in &self.float_stacking_contexts { + child_context.build_display_list(builder); + } + + // Step 7, 8 & 9: Inlines that generate stacking contexts and positioned + // descendants with nonnegative, numeric z-indices. + for child_context in child_stacking_contexts { + child_context.build_display_list(builder); + } + } } impl Fragment { @@ -53,6 +144,80 @@ impl Fragment { } impl BoxFragment { + fn get_stacking_context_type(&self) -> Option { + if self.establishes_stacking_context() { + return Some(StackingContextType::Real); + } + + if self.style.get_box().position != ComputedPosition::Static { + return Some(StackingContextType::PseudoPositioned); + } + + if self.style.get_box().float != ComputedFloat::None { + return Some(StackingContextType::PseudoFloat); + } + + None + } + + /// Returns true if this fragment establishes a new stacking context and false otherwise. + fn establishes_stacking_context(&self) -> bool { + if self.style.get_effects().opacity != 1.0 { + return true; + } + + if self.style.get_effects().mix_blend_mode != ComputedMixBlendMode::Normal { + return true; + } + + if self.has_filter_transform_or_perspective() { + return true; + } + + if self.style.get_box().transform_style == ComputedTransformStyle::Preserve3d || + self.style.overrides_transform_style() + { + return true; + } + + // Fixed position and sticky position always create stacking contexts. + // TODO(mrobinson): We need to handle sticky positioning here when we support it. + if self.style.get_box().position == ComputedPosition::Fixed { + return true; + } + + // Statically positioned fragments don't establish stacking contexts if the previous + // conditions are not fulfilled. Furthermore, z-index doesn't apply to statically + // positioned fragments. + if self.style.get_box().position == ComputedPosition::Static { + return false; + } + + // For absolutely and relatively positioned fragments we only establish a stacking + // context if there is a z-index set. + // See https://www.w3.org/TR/CSS2/visuren.html#z-index + !self.style.get_position().z_index.is_auto() + } + + // Get the effective z-index of this fragment. Z-indices only apply to positioned element + // per CSS 2 9.9.1 (http://www.w3.org/TR/CSS2/visuren.html#z-index), so this value may differ + // from the value specified in the style. + fn effective_z_index(&self) -> i32 { + match self.style.get_box().position { + ComputedPosition::Static => {}, + _ => return self.style.get_position().z_index.integer_or(0), + } + + 0 + } + + /// Returns true if this fragment has a filter, transform, or perspective property set. + fn has_filter_transform_or_perspective(&self) -> bool { + // TODO(mrobinson): We need to handle perspective here. + !self.style.get_box().transform.0.is_empty() || + !self.style.get_effects().filter.0.is_empty() + } + fn build_stacking_context_tree<'a>( &'a self, fragment: &'a Fragment, @@ -63,26 +228,70 @@ impl BoxFragment { builder.clipping_and_scrolling_scope(|builder| { self.adjust_spatial_id_for_positioning(builder); - stacking_context.fragments.push(StackingContextFragment { - space_and_clip: builder.current_space_and_clip, - containing_block: *containing_block, - fragment, - }); + let context_type = match self.get_stacking_context_type() { + Some(context_type) => context_type, + None => { + self.build_stacking_context_tree_for_children( + fragment, + builder, + containing_block, + stacking_context, + ); + return; + }, + }; - // We want to build the scroll frame after the background and border, because - // they shouldn't scroll with the rest of the box content. - self.build_scroll_frame_if_necessary(builder, containing_block); + let mut child_stacking_context = + StackingContext::new(context_type, self.effective_z_index()); + self.build_stacking_context_tree_for_children( + fragment, + builder, + containing_block, + &mut child_stacking_context, + ); - let new_containing_block = self - .content_rect - .to_physical(self.style.writing_mode, containing_block) - .translate(containing_block.origin.to_vector()); - for child in &self.children { - child.build_stacking_context_tree(builder, &new_containing_block, stacking_context); + if context_type == StackingContextType::Real { + child_stacking_context.sort_stacking_contexts(); + stacking_context + .stacking_contexts + .push(child_stacking_context); + } else { + let mut children = + mem::replace(&mut child_stacking_context.stacking_contexts, Vec::new()); + stacking_context + .stacking_contexts + .push(child_stacking_context); + stacking_context.stacking_contexts.append(&mut children); } }); } + fn build_stacking_context_tree_for_children<'a>( + &'a self, + fragment: &'a Fragment, + builder: &mut DisplayListBuilder, + containing_block: &PhysicalRect, + stacking_context: &mut StackingContext<'a>, + ) { + stacking_context.fragments.push(StackingContextFragment { + space_and_clip: builder.current_space_and_clip, + containing_block: *containing_block, + fragment, + }); + + // We want to build the scroll frame after the background and border, because + // they shouldn't scroll with the rest of the box content. + self.build_scroll_frame_if_necessary(builder, containing_block); + + let new_containing_block = self + .content_rect + .to_physical(self.style.writing_mode, containing_block) + .translate(containing_block.origin.to_vector()); + for child in &self.children { + child.build_stacking_context_tree(builder, &new_containing_block, stacking_context); + } + } + fn adjust_spatial_id_for_positioning(&self, builder: &mut DisplayListBuilder) { if self.style.get_box().position != ComputedPosition::Fixed { return; @@ -154,14 +363,3 @@ impl AnonymousFragment { } } } - -impl<'a> StackingContext<'a> { - pub(crate) fn build_display_list(&'a self, builder: &'a mut DisplayListBuilder) { - for child in &self.fragments { - builder.current_space_and_clip = child.space_and_clip; - child - .fragment - .build_display_list(builder, &child.containing_block); - } - } -} diff --git a/components/layout_2020/flow/root.rs b/components/layout_2020/flow/root.rs index 0cfccf481f5d..25cb7fb03acd 100644 --- a/components/layout_2020/flow/root.rs +++ b/components/layout_2020/flow/root.rs @@ -3,6 +3,7 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ use crate::context::LayoutContext; +use crate::display_list::stacking_context::{StackingContext, StackingContextType}; use crate::dom_traversal::{Contents, NodeExt}; use crate::flow::construct::ContainsFloats; use crate::flow::float::FloatBox; @@ -181,7 +182,7 @@ impl BoxTreeRoot { impl FragmentTreeRoot { pub fn build_display_list(&self, builder: &mut crate::display_list::DisplayListBuilder) { - let mut stacking_context = Default::default(); + let mut stacking_context = StackingContext::new(StackingContextType::Real, 0); for fragment in &self.children { fragment.build_stacking_context_tree( builder, @@ -190,6 +191,7 @@ impl FragmentTreeRoot { ); } + stacking_context.sort_stacking_contexts(); stacking_context.build_display_list(builder); } diff --git a/components/style/properties/longhands/position.mako.rs b/components/style/properties/longhands/position.mako.rs index 5e3cbf1324c0..6b7ef1b8f4eb 100644 --- a/components/style/properties/longhands/position.mako.rs +++ b/components/style/properties/longhands/position.mako.rs @@ -60,7 +60,6 @@ ${helpers.predefined_type( "ZIndex", "computed::ZIndex::auto()", engines="gecko servo-2013 servo-2020", - servo_2020_pref="layout.2020.unimplemented", spec="https://www.w3.org/TR/CSS2/visuren.html#z-index", flags="CREATES_STACKING_CONTEXT", animation_value_type="ComputedValue", diff --git a/tests/wpt/metadata-layout-2020/css/CSS2/zindex/z-index-001.xht.ini b/tests/wpt/metadata-layout-2020/css/CSS2/zindex/z-index-001.xht.ini deleted file mode 100644 index be45426fd172..000000000000 --- a/tests/wpt/metadata-layout-2020/css/CSS2/zindex/z-index-001.xht.ini +++ /dev/null @@ -1,2 +0,0 @@ -[z-index-001.xht] - expected: FAIL diff --git a/tests/wpt/metadata-layout-2020/css/CSS2/zindex/z-index-002.xht.ini b/tests/wpt/metadata-layout-2020/css/CSS2/zindex/z-index-002.xht.ini deleted file mode 100644 index c070fd97625e..000000000000 --- a/tests/wpt/metadata-layout-2020/css/CSS2/zindex/z-index-002.xht.ini +++ /dev/null @@ -1,2 +0,0 @@ -[z-index-002.xht] - expected: FAIL diff --git a/tests/wpt/metadata-layout-2020/css/CSS2/zindex/z-index-003.xht.ini b/tests/wpt/metadata-layout-2020/css/CSS2/zindex/z-index-003.xht.ini deleted file mode 100644 index e5d8191c167b..000000000000 --- a/tests/wpt/metadata-layout-2020/css/CSS2/zindex/z-index-003.xht.ini +++ /dev/null @@ -1,2 +0,0 @@ -[z-index-003.xht] - expected: FAIL diff --git a/tests/wpt/metadata-layout-2020/css/CSS2/zindex/z-index-007.xht.ini b/tests/wpt/metadata-layout-2020/css/CSS2/zindex/z-index-007.xht.ini deleted file mode 100644 index ecc9686181b5..000000000000 --- a/tests/wpt/metadata-layout-2020/css/CSS2/zindex/z-index-007.xht.ini +++ /dev/null @@ -1,2 +0,0 @@ -[z-index-007.xht] - expected: FAIL diff --git a/tests/wpt/metadata-layout-2020/css/CSS2/zindex/z-index-008.xht.ini b/tests/wpt/metadata-layout-2020/css/CSS2/zindex/z-index-008.xht.ini deleted file mode 100644 index 31bdee6f0aa1..000000000000 --- a/tests/wpt/metadata-layout-2020/css/CSS2/zindex/z-index-008.xht.ini +++ /dev/null @@ -1,2 +0,0 @@ -[z-index-008.xht] - expected: FAIL diff --git a/tests/wpt/metadata-layout-2020/css/CSS2/zindex/z-index-009.xht.ini b/tests/wpt/metadata-layout-2020/css/CSS2/zindex/z-index-009.xht.ini deleted file mode 100644 index 9f81df5a0550..000000000000 --- a/tests/wpt/metadata-layout-2020/css/CSS2/zindex/z-index-009.xht.ini +++ /dev/null @@ -1,2 +0,0 @@ -[z-index-009.xht] - expected: FAIL diff --git a/tests/wpt/metadata-layout-2020/css/CSS2/zindex/z-index-010.xht.ini b/tests/wpt/metadata-layout-2020/css/CSS2/zindex/z-index-010.xht.ini deleted file mode 100644 index 96a53382b482..000000000000 --- a/tests/wpt/metadata-layout-2020/css/CSS2/zindex/z-index-010.xht.ini +++ /dev/null @@ -1,2 +0,0 @@ -[z-index-010.xht] - expected: FAIL diff --git a/tests/wpt/metadata-layout-2020/css/CSS2/zindex/z-index-011.xht.ini b/tests/wpt/metadata-layout-2020/css/CSS2/zindex/z-index-011.xht.ini deleted file mode 100644 index b3bfadfe8d85..000000000000 --- a/tests/wpt/metadata-layout-2020/css/CSS2/zindex/z-index-011.xht.ini +++ /dev/null @@ -1,2 +0,0 @@ -[z-index-011.xht] - expected: FAIL diff --git a/tests/wpt/metadata-layout-2020/css/CSS2/zindex/z-index-012.xht.ini b/tests/wpt/metadata-layout-2020/css/CSS2/zindex/z-index-012.xht.ini deleted file mode 100644 index 3dfc42df0f4d..000000000000 --- a/tests/wpt/metadata-layout-2020/css/CSS2/zindex/z-index-012.xht.ini +++ /dev/null @@ -1,2 +0,0 @@ -[z-index-012.xht] - expected: FAIL diff --git a/tests/wpt/metadata-layout-2020/css/CSS2/zindex/z-index-014.xht.ini b/tests/wpt/metadata-layout-2020/css/CSS2/zindex/z-index-014.xht.ini deleted file mode 100644 index ec47f89df875..000000000000 --- a/tests/wpt/metadata-layout-2020/css/CSS2/zindex/z-index-014.xht.ini +++ /dev/null @@ -1,2 +0,0 @@ -[z-index-014.xht] - expected: FAIL diff --git a/tests/wpt/metadata-layout-2020/css/CSS2/zindex/z-index-dynamic-001.xht.ini b/tests/wpt/metadata-layout-2020/css/CSS2/zindex/z-index-dynamic-001.xht.ini deleted file mode 100644 index 28cc8b1f4258..000000000000 --- a/tests/wpt/metadata-layout-2020/css/CSS2/zindex/z-index-dynamic-001.xht.ini +++ /dev/null @@ -1,2 +0,0 @@ -[z-index-dynamic-001.xht] - expected: FAIL diff --git a/tests/wpt/metadata-layout-2020/css/css-backgrounds/css3-background-origin-border-box.html.ini b/tests/wpt/metadata-layout-2020/css/css-backgrounds/css3-background-origin-border-box.html.ini deleted file mode 100644 index e64df69484cd..000000000000 --- a/tests/wpt/metadata-layout-2020/css/css-backgrounds/css3-background-origin-border-box.html.ini +++ /dev/null @@ -1,2 +0,0 @@ -[css3-background-origin-border-box.html] - expected: FAIL diff --git a/tests/wpt/mozilla/meta-layout-2020/css/css-position-3/position-sticky-stacking-context.html.ini b/tests/wpt/mozilla/meta-layout-2020/css/css-position-3/position-sticky-stacking-context.html.ini new file mode 100644 index 000000000000..d714d2658ace --- /dev/null +++ b/tests/wpt/mozilla/meta-layout-2020/css/css-position-3/position-sticky-stacking-context.html.ini @@ -0,0 +1,2 @@ +[position-sticky-stacking-context.html] + expected: FAIL diff --git a/tests/wpt/mozilla/meta-layout-2020/css/opacity_stacking_context_a.html.ini b/tests/wpt/mozilla/meta-layout-2020/css/opacity_stacking_context_a.html.ini new file mode 100644 index 000000000000..1036a88a3e8e --- /dev/null +++ b/tests/wpt/mozilla/meta-layout-2020/css/opacity_stacking_context_a.html.ini @@ -0,0 +1,2 @@ +[opacity_stacking_context_a.html] + expected: FAIL diff --git a/tests/wpt/mozilla/meta-layout-2020/css/position_relative_painting_order_a.html.ini b/tests/wpt/mozilla/meta-layout-2020/css/position_relative_painting_order_a.html.ini deleted file mode 100644 index b73409192386..000000000000 --- a/tests/wpt/mozilla/meta-layout-2020/css/position_relative_painting_order_a.html.ini +++ /dev/null @@ -1,2 +0,0 @@ -[position_relative_painting_order_a.html] - expected: FAIL diff --git a/tests/wpt/mozilla/meta-layout-2020/css/position_relative_stacking_context_a.html.ini b/tests/wpt/mozilla/meta-layout-2020/css/position_relative_stacking_context_a.html.ini deleted file mode 100644 index ac04d35f7e83..000000000000 --- a/tests/wpt/mozilla/meta-layout-2020/css/position_relative_stacking_context_a.html.ini +++ /dev/null @@ -1,2 +0,0 @@ -[position_relative_stacking_context_a.html] - expected: FAIL diff --git a/tests/wpt/mozilla/meta-layout-2020/css/transform_stacking_context_a.html.ini b/tests/wpt/mozilla/meta-layout-2020/css/transform_stacking_context_a.html.ini new file mode 100644 index 000000000000..18d0ddfbd1fa --- /dev/null +++ b/tests/wpt/mozilla/meta-layout-2020/css/transform_stacking_context_a.html.ini @@ -0,0 +1,2 @@ +[transform_stacking_context_a.html] + expected: FAIL