Skip to content
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

Merged
merged 3 commits into from Feb 17, 2020
Merged
Changes from 1 commit
Commits
File filter...
Filter file types
Jump to…
Jump to file
Failed to load files.

Always

Just for now

Prev

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
mrobinson committed Feb 17, 2020
commit 4a2787b974833fe4957fa8994700d30916bf3c6e
@@ -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 {
@@ -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,

This comment has been minimized.

Copy link
@SimonSapin

SimonSapin Feb 14, 2020

Member

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.

This comment has been minimized.

Copy link
@mrobinson

mrobinson Feb 17, 2020

Author Member

Nice catch! I plan to add support for this kind of pseudo stacking context in a followup change, if that's okay with you. Adding it allows more tests to pass once support is added for sorting the fragments inside a stacking context.

}

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

This comment has been minimized.

Copy link
@SimonSapin

SimonSapin Feb 14, 2020

Member

Instead of this enum being part of the sort, what do you think of having separate Vecs for each categories? (I’m not saying we should do that, I haven’t thought through the advantages or downsides of each approach.)

This comment has been minimized.

Copy link
@mrobinson

mrobinson Feb 17, 2020

Author Member

So it's essentially a tradeoff between sorting one slightly larger Vec of stacking contexts versus sorting three or four smaller ones. I'm not sure which is better, but I've tried to more-or-less copy the design of the old layout system which went through a process of iteration and optimization.

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

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

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
ProTip! Use n and p to navigate between commits in a pull request.
You can’t perform that action at this time.