Fix painting order of atomic inline stacking containers (#30458)

* Sort stacking contexts and stacking containers by painting order

* fix stealing of stacking containers; fix interleaving with fragments

* actually positioned stacking containers should be stolen too

* update expectations and clean up panic changes

* rework naming and docs

* rename s_c_a_p_s_c to real_s_c_a_p_s_c; fix docs

* rename InlineStackingContainer to AtomicInlineStackingContainer

* rework debug logging to use PrintTree

* clean up docs and PrintTree output

* don't panic unless cfg!(debug_assertions) is true

* update expectations
This commit is contained in:
Delan Azabani 2023-10-07 00:00:00 +08:00 committed by GitHub
parent c06ec90151
commit afe4faa09a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 374 additions and 156 deletions

1
Cargo.lock generated
View file

@ -3094,6 +3094,7 @@ dependencies = [
"serde", "serde",
"serde_json", "serde_json",
"servo_arc", "servo_arc",
"servo_config",
"servo_url", "servo_url",
"style", "style",
"style_traits", "style_traits",

View file

@ -176,9 +176,12 @@ pub struct DebugOptions {
/// Dumps the rule tree. /// Dumps the rule tree.
pub dump_rule_tree: bool, pub dump_rule_tree: bool,
/// Print the flow tree after each layout. /// Print the flow tree (Layout 2013) or fragment tree (Layout 2020) after each layout.
pub dump_flow_tree: bool, pub dump_flow_tree: bool,
/// Print the stacking context tree after each layout.
pub dump_stacking_context_tree: bool,
/// Print the display list after each layout. /// Print the display list after each layout.
pub dump_display_list: bool, pub dump_display_list: bool,
@ -247,6 +250,7 @@ impl DebugOptions {
"disable-text-aa" => self.disable_text_antialiasing = true, "disable-text-aa" => self.disable_text_antialiasing = true,
"dump-display-list" => self.dump_display_list = true, "dump-display-list" => self.dump_display_list = true,
"dump-display-list-json" => self.dump_display_list_json = true, "dump-display-list-json" => self.dump_display_list_json = true,
"dump-stacking-context-tree" => self.dump_stacking_context_tree = true,
"dump-flow-tree" => self.dump_flow_tree = true, "dump-flow-tree" => self.dump_flow_tree = true,
"dump-rule-tree" => self.dump_rule_tree = true, "dump-rule-tree" => self.dump_rule_tree = true,
"dump-style-tree" => self.dump_style_tree = true, "dump-style-tree" => self.dump_style_tree = true,
@ -305,6 +309,10 @@ impl DebugOptions {
"Disable subpixel text antialiasing overriding preference.", "Disable subpixel text antialiasing overriding preference.",
); );
print_option("disable-text-aa", "Disable antialiasing of rendered text."); print_option("disable-text-aa", "Disable antialiasing of rendered text.");
print_option(
"dump-stacking-context-tree",
"Print the stacking context tree after each layout.",
);
print_option( print_option(
"dump-display-list", "dump-display-list",
"Print the display list after each layout.", "Print the display list after each layout.",
@ -313,7 +321,10 @@ impl DebugOptions {
"dump-display-list-json", "dump-display-list-json",
"Print the display list in JSON form.", "Print the display list in JSON form.",
); );
print_option("dump-flow-tree", "Print the flow tree after each layout."); print_option(
"dump-flow-tree",
"Print the flow tree (Layout 2013) or fragment tree (Layout 2020) after each layout.",
);
print_option( print_option(
"dump-rule-tree", "dump-rule-tree",
"Print the style rule tree after each layout.", "Print the style rule tree after each layout.",

View file

@ -84,6 +84,6 @@ impl PrintTree {
impl Drop for PrintTree { impl Drop for PrintTree {
fn drop(&mut self) { fn drop(&mut self) {
self.flush_queued_item("\u{9492}\u{9472}"); self.flush_queued_item("\u{2514}\u{2500}");
} }
} }

View file

@ -38,6 +38,7 @@ script_traits = { path = "../script_traits" }
serde = { workspace = true } serde = { workspace = true }
serde_json = { workspace = true } serde_json = { workspace = true }
servo_arc = { path = "../servo_arc" } servo_arc = { path = "../servo_arc" }
servo_config = { path = "../config" }
servo_url = { path = "../url" } servo_url = { path = "../url" }
style = { path = "../style", features = ["servo"] } style = { path = "../style", features = ["servo"] }
style_traits = { path = "../style_traits" } style_traits = { path = "../style_traits" }

View file

@ -2,12 +2,15 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this * 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/. */ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use std::cmp::Ordering; use std::cell::RefCell;
use std::mem; use std::mem;
use euclid::default::Rect; use euclid::default::Rect;
use gfx_traits::print_tree::PrintTree;
use log::warn;
use script_traits::compositor::{ScrollTreeNodeId, ScrollableNodeInfo}; use script_traits::compositor::{ScrollTreeNodeId, ScrollableNodeInfo};
use servo_arc::Arc as ServoArc; use servo_arc::Arc as ServoArc;
use servo_config::opts::DebugOptions;
use style::computed_values::float::T as ComputedFloat; use style::computed_values::float::T as ComputedFloat;
use style::computed_values::mix_blend_mode::T as ComputedMixBlendMode; use style::computed_values::mix_blend_mode::T as ComputedMixBlendMode;
use style::computed_values::overflow_x::T as ComputedOverflow; use style::computed_values::overflow_x::T as ComputedOverflow;
@ -70,14 +73,18 @@ pub(crate) type ContainingBlockInfo<'a> = ContainingBlockManager<'a, ContainingB
#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)] #[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
pub(crate) enum StackingContextSection { pub(crate) enum StackingContextSection {
BackgroundsAndBorders, OwnBackgroundsAndBorders,
BlockBackgroundsAndBorders, DescendantBackgroundsAndBorders,
Content, Foreground,
Outline, Outline,
} }
impl DisplayList { impl DisplayList {
pub fn build_stacking_context_tree(&mut self, fragment_tree: &FragmentTree) -> StackingContext { pub fn build_stacking_context_tree(
&mut self,
fragment_tree: &FragmentTree,
debug: &DebugOptions,
) -> StackingContext {
let root_clip_chain_id = self let root_clip_chain_id = self
.wr .wr
.define_clip_chain(None, [wr::ClipId::root(self.wr.pipeline_id)]); .define_clip_chain(None, [wr::ClipId::root(self.wr.pipeline_id)]);
@ -105,7 +112,7 @@ impl DisplayList {
for_absolute_and_fixed_descendants: &cb_for_fixed_descendants, for_absolute_and_fixed_descendants: &cb_for_fixed_descendants,
}; };
let mut root_stacking_context = StackingContext::create_root(&self.wr); let mut root_stacking_context = StackingContext::create_root(&self.wr, debug);
for fragment in &fragment_tree.root_fragments { for fragment in &fragment_tree.root_fragments {
fragment.borrow().build_stacking_context_tree( fragment.borrow().build_stacking_context_tree(
fragment, fragment,
@ -192,37 +199,73 @@ impl DisplayList {
} }
} }
pub(crate) struct StackingContextFragment { /// A piece of content that directly belongs to a section of a stacking context.
scroll_node_id: ScrollTreeNodeId, ///
clip_chain_id: wr::ClipChainId, /// This is generally part of a fragment, like its borders or foreground, but it
section: StackingContextSection, /// can also be a stacking container that needs to be painted in fragment order.
containing_block: PhysicalRect<Length>, pub(crate) enum StackingContextContent {
fragment: ArcRefCell<Fragment>, /// A fragment that does not generate a stacking context or stacking container.
Fragment {
scroll_node_id: ScrollTreeNodeId,
clip_chain_id: wr::ClipChainId,
section: StackingContextSection,
containing_block: PhysicalRect<Length>,
fragment: ArcRefCell<Fragment>,
},
/// An index into [StackingContext::atomic_inline_stacking_containers].
///
/// There is no section field, because these are always in [StackingContextSection::Foreground].
AtomicInlineStackingContainer { index: usize },
} }
impl StackingContextFragment { impl StackingContextContent {
fn build_display_list(&self, builder: &mut DisplayListBuilder) { fn section(&self) -> StackingContextSection {
builder.current_scroll_node_id = self.scroll_node_id; match self {
builder.current_clip_chain_id = self.clip_chain_id; Self::Fragment { section, .. } => *section,
self.fragment Self::AtomicInlineStackingContainer { .. } => StackingContextSection::Foreground,
.borrow() }
.build_display_list(builder, &self.containing_block, self.section); }
fn build_display_list(
&self,
builder: &mut DisplayListBuilder,
inline_stacking_containers: &[StackingContext],
) {
match self {
Self::Fragment {
scroll_node_id,
clip_chain_id,
section,
containing_block,
fragment,
} => {
builder.current_scroll_node_id = *scroll_node_id;
builder.current_clip_chain_id = *clip_chain_id;
fragment
.borrow()
.build_display_list(builder, containing_block, *section);
},
Self::AtomicInlineStackingContainer { index } => {
inline_stacking_containers[*index].build_display_list(builder);
},
}
} }
} }
#[derive(Clone, Copy, Eq, PartialEq)] #[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(crate) enum StackingContextType { pub(crate) enum StackingContextType {
Real, RealStackingContext,
PseudoPositioned, PositionedStackingContainer,
PseudoFloat, FloatStackingContainer,
PseudoAtomicInline, AtomicInlineStackingContainer,
} }
/// A [StackingContext] represents either a stacking context or a stacking /// Either a stacking context or a stacking container, per the definitions in
/// container according to the definitions outlined in /// <https://drafts.csswg.org/css-position-4/#painting-order>.
/// <https://drafts.csswg.org/css-position-4/#painting-order> ///
/// Stacking containers are sometimes called "pseudo-stacking contexts" /// We use the term “real stacking context” in situations that call for a
/// in the Servo source. /// stacking context but not a stacking container.
pub struct StackingContext { pub struct StackingContext {
/// The spatial id of this fragment. This is used to properly handle /// The spatial id of this fragment. This is used to properly handle
/// things like preserve-3d. /// things like preserve-3d.
@ -231,34 +274,61 @@ pub struct StackingContext {
/// The fragment that established this stacking context. /// The fragment that established this stacking context.
initializing_fragment_style: Option<ServoArc<ComputedValues>>, initializing_fragment_style: Option<ServoArc<ComputedValues>>,
/// The type of this StackingContext. Used for collecting and sorting. /// The type of this stacking context. Used for collecting and sorting.
context_type: StackingContextType, context_type: StackingContextType,
/// Fragments that make up the content of this stacking context. /// The contents that need to be painted in fragment order.
fragments: Vec<StackingContextFragment>, contents: Vec<StackingContextContent>,
/// All non-float stacking context and pseudo stacking context children /// Stacking contexts that need to be stolen by the parent stacking context
/// of this stacking context. /// if this is a stacking container, that is, real stacking contexts and
stacking_contexts: Vec<StackingContext>, /// positioned stacking containers (where z-index is auto).
/// <https://drafts.csswg.org/css-position-4/#paint-a-stacking-container>
/// All float stacking container children of this stacking context.
/// These are stored separately because they should not be passed up to
/// their real stacking context ancestors. From the definition of stacking
/// containers from <https://drafts.csswg.org/css-position-4#painting-order>:
///
/// > To paint a stacking container, given a box root and a canvas canvas: /// > 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 /// > 1. Paint a stacking context given root and canvas, treating root as
/// > if it created a new stacking context, but omitting any positioned /// > if it created a new stacking context, but omitting any positioned
/// > descendants or descendants that actually create a stacking context /// > descendants or descendants that actually create a stacking context
/// > (letting the parent stacking context paint them, instead). /// > (letting the parent stacking context paint them, instead).
/// real_stacking_contexts_and_positioned_stacking_containers: Vec<StackingContext>,
/// Note that all stacking containers / pseudo stacking contexts are passed up
/// to parent stacking contexts, except in the case of floats. /// Float stacking containers.
float_stacking_contexts: Vec<StackingContext>, /// 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>
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>
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)]
pub struct DebugPrintItem {
field: DebugPrintField,
index: usize,
}
/// Refers to one of the vecs of a [StackingContext].
#[derive(Clone, Copy)]
pub enum DebugPrintField {
Contents,
RealStackingContextsAndPositionedStackingContainers,
FloatStackingContainers,
AtomicInlineStackingContainers,
} }
impl StackingContext { impl StackingContext {
pub(crate) fn new( fn create_descendant(
&self,
spatial_id: wr::SpatialId, spatial_id: wr::SpatialId,
initializing_fragment_style: ServoArc<ComputedValues>, initializing_fragment_style: ServoArc<ComputedValues>,
context_type: StackingContextType, context_type: StackingContextType,
@ -267,30 +337,42 @@ impl StackingContext {
spatial_id, spatial_id,
initializing_fragment_style: Some(initializing_fragment_style), initializing_fragment_style: Some(initializing_fragment_style),
context_type, context_type,
fragments: vec![], contents: vec![],
stacking_contexts: vec![], real_stacking_contexts_and_positioned_stacking_containers: vec![],
float_stacking_contexts: vec![], float_stacking_containers: vec![],
atomic_inline_stacking_containers: vec![],
debug_print_items: self.debug_print_items.is_some().then(|| vec![].into()),
} }
} }
pub(crate) fn create_root(wr: &wr::DisplayListBuilder) -> Self { pub(crate) fn create_root(wr: &wr::DisplayListBuilder, debug: &DebugOptions) -> Self {
Self { Self {
spatial_id: wr::SpaceAndClipInfo::root_scroll(wr.pipeline_id).spatial_id, spatial_id: wr::SpaceAndClipInfo::root_scroll(wr.pipeline_id).spatial_id,
initializing_fragment_style: None, initializing_fragment_style: None,
context_type: StackingContextType::Real, context_type: StackingContextType::RealStackingContext,
fragments: vec![], contents: vec![],
stacking_contexts: vec![], real_stacking_contexts_and_positioned_stacking_containers: vec![],
float_stacking_contexts: 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. /// Add a child stacking context to this stacking context.
fn add_stacking_context(&mut self, stacking_context: StackingContext) { fn add_stacking_context(&mut self, stacking_context: StackingContext) {
if stacking_context.context_type == StackingContextType::PseudoFloat { match stacking_context.context_type {
self.float_stacking_contexts.push(stacking_context); StackingContextType::RealStackingContext => {
} else { &mut self.real_stacking_contexts_and_positioned_stacking_containers
self.stacking_contexts.push(stacking_context); },
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)
} }
fn z_index(&self) -> i32 { fn z_index(&self) -> i32 {
@ -300,24 +382,32 @@ impl StackingContext {
} }
pub(crate) fn sort(&mut self) { pub(crate) fn sort(&mut self) {
self.fragments.sort_by(|a, b| a.section.cmp(&b.section)); self.contents.sort_by(|a, b| a.section().cmp(&b.section()));
self.real_stacking_contexts_and_positioned_stacking_containers
.sort_by(|a, b| a.z_index().cmp(&b.z_index()));
self.stacking_contexts.sort_by(|a, b| { debug_assert!(self
let a_z_index = a.z_index(); .real_stacking_contexts_and_positioned_stacking_containers
let b_z_index = b.z_index(); .iter()
if a_z_index != 0 || b_z_index != 0 { .all(|c| match c.context_type {
return a_z_index.cmp(&b_z_index); StackingContextType::RealStackingContext |
} StackingContextType::PositionedStackingContainer => true,
_ => false,
match (a.context_type, b.context_type) { }));
(StackingContextType::PseudoFloat, StackingContextType::PseudoFloat) => { debug_assert!(self
Ordering::Equal .float_stacking_containers
}, .iter()
(StackingContextType::PseudoFloat, _) => Ordering::Less, .all(
(_, StackingContextType::PseudoFloat) => Ordering::Greater, |c| c.context_type == StackingContextType::FloatStackingContainer &&
(_, _) => Ordering::Equal, 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( fn push_webrender_stacking_context_if_necessary(
@ -421,20 +511,23 @@ impl StackingContext {
// Lets find the corresponding fragment. // Lets find the corresponding fragment.
// The fragment generated by the root element is the first one here, unless… // The fragment generated by the root element is the first one here, unless…
let first_if_any = self.fragments.first().or_else(|| { let first_if_any = self.contents.first().or_else(|| {
// There wasnt any `StackingContextFragment` in the root `StackingContext`, // There wasnt any `StackingContextFragment` in the root `StackingContext`,
// because the root element generates a stacking context. Lets find that one. // because the root element generates a stacking context. Lets find that one.
self.stacking_contexts self.real_stacking_contexts_and_positioned_stacking_containers
.first() .first()
.and_then(|first_child_stacking_context| { .and_then(|first_child_stacking_context| {
first_child_stacking_context.fragments.first() first_child_stacking_context.contents.first()
}) })
}); });
macro_rules! debug_panic { macro_rules! debug_panic {
($msg: expr) => { ($msg: expr) => {
if cfg!(debug_assertions) { if cfg!(debug_assertions) {
panic!($msg) panic!($msg);
} else {
warn!($msg);
return;
} }
}; };
} }
@ -444,15 +537,17 @@ impl StackingContext {
} else { } else {
// This should only happen if the root element has `display: none` // This should only happen if the root element has `display: none`
debug_panic!("`CanvasBackground::for_root_element` should have returned `style: None`"); debug_panic!("`CanvasBackground::for_root_element` should have returned `style: None`");
return;
}; };
let fragment = first_stacking_context_fragment.fragment.borrow(); let StackingContextContent::Fragment { fragment, scroll_node_id, containing_block, .. }
= first_stacking_context_fragment else {
debug_panic!("Expected a fragment, not a stacking container");
};
let fragment = fragment.borrow();
let box_fragment = match &*fragment { let box_fragment = match &*fragment {
Fragment::Box(box_fragment) | Fragment::Float(box_fragment) => box_fragment, Fragment::Box(box_fragment) | Fragment::Float(box_fragment) => box_fragment,
_ => { _ => {
debug_panic!("Expected a box-generated fragment"); debug_panic!("Expected a box-generated fragment");
return;
}, },
}; };
@ -465,7 +560,7 @@ impl StackingContext {
// The root element may have a CSS transform, and we want the canvas // The root element may have a CSS transform, and we want the canvas
// background image to be transformed. To do so, take its `SpatialId` // background image to be transformed. To do so, take its `SpatialId`
// (but not its `ClipId`) // (but not its `ClipId`)
builder.current_scroll_node_id = first_stacking_context_fragment.scroll_node_id; builder.current_scroll_node_id = *scroll_node_id;
// Now we need express the painting area rectangle in the local coordinate system, // Now we need express the painting area rectangle in the local coordinate system,
// which differs from the top-level coordinate system based on… // which differs from the top-level coordinate system based on…
@ -487,10 +582,8 @@ impl StackingContext {
} }
} }
let mut fragment_builder = super::BuilderForBoxFragment::new( let mut fragment_builder =
box_fragment, super::BuilderForBoxFragment::new(box_fragment, containing_block);
&first_stacking_context_fragment.containing_block,
);
let source = super::background::Source::Canvas { let source = super::background::Source::Canvas {
style, style,
painting_area, painting_area,
@ -501,63 +594,159 @@ impl StackingContext {
pub(crate) fn build_display_list(&self, builder: &mut DisplayListBuilder) { pub(crate) fn build_display_list(&self, builder: &mut DisplayListBuilder) {
let pushed_context = self.push_webrender_stacking_context_if_necessary(builder); let pushed_context = self.push_webrender_stacking_context_if_necessary(builder);
// Properly order display items that make up a stacking context. "Steps" here // Properly order display items that make up a stacking context.
// refer to the steps in CSS 2.1 Appendix E. // “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 // Steps 1 and 2: Borders and background for the root
let mut child_fragments = self.fragments.iter().peekable(); let mut contents = self.contents.iter().enumerate().peekable();
while child_fragments.peek().map_or(false, |child| { while contents.peek().map_or(false, |(_, child)| {
child.section == StackingContextSection::BackgroundsAndBorders child.section() == StackingContextSection::OwnBackgroundsAndBorders
}) { }) {
child_fragments.next().unwrap().build_display_list(builder); 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: Positioned descendants with negative z-indices // Step 3: Stacking contexts with negative z-index
let mut child_stacking_contexts = self.stacking_contexts.iter().peekable(); let mut real_stacking_contexts_and_positioned_stacking_containers = self
while child_stacking_contexts .real_stacking_contexts_and_positioned_stacking_containers
.iter()
.enumerate()
.peekable();
while real_stacking_contexts_and_positioned_stacking_containers
.peek() .peek()
.map_or(false, |child| child.z_index() < 0) .map_or(false, |(_, child)| child.z_index() < 0)
{ {
let child_context = child_stacking_contexts.next().unwrap(); let (i, child) = real_stacking_contexts_and_positioned_stacking_containers
child_context.build_display_list(builder); .next()
.unwrap();
self.debug_push_print_item(
DebugPrintField::RealStackingContextsAndPositionedStackingContainers,
i,
);
child.build_display_list(builder);
} }
// Step 4: Block backgrounds and borders // Step 4: Block backgrounds and borders
while child_fragments.peek().map_or(false, |child| { while contents.peek().map_or(false, |(_, child)| {
child.section == StackingContextSection::BlockBackgroundsAndBorders child.section() == StackingContextSection::DescendantBackgroundsAndBorders
}) { }) {
child_fragments.next().unwrap().build_display_list(builder); 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: Floats // Step 5: Float stacking containers
for child_context in &self.float_stacking_contexts { for (i, child) in self.float_stacking_containers.iter().enumerate() {
child_context.build_display_list(builder); self.debug_push_print_item(DebugPrintField::FloatStackingContainers, i);
child.build_display_list(builder);
} }
// Step 6: Content // Steps 6 and 7: Fragments and inline stacking containers
while child_fragments.peek().map_or(false, |child| { while contents.peek().map_or(false, |(_, child)| {
child.section == StackingContextSection::Content child.section() == StackingContextSection::Foreground
}) { }) {
child_fragments.next().unwrap().build_display_list(builder); 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 7, 8 & 9: Inlines that generate stacking contexts and positioned // Steps 8 and 9: Stacking contexts with non-negative z-index, and
// descendants with nonnegative, numeric z-indices // positioned stacking containers (where z-index is auto)
for child_context in child_stacking_contexts { for (i, child) in real_stacking_contexts_and_positioned_stacking_containers {
child_context.build_display_list(builder); self.debug_push_print_item(
DebugPrintField::RealStackingContextsAndPositionedStackingContainers,
i,
);
child.build_display_list(builder);
} }
// Step 10: Outline // Step 10: Outline
while child_fragments.peek().map_or(false, |child| { while contents.peek().map_or(false, |(_, child)| {
child.section == StackingContextSection::Outline child.section() == StackingContextSection::Outline
}) { }) {
child_fragments.next().unwrap().build_display_list(builder); 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 { if pushed_context {
builder.display_list.wr.pop_stacking_context(); builder.display_list.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);
},
DebugPrintField::AtomicInlineStackingContainers => {
// do nothing; we print these in DebugPrintField::Contents
},
}
}
match self.context_type {
StackingContextType::AtomicInlineStackingContainer => {
// do nothing; we print the heading with its index in DebugPrintField::Contents
},
_ => {
tree.end_level();
},
}
}
} }
#[derive(PartialEq)] #[derive(PartialEq)]
@ -624,13 +813,15 @@ impl Fragment {
); );
}, },
Fragment::Text(_) | Fragment::Image(_) | Fragment::IFrame(_) => { Fragment::Text(_) | Fragment::Image(_) | Fragment::IFrame(_) => {
stacking_context.fragments.push(StackingContextFragment { stacking_context
section: StackingContextSection::Content, .contents
scroll_node_id: containing_block.scroll_node_id, .push(StackingContextContent::Fragment {
clip_chain_id: containing_block.clip_chain_id, section: StackingContextSection::Foreground,
containing_block: containing_block.rect, scroll_node_id: containing_block.scroll_node_id,
fragment: fragment_ref.clone(), clip_chain_id: containing_block.clip_chain_id,
}); containing_block: containing_block.rect,
fragment: fragment_ref.clone(),
});
}, },
} }
} }
@ -645,20 +836,20 @@ struct ReferenceFrameData {
impl BoxFragment { impl BoxFragment {
fn get_stacking_context_type(&self) -> Option<StackingContextType> { fn get_stacking_context_type(&self) -> Option<StackingContextType> {
if self.style.establishes_stacking_context() { if self.style.establishes_stacking_context() {
return Some(StackingContextType::Real); return Some(StackingContextType::RealStackingContext);
} }
let box_style = &self.style.get_box(); let box_style = &self.style.get_box();
if box_style.position != ComputedPosition::Static { if box_style.position != ComputedPosition::Static {
return Some(StackingContextType::PseudoPositioned); return Some(StackingContextType::PositionedStackingContainer);
} }
if box_style.float != ComputedFloat::None { if box_style.float != ComputedFloat::None {
return Some(StackingContextType::PseudoFloat); return Some(StackingContextType::FloatStackingContainer);
} }
if box_style.display.is_atomic_inline_level() { if box_style.display.is_atomic_inline_level() {
return Some(StackingContextType::PseudoAtomicInline); return Some(StackingContextType::AtomicInlineStackingContainer);
} }
None None
@ -666,14 +857,14 @@ impl BoxFragment {
fn get_stacking_context_section(&self) -> StackingContextSection { fn get_stacking_context_section(&self) -> StackingContextSection {
if self.get_stacking_context_type().is_some() { if self.get_stacking_context_type().is_some() {
return StackingContextSection::BackgroundsAndBorders; return StackingContextSection::OwnBackgroundsAndBorders;
} }
if self.style.get_box().display.outside() == DisplayOutside::Inline { if self.style.get_box().display.outside() == DisplayOutside::Inline {
return StackingContextSection::Content; return StackingContextSection::Foreground;
} }
StackingContextSection::BlockBackgroundsAndBorders StackingContextSection::DescendantBackgroundsAndBorders
} }
fn build_stacking_context_tree( fn build_stacking_context_tree(
@ -778,7 +969,18 @@ impl BoxFragment {
}, },
}; };
let mut child_stacking_context = StackingContext::new( 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(),
},
);
}
let mut child_stacking_context = parent_stacking_context.create_descendant(
containing_block.scroll_node_id.spatial_id, containing_block.scroll_node_id.spatial_id,
self.style.clone(), self.style.clone(),
context_type, context_type,
@ -792,9 +994,10 @@ impl BoxFragment {
); );
let mut stolen_children = vec![]; let mut stolen_children = vec![];
if context_type != StackingContextType::Real { if context_type != StackingContextType::RealStackingContext {
stolen_children = mem::replace( stolen_children = mem::replace(
&mut child_stacking_context.stacking_contexts, &mut child_stacking_context
.real_stacking_contexts_and_positioned_stacking_containers,
stolen_children, stolen_children,
); );
} }
@ -802,7 +1005,7 @@ impl BoxFragment {
child_stacking_context.sort(); child_stacking_context.sort();
parent_stacking_context.add_stacking_context(child_stacking_context); parent_stacking_context.add_stacking_context(child_stacking_context);
parent_stacking_context parent_stacking_context
.stacking_contexts .real_stacking_contexts_and_positioned_stacking_containers
.append(&mut stolen_children); .append(&mut stolen_children);
} }
@ -825,21 +1028,25 @@ impl BoxFragment {
new_clip_chain_id = clip_chain_id; new_clip_chain_id = clip_chain_id;
} }
stacking_context.fragments.push(StackingContextFragment { stacking_context
scroll_node_id: new_scroll_node_id, .contents
clip_chain_id: new_clip_chain_id, .push(StackingContextContent::Fragment {
section: self.get_stacking_context_section(),
containing_block: containing_block.rect,
fragment: fragment.clone(),
});
if self.style.get_outline().outline_width.px() > 0.0 {
stacking_context.fragments.push(StackingContextFragment {
scroll_node_id: new_scroll_node_id, scroll_node_id: new_scroll_node_id,
clip_chain_id: new_clip_chain_id, clip_chain_id: new_clip_chain_id,
section: StackingContextSection::Outline, section: self.get_stacking_context_section(),
containing_block: containing_block.rect, containing_block: containing_block.rect,
fragment: fragment.clone(), fragment: fragment.clone(),
}); });
if self.style.get_outline().outline_width.px() > 0.0 {
stacking_context
.contents
.push(StackingContextContent::Fragment {
scroll_node_id: new_scroll_node_id,
clip_chain_id: new_clip_chain_id,
section: StackingContextSection::Outline,
containing_block: containing_block.rect,
fragment: fragment.clone(),
});
} }
// We want to build the scroll frame after the background and border, because // We want to build the scroll frame after the background and border, because

View file

@ -1259,7 +1259,8 @@ impl LayoutThread {
// Build the root stacking context. This turns the `FragmentTree` into a // Build the root stacking context. This turns the `FragmentTree` into a
// tree of fragments in CSS painting order and also creates all // tree of fragments in CSS painting order and also creates all
// applicable spatial and clip nodes. // applicable spatial and clip nodes.
let root_stacking_context = display_list.build_stacking_context_tree(&fragment_tree); let root_stacking_context =
display_list.build_stacking_context_tree(&fragment_tree, &self.debug);
// Build the rest of the display list which inclues all of the WebRender primitives. // Build the rest of the display list which inclues all of the WebRender primitives.
let (iframe_sizes, is_contentful) = let (iframe_sizes, is_contentful) =
@ -1268,6 +1269,9 @@ impl LayoutThread {
if self.debug.dump_flow_tree { if self.debug.dump_flow_tree {
fragment_tree.print(); fragment_tree.print();
} }
if self.debug.dump_stacking_context_tree {
root_stacking_context.debug_print();
}
debug!("Layout done!"); debug!("Layout done!");
// Observe notifications about rendered frames if needed right before // Observe notifications about rendered frames if needed right before

View file

@ -1,2 +0,0 @@
[overflow-scroll-float-paint-order.html]
expected: FAIL

View file

@ -1,2 +0,0 @@
[inline-block-zorder-003.xht]
expected: FAIL

View file

@ -1,2 +0,0 @@
[inline-table-zorder-003.xht]
expected: FAIL

View file

@ -0,0 +1,2 @@
[preserve3d-and-flattening-z-order-005.html]
expected: FAIL

View file

@ -1,2 +0,0 @@
[select-multiple-covered-by-abspos.html]
expected: FAIL