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.
This commit is contained in:
Martin Robinson 2020-02-13 17:22:22 +01:00
parent 318cc16799
commit 4a2787b974
21 changed files with 235 additions and 58 deletions

View file

@ -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 {

View file

@ -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<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) {
(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,
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;
},
};
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,
);
// 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);
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);
}
}
}

View file

@ -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);
}

View file

@ -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",

View file

@ -1,2 +0,0 @@
[z-index-001.xht]
expected: FAIL

View file

@ -1,2 +0,0 @@
[z-index-002.xht]
expected: FAIL

View file

@ -1,2 +0,0 @@
[z-index-003.xht]
expected: FAIL

View file

@ -1,2 +0,0 @@
[z-index-007.xht]
expected: FAIL

View file

@ -1,2 +0,0 @@
[z-index-008.xht]
expected: FAIL

View file

@ -1,2 +0,0 @@
[z-index-009.xht]
expected: FAIL

View file

@ -1,2 +0,0 @@
[z-index-010.xht]
expected: FAIL

View file

@ -1,2 +0,0 @@
[z-index-011.xht]
expected: FAIL

View file

@ -1,2 +0,0 @@
[z-index-012.xht]
expected: FAIL

View file

@ -1,2 +0,0 @@
[z-index-014.xht]
expected: FAIL

View file

@ -1,2 +0,0 @@
[z-index-dynamic-001.xht]
expected: FAIL

View file

@ -1,2 +0,0 @@
[css3-background-origin-border-box.html]
expected: FAIL

View file

@ -0,0 +1,2 @@
[position-sticky-stacking-context.html]
expected: FAIL

View file

@ -0,0 +1,2 @@
[opacity_stacking_context_a.html]
expected: FAIL

View file

@ -1,2 +0,0 @@
[position_relative_painting_order_a.html]
expected: FAIL

View file

@ -1,2 +0,0 @@
[position_relative_stacking_context_a.html]
expected: FAIL

View file

@ -0,0 +1,2 @@
[transform_stacking_context_a.html]
expected: FAIL