New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add initial support for stacking contexts to layout_2020 #25763
Changes from 1 commit
File filter...
Jump to…
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.
- Loading branch information
| @@ -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, | ||
mrobinson
Author
Member
|
||
| } | ||
|
|
||
| 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<StackingContextFragment<'a>>, | ||
|
|
||
| /// All non-float stacking context and pseudo stacking context children | ||
| /// of this stacking context. | ||
| stacking_contexts: Vec<StackingContext<'a>>, | ||
|
|
||
| /// All float pseudo stacking context children of this stacking context. | ||
| float_stacking_contexts: Vec<StackingContext<'a>>, | ||
| } | ||
|
|
||
| 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) { | ||
SimonSapin
Member
|
||
| (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<StackingContextType> { | ||
| 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<Length>, | ||
| 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); | ||
| } | ||
| } | ||
| } | ||
This file was deleted.
This file was deleted.
This file was deleted.
This file was deleted.
This file was deleted.
This file was deleted.
This file was deleted.
This file was deleted.
This file was deleted.
This file was deleted.
This file was deleted.
This file was deleted.
| @@ -0,0 +1,2 @@ | ||
| [position-sticky-stacking-context.html] | ||
| expected: FAIL |
| @@ -0,0 +1,2 @@ | ||
| [opacity_stacking_context_a.html] | ||
| expected: FAIL |
This file was deleted.
This file was deleted.
| @@ -0,0 +1,2 @@ | ||
| [transform_stacking_context_a.html] | ||
| expected: FAIL |
The spec uses the phrase “treat the element as if it created a new stacking context” a third time: for “inline-block and inline-table elements”. I think this should generalize to non-replaced atomic inline-level elements/boxes.