layout: Rewrite the block formatting context/float inline-size

speculation code.

The old code tried to do the speculation as a single bottom-up pass
after intrinsic inline-size calculation, which was unable to handle
cases like this:

    <div>
        <div style="float: left">Foo</div>
    </div>
    <div>
        <div style="overflow: hidden">Bar</div>
    </div>

No single bottom-up pass could possibly handle this case, because the
inline-size of the float flowing out of the "Foo" block could never make
it down to the "Bar" block, where it is needed for speculation.

On the pages I tried, this regresses layout performance by 1%-2%.

I first noticed this breaking some pages, like the Google SERPs, several
months ago.
This commit is contained in:
Patrick Walton 2016-03-18 15:23:08 -07:00
parent 9b2ae3a62f
commit b29719e36b
15 changed files with 346 additions and 184 deletions

View file

@ -33,10 +33,7 @@ use display_list_builder::BlockFlowDisplayListBuilding;
use display_list_builder::{BorderPaintingMode, DisplayListBuildState, FragmentDisplayListBuilding};
use euclid::{Point2D, Rect, Size2D};
use floats::{ClearType, FloatKind, Floats, PlacementInfo};
use flow::{BLOCK_POSITION_IS_STATIC};
use flow::{CLEARS_LEFT, CLEARS_RIGHT};
use flow::{HAS_LEFT_FLOATED_DESCENDANTS, HAS_RIGHT_FLOATED_DESCENDANTS};
use flow::{IMPACTED_BY_LEFT_FLOATS, IMPACTED_BY_RIGHT_FLOATS, INLINE_POSITION_IS_STATIC};
use flow::{BLOCK_POSITION_IS_STATIC, CLEARS_LEFT, CLEARS_RIGHT, INLINE_POSITION_IS_STATIC};
use flow::{IS_ABSOLUTELY_POSITIONED};
use flow::{ImmutableFlowUtils, LateAbsolutePositionInfo, MutableFlowUtils, OpaqueFlow};
use flow::{NEEDS_LAYER, PostorderFlowTraversal, PreorderFlowTraversal, FragmentationContext};
@ -497,7 +494,7 @@ pub enum MarginsMayCollapseFlag {
}
#[derive(PartialEq)]
enum FormattingContextType {
pub enum FormattingContextType {
None,
Block,
Other,
@ -844,7 +841,7 @@ impl BlockFlow {
}
}
// Assign block-size now for the child if it was impacted by floats and we couldn't
// Assign block-size now for the child if it might have floats in and we couldn't
// before.
flow::mut_base(kid).floats = floats.clone();
if flow::base(kid).flags.is_float() {
@ -1289,9 +1286,8 @@ impl BlockFlow {
}
/// Assigns the computed inline-start content edge and inline-size to all the children of this
/// block flow. Also computes whether each child will be impacted by floats. The given
/// `callback`, if supplied, will be called once per child; it is currently used to push down
/// column sizes for tables.
/// block flow. The given `callback`, if supplied, will be called once per child; it is
/// currently used to push down column sizes for tables.
///
/// `#[inline(always)]` because this is called only from block or table inline-size assignment
/// and the code for block layout is significantly simpler.
@ -1308,34 +1304,8 @@ impl BlockFlow {
WritingMode,
&mut Au,
&mut Au) {
// Keep track of whether floats could impact each child.
let mut inline_start_floats_impact_child =
self.base.flags.contains(IMPACTED_BY_LEFT_FLOATS);
let mut inline_end_floats_impact_child =
self.base.flags.contains(IMPACTED_BY_RIGHT_FLOATS);
let flags = self.base.flags.clone();
// Remember the inline-sizes of the last left and right floats, if there were any. These
// are used for estimating the inline-sizes of block formatting contexts. (We estimate that
// the inline-size of any block formatting context that we see will be based on the
// inline-size of the containing block as well as the last float seen before it in each
// direction.)
let mut inline_size_of_preceding_left_floats = Au(0);
let mut inline_size_of_preceding_right_floats = Au(0);
if self.formatting_context_type() == FormattingContextType::None {
if inline_start_content_edge > Au(0) {
inline_size_of_preceding_left_floats =
max(self.inline_size_of_preceding_left_floats - inline_start_content_edge,
Au(0));
}
if inline_end_content_edge > Au(0) {
inline_size_of_preceding_right_floats =
max(self.inline_size_of_preceding_right_floats - inline_end_content_edge,
Au(0));
}
}
let opaque_self = OpaqueFlow::from_flow(self);
// Calculate non-auto block size to pass to children.
@ -1358,29 +1328,6 @@ impl BlockFlow {
while let Some((i, kid)) = iterator.next() {
flow::mut_base(kid).block_container_explicit_block_size = explicit_content_size;
// Determine float impaction, and update the inline size speculations if necessary.
if flow::base(kid).flags.contains(CLEARS_LEFT) {
inline_start_floats_impact_child = false;
inline_size_of_preceding_left_floats = Au(0);
}
if flow::base(kid).flags.contains(CLEARS_RIGHT) {
inline_end_floats_impact_child = false;
inline_size_of_preceding_right_floats = Au(0);
}
// Update the speculated inline size if this child is floated.
match flow::base(kid).flags.float_kind() {
float::T::none => {}
float::T::left => {
inline_size_of_preceding_left_floats = inline_size_of_preceding_left_floats +
flow::base(kid).intrinsic_inline_sizes.preferred_inline_size;
}
float::T::right => {
inline_size_of_preceding_right_floats = inline_size_of_preceding_right_floats +
flow::base(kid).intrinsic_inline_sizes.preferred_inline_size;
}
}
// The inline-start margin edge of the child flow is at our inline-start content edge,
// and its inline-size is our content inline-size.
let kid_mode = flow::base(kid).writing_mode;
@ -1399,24 +1346,6 @@ impl BlockFlow {
kid_base.block_container_writing_mode = containing_block_mode;
}
{
let kid_base = flow::mut_base(kid);
inline_start_floats_impact_child = inline_start_floats_impact_child ||
kid_base.flags.contains(HAS_LEFT_FLOATED_DESCENDANTS);
inline_end_floats_impact_child = inline_end_floats_impact_child ||
kid_base.flags.contains(HAS_RIGHT_FLOATED_DESCENDANTS);
kid_base.flags.set(IMPACTED_BY_LEFT_FLOATS, inline_start_floats_impact_child);
kid_base.flags.set(IMPACTED_BY_RIGHT_FLOATS, inline_end_floats_impact_child);
}
if kid.is_block_flow() {
let kid_block = kid.as_mut_block();
kid_block.inline_size_of_preceding_left_floats =
inline_size_of_preceding_left_floats;
kid_block.inline_size_of_preceding_right_floats =
inline_size_of_preceding_right_floats;
}
// Call the callback to propagate extra inline size information down to the child. This
// is currently used for tables.
callback(kid,
@ -1445,7 +1374,7 @@ impl BlockFlow {
/// Determines the type of formatting context this is. See the definition of
/// `FormattingContextType`.
fn formatting_context_type(&self) -> FormattingContextType {
pub fn formatting_context_type(&self) -> FormattingContextType {
let style = self.fragment.style();
if style.get_box().float != float::T::none {
return FormattingContextType::Other
@ -1528,8 +1457,6 @@ impl BlockFlow {
let _scope = layout_debug_scope!("block::bubble_inline_sizes {:x}", self.base.debug_id());
let mut flags = self.base.flags;
flags.remove(HAS_LEFT_FLOATED_DESCENDANTS);
flags.remove(HAS_RIGHT_FLOATED_DESCENDANTS);
// Find the maximum inline-size from children.
let mut computation = self.fragment.compute_intrinsic_inline_sizes();
@ -1584,12 +1511,6 @@ impl BlockFlow {
left_float_width + right_float_width);
self.base.intrinsic_inline_sizes = computation.finish();
match self.fragment.style().get_box().float {
float::T::none => {}
float::T::left => flags.insert(HAS_LEFT_FLOATED_DESCENDANTS),
float::T::right => flags.insert(HAS_RIGHT_FLOATED_DESCENDANTS),
}
self.base.flags = flags
}
@ -1668,23 +1589,15 @@ impl BlockFlow {
self.base.block_container_inline_size = LogicalSize::from_physical(
self.base.writing_mode, layout_context.shared_context().viewport_size).inline;
self.base.block_container_writing_mode = self.base.writing_mode;
// The root element is never impacted by floats.
self.base.flags.remove(IMPACTED_BY_LEFT_FLOATS);
self.base.flags.remove(IMPACTED_BY_RIGHT_FLOATS);
}
// Our inline-size was set to the inline-size of the containing block by the flow's parent.
// Now compute the real value.
self.propagate_and_compute_used_inline_size(layout_context);
// Formatting contexts are never impacted by floats.
// Now for some speculation.
match self.formatting_context_type() {
FormattingContextType::None => {}
FormattingContextType::Block => {
self.base.flags.remove(IMPACTED_BY_LEFT_FLOATS);
self.base.flags.remove(IMPACTED_BY_RIGHT_FLOATS);
// We can't actually compute the inline-size of this block now, because floats
// might affect it. Speculate that its inline-size is equal to the inline-size
// computed above minus the inline-size of the previous left and/or right floats.
@ -1695,14 +1608,11 @@ impl BlockFlow {
if self.fragment.style.max_inline_size() == LengthOrPercentageOrNone::None {
self.fragment.border_box.size.inline =
self.fragment.border_box.size.inline -
self.inline_size_of_preceding_left_floats -
self.inline_size_of_preceding_right_floats;
self.base.speculated_float_placement_in.left -
self.base.speculated_float_placement_in.right;
}
}
FormattingContextType::Other => {
self.base.flags.remove(IMPACTED_BY_LEFT_FLOATS);
self.base.flags.remove(IMPACTED_BY_RIGHT_FLOATS);
}
FormattingContextType::None | FormattingContextType::Other => {}
}
}
}
@ -1786,7 +1696,7 @@ impl Flow for BlockFlow {
self.assign_inline_position_for_formatting_context();
}
if self.base.flags.impacted_by_floats() {
if (self as &Flow).floats_might_flow_through() {
self.base.thread_id = parent_thread_id;
if self.base.restyle_damage.intersects(REFLOW_OUT_OF_FLOW | REFLOW) {
self.assign_block_size(layout_context);
@ -1797,7 +1707,7 @@ impl Flow for BlockFlow {
}
if is_formatting_context {
// If this is a formatting context and was *not* impacted by floats, then we must
// If this is a formatting context and definitely did not have floats in, then we must
// translate the floats past us.
let writing_mode = self.base.floats.writing_mode;
let delta = self.base.position.size.block;