layout: Make incremental reflow more fine-grained by introducing "reflow

out-of-flow" and "reconstruct flow" damage bits.

This is needed for good performance on the maze solver.
This commit is contained in:
Patrick Walton 2014-10-30 13:27:35 -07:00
parent 7712052e13
commit 08fc7c2795
20 changed files with 644 additions and 403 deletions

View file

@ -36,6 +36,7 @@ use flow::{BaseFlow, BlockFlowClass, FlowClass, Flow, ImmutableFlowUtils};
use flow::{MutableFlowUtils, PreorderFlowTraversal, PostorderFlowTraversal, mut_base};
use flow;
use fragment::{Fragment, ImageFragment, InlineBlockFragment, ScannedTextFragment};
use incremental::{Reflow, ReflowOutOfFlow};
use layout_debug;
use model::{Auto, IntrinsicISizes, MarginCollapseInfo, MarginsCollapse, MarginsCollapseThrough};
use model::{MaybeAuto, NoCollapsibleMargins, Specified, specified, specified_or_none};
@ -51,9 +52,11 @@ use servo_util::logical_geometry::{LogicalPoint, LogicalRect, LogicalSize};
use servo_util::opts;
use std::cmp::{max, min};
use std::fmt;
use style::ComputedValues;
use style::computed_values::{LPA_Auto, LPA_Length, LPA_Percentage, LPN_Length, LPN_None};
use style::computed_values::{LPN_Percentage, LP_Length, LP_Percentage, box_sizing, display, float};
use style::computed_values::{overflow, position};
use sync::Arc;
/// Information specific to floated blocks.
#[deriving(Clone, Encodable)]
@ -436,6 +439,11 @@ impl<'a> PreorderFlowTraversal for AbsoluteAssignBSizesTraversal<'a> {
return;
}
assert!(block_flow.base.flags.is_absolutely_positioned());
if !block_flow.base.restyle_damage.intersects(ReflowOutOfFlow | Reflow) {
return
}
let AbsoluteAssignBSizesTraversal(ref ctx) = *self;
block_flow.calculate_absolute_block_size_and_margins(*ctx);
}
@ -818,215 +826,225 @@ impl BlockFlow {
margins_may_collapse: MarginsMayCollapseFlag) {
let _scope = layout_debug_scope!("assign_block_size_block_base {:x}", self.base.debug_id());
// Our current border-box position.
let mut cur_b = Au(0);
if self.base.restyle_damage.contains(Reflow) {
// Our current border-box position.
let mut cur_b = Au(0);
// Absolute positioning establishes a block formatting context. Don't propagate floats
// in or out. (But do propagate them between kids.)
if self.base.flags.is_absolutely_positioned() ||
margins_may_collapse != MarginsMayCollapse {
self.base.floats = Floats::new(self.fragment.style.writing_mode);
}
let mut margin_collapse_info = MarginCollapseInfo::new();
self.base.floats.translate(LogicalSize::new(
self.fragment.style.writing_mode, -self.fragment.inline_start_offset(), Au(0)));
// The sum of our block-start border and block-start padding.
let block_start_offset = self.fragment.border_padding.block_start;
translate_including_floats(&mut cur_b, block_start_offset, &mut self.base.floats);
let can_collapse_block_start_margin_with_kids =
margins_may_collapse == MarginsMayCollapse &&
!self.base.flags.is_absolutely_positioned() &&
self.fragment.border_padding.block_start == Au(0);
margin_collapse_info.initialize_block_start_margin(
&self.fragment,
can_collapse_block_start_margin_with_kids);
// At this point, `cur_b` is at the content edge of our box. Now iterate over children.
let mut floats = self.base.floats.clone();
let mut layers_needed_for_descendants = false;
for kid in self.base.child_iter() {
if flow::base(kid).flags.is_absolutely_positioned() {
// Assume that the *hypothetical box* for an absolute flow starts immediately
// after the block-end border edge of the previous flow.
kid.as_block().hypothetical_position.b = cur_b;
kid.assign_block_size_for_inorder_child_if_necessary(layout_context);
propagate_layer_flag_from_child(&mut layers_needed_for_descendants, kid);
// Skip the collapsing and float processing for absolute flow kids and continue
// with the next flow.
continue
// Absolute positioning establishes a block formatting context. Don't propagate floats
// in or out. (But do propagate them between kids.)
if self.base.flags.is_absolutely_positioned() ||
margins_may_collapse != MarginsMayCollapse {
self.base.floats = Floats::new(self.fragment.style.writing_mode);
}
// Assign block-size now for the child if it was impacted by floats and we couldn't
// before.
flow::mut_base(kid).floats = floats.clone();
if kid.is_float() {
flow::mut_base(kid).position.start.b = cur_b;
{
let kid_block = kid.as_block();
kid_block.float.as_mut().unwrap().float_ceiling =
margin_collapse_info.current_float_ceiling();
}
propagate_layer_flag_from_child(&mut layers_needed_for_descendants, kid);
let mut margin_collapse_info = MarginCollapseInfo::new();
self.base.floats.translate(LogicalSize::new(
self.fragment.style.writing_mode, -self.fragment.inline_start_offset(), Au(0)));
// The sum of our block-start border and block-start padding.
let block_start_offset = self.fragment.border_padding.block_start;
translate_including_floats(&mut cur_b, block_start_offset, &mut self.base.floats);
let can_collapse_block_start_margin_with_kids =
margins_may_collapse == MarginsMayCollapse &&
!self.base.flags.is_absolutely_positioned() &&
self.fragment.border_padding.block_start == Au(0);
margin_collapse_info.initialize_block_start_margin(
&self.fragment,
can_collapse_block_start_margin_with_kids);
// At this point, `cur_b` is at the content edge of our box. Now iterate over children.
let mut floats = self.base.floats.clone();
let mut layers_needed_for_descendants = false;
for kid in self.base.child_iter() {
if flow::base(kid).flags.is_absolutely_positioned() {
// Assume that the *hypothetical box* for an absolute flow starts immediately
// after the block-end border edge of the previous flow.
kid.as_block().hypothetical_position.b = cur_b;
kid.assign_block_size_for_inorder_child_if_necessary(layout_context);
propagate_layer_flag_from_child(&mut layers_needed_for_descendants, kid);
// Skip the collapsing and float processing for absolute flow kids and continue
// with the next flow.
continue
}
// Assign block-size now for the child if it was impacted by floats and we couldn't
// before.
flow::mut_base(kid).floats = floats.clone();
if kid.is_float() {
flow::mut_base(kid).position.start.b = cur_b;
{
let kid_block = kid.as_block();
kid_block.float.as_mut().unwrap().float_ceiling =
margin_collapse_info.current_float_ceiling();
}
propagate_layer_flag_from_child(&mut layers_needed_for_descendants, kid);
let need_to_process_child_floats =
kid.assign_block_size_for_inorder_child_if_necessary(layout_context);
assert!(need_to_process_child_floats); // As it was a float itself...
let kid_base = flow::mut_base(kid);
floats = kid_base.floats.clone();
continue
}
// If we have clearance, assume there are no floats in.
//
// FIXME(#2008, pcwalton): This could be wrong if we have `clear: left` or `clear:
// right` and there are still floats to impact, of course. But this gets
// complicated with margin collapse. Possibly the right thing to do is to lay out
// the block again in this rare case. (Note that WebKit can lay blocks out twice;
// this may be related, although I haven't looked into it closely.)
if flow::base(kid).flags.clears_floats() {
flow::mut_base(kid).floats = Floats::new(self.fragment.style.writing_mode)
}
// Lay the child out if this was an in-order traversal.
let need_to_process_child_floats =
kid.assign_block_size_for_inorder_child_if_necessary(layout_context);
assert!(need_to_process_child_floats); // As it was a float itself...
let kid_base = flow::mut_base(kid);
floats = kid_base.floats.clone();
continue
}
// Mark flows for layerization if necessary to handle painting order correctly.
propagate_layer_flag_from_child(&mut layers_needed_for_descendants, kid);
// If we have clearance, assume there are no floats in.
//
// FIXME(#2008, pcwalton): This could be wrong if we have `clear: left` or `clear:
// right` and there are still floats to impact, of course. But this gets complicated
// with margin collapse. Possibly the right thing to do is to lay out the block again
// in this rare case. (Note that WebKit can lay blocks out twice; this may be related,
// although I haven't looked into it closely.)
if flow::base(kid).flags.clears_floats() {
flow::mut_base(kid).floats = Floats::new(self.fragment.style.writing_mode)
}
// Handle any (possibly collapsed) top margin.
let delta = margin_collapse_info.advance_block_start_margin(
&flow::base(kid).collapsible_margins);
translate_including_floats(&mut cur_b, delta, &mut floats);
// Lay the child out if this was an in-order traversal.
let need_to_process_child_floats =
kid.assign_block_size_for_inorder_child_if_necessary(layout_context);
// Clear past the floats that came in, if necessary.
let clearance = match (flow::base(kid).flags.clears_left(),
flow::base(kid).flags.clears_right()) {
(false, false) => Au(0),
(true, false) => floats.clearance(ClearLeft),
(false, true) => floats.clearance(ClearRight),
(true, true) => floats.clearance(ClearBoth),
};
translate_including_floats(&mut cur_b, clearance, &mut floats);
// Mark flows for layerization if necessary to handle painting order correctly.
propagate_layer_flag_from_child(&mut layers_needed_for_descendants, kid);
// At this point, `cur_b` is at the border edge of the child.
flow::mut_base(kid).position.start.b = cur_b;
// Handle any (possibly collapsed) top margin.
let delta = margin_collapse_info.advance_block_start_margin(
&flow::base(kid).collapsible_margins);
translate_including_floats(&mut cur_b, delta, &mut floats);
// Clear past the floats that came in, if necessary.
let clearance = match (flow::base(kid).flags.clears_left(),
flow::base(kid).flags.clears_right()) {
(false, false) => Au(0),
(true, false) => floats.clearance(ClearLeft),
(false, true) => floats.clearance(ClearRight),
(true, true) => floats.clearance(ClearBoth),
};
translate_including_floats(&mut cur_b, clearance, &mut floats);
// At this point, `cur_b` is at the border edge of the child.
flow::mut_base(kid).position.start.b = cur_b;
// Now pull out the child's outgoing floats. We didn't do this immediately after the
// `assign_block_size_for_inorder_child_if_necessary` call because clearance on a block
// operates on the floats that come *in*, not the floats that go *out*.
if need_to_process_child_floats {
floats = flow::mut_base(kid).floats.clone()
}
// Move past the child's border box. Do not use the `translate_including_floats`
// function here because the child has already translated floats past its border box.
let kid_base = flow::mut_base(kid);
cur_b = cur_b + kid_base.position.size.block;
// Handle any (possibly collapsed) block-end margin.
let delta =
margin_collapse_info.advance_block_end_margin(&kid_base.collapsible_margins);
translate_including_floats(&mut cur_b, delta, &mut floats);
}
// Mark ourselves for layerization if that will be necessary to paint in the proper order
// (CSS 2.1, Appendix E).
self.base.flags.set_layers_needed_for_descendants(layers_needed_for_descendants);
// Collect various offsets needed by absolutely positioned descendants.
(&mut *self as &mut Flow).collect_static_block_offsets_from_children();
// Add in our block-end margin and compute our collapsible margins.
let can_collapse_block_end_margin_with_kids =
margins_may_collapse == MarginsMayCollapse &&
!self.base.flags.is_absolutely_positioned() &&
self.fragment.border_padding.block_end == Au(0);
let (collapsible_margins, delta) =
margin_collapse_info.finish_and_compute_collapsible_margins(
&self.fragment,
can_collapse_block_end_margin_with_kids);
self.base.collapsible_margins = collapsible_margins;
translate_including_floats(&mut cur_b, delta, &mut floats);
// FIXME(#2003, pcwalton): The max is taken here so that you can scroll the page, but this
// is not correct behavior according to CSS 2.1 § 10.5. Instead I think we should treat the
// root element as having `overflow: scroll` and use the layers-based scrolling
// infrastructure to make it scrollable.
let mut block_size = cur_b - block_start_offset;
let is_root = self.is_root();
if is_root {
let screen_size = LogicalSize::from_physical(
self.fragment.style.writing_mode, layout_context.shared.screen_size);
block_size = Au::max(screen_size.block, block_size)
}
if is_root || self.formatting_context_type() != NonformattingContext ||
self.base.flags.is_absolutely_positioned() {
// The content block-size includes all the floats per CSS 2.1 § 10.6.7. The easiest way
// to handle this is to just treat this as clearance.
block_size = block_size + floats.clearance(ClearBoth);
}
if self.base.flags.is_absolutely_positioned() {
// Fixed position layers get layers.
if self.is_fixed() {
self.base.flags.set_needs_layer(true)
}
// Store the content block-size for use in calculating the absolute flow's dimensions
// later.
//
// FIXME(pcwalton): This looks not idempotent. Is it?
self.fragment.border_box.size.block = block_size;
return
}
// Compute any explicitly-specified block size.
// Can't use `for` because we assign to `candidate_block_size_iterator.candidate_value`.
let mut candidate_block_size_iterator = CandidateBSizeIterator::new(
&self.fragment,
self.base.block_container_explicit_block_size);
loop {
match candidate_block_size_iterator.next() {
Some(candidate_block_size) => {
candidate_block_size_iterator.candidate_value = match candidate_block_size {
Auto => block_size,
Specified(value) => value
}
// Now pull out the child's outgoing floats. We didn't do this immediately after
// the `assign_block_size_for_inorder_child_if_necessary` call because clearance on
// a block operates on the floats that come *in*, not the floats that go *out*.
if need_to_process_child_floats {
floats = flow::mut_base(kid).floats.clone()
}
None => break,
// Move past the child's border box. Do not use the `translate_including_floats`
// function here because the child has already translated floats past its border
// box.
let kid_base = flow::mut_base(kid);
cur_b = cur_b + kid_base.position.size.block;
// Handle any (possibly collapsed) block-end margin.
let delta =
margin_collapse_info.advance_block_end_margin(&kid_base.collapsible_margins);
translate_including_floats(&mut cur_b, delta, &mut floats);
}
// Mark ourselves for layerization if that will be necessary to paint in the proper
// order (CSS 2.1, Appendix E).
self.base.flags.set_layers_needed_for_descendants(layers_needed_for_descendants);
// Collect various offsets needed by absolutely positioned descendants.
(&mut *self as &mut Flow).collect_static_block_offsets_from_children();
// Add in our block-end margin and compute our collapsible margins.
let can_collapse_block_end_margin_with_kids =
margins_may_collapse == MarginsMayCollapse &&
!self.base.flags.is_absolutely_positioned() &&
self.fragment.border_padding.block_end == Au(0);
let (collapsible_margins, delta) =
margin_collapse_info.finish_and_compute_collapsible_margins(
&self.fragment,
can_collapse_block_end_margin_with_kids);
self.base.collapsible_margins = collapsible_margins;
translate_including_floats(&mut cur_b, delta, &mut floats);
// FIXME(#2003, pcwalton): The max is taken here so that you can scroll the page, but
// this is not correct behavior according to CSS 2.1 § 10.5. Instead I think we should
// treat the root element as having `overflow: scroll` and use the layers-based
// scrolling infrastructure to make it scrollable.
let mut block_size = cur_b - block_start_offset;
let is_root = self.is_root();
if is_root {
let screen_size = LogicalSize::from_physical(self.fragment.style.writing_mode,
layout_context.shared.screen_size);
block_size = Au::max(screen_size.block, block_size)
}
if is_root || self.formatting_context_type() != NonformattingContext ||
self.base.flags.is_absolutely_positioned() {
// The content block-size includes all the floats per CSS 2.1 § 10.6.7. The easiest
// way to handle this is to just treat it as clearance.
block_size = block_size + floats.clearance(ClearBoth);
}
if self.base.flags.is_absolutely_positioned() {
// Fixed position layers get layers.
if self.is_fixed() {
self.base.flags.set_needs_layer(true)
}
// Store the content block-size for use in calculating the absolute flow's
// dimensions later.
//
// FIXME(pcwalton): This looks not idempotent. Is it?
self.fragment.border_box.size.block = block_size;
return
}
// Compute any explicitly-specified block size.
// Can't use `for` because we assign to `candidate_block_size_iterator.candidate_value`.
let mut candidate_block_size_iterator = CandidateBSizeIterator::new(
&self.fragment,
self.base.block_container_explicit_block_size);
loop {
match candidate_block_size_iterator.next() {
Some(candidate_block_size) => {
candidate_block_size_iterator.candidate_value =
match candidate_block_size {
Auto => block_size,
Specified(value) => value
}
}
None => break,
}
}
// Adjust `cur_b` as necessary to account for the explicitly-specified block-size.
block_size = candidate_block_size_iterator.candidate_value;
let delta = block_size - (cur_b - block_start_offset);
translate_including_floats(&mut cur_b, delta, &mut floats);
// Take border and padding into account.
let block_end_offset = self.fragment.border_padding.block_end;
translate_including_floats(&mut cur_b, block_end_offset, &mut floats);
// Now that `cur_b` is at the block-end of the border box, compute the final border box
// position.
self.fragment.border_box.size.block = cur_b;
self.fragment.border_box.start.b = Au(0);
if !self.base.flags.is_absolutely_positioned() {
self.base.position.size.block = cur_b;
}
// Store the current set of floats in the flow so that flows that come later in the
// document can access them.
self.base.floats = floats.clone();
self.adjust_fragments_for_collapsed_margins_if_root();
} else {
// We don't need to reflow, but we still need to perform in-order traversals if
// necessary.
for kid in self.base.child_iter() {
kid.assign_block_size_for_inorder_child_if_necessary(layout_context);
}
}
// Adjust `cur_b` as necessary to account for the explicitly-specified block-size.
block_size = candidate_block_size_iterator.candidate_value;
let delta = block_size - (cur_b - block_start_offset);
translate_including_floats(&mut cur_b, delta, &mut floats);
// Take border and padding into account.
let block_end_offset = self.fragment.border_padding.block_end;
translate_including_floats(&mut cur_b, block_end_offset, &mut floats);
// Now that `cur_b` is at the block-end of the border box, compute the final border box
// position.
self.fragment.border_box.size.block = cur_b;
self.fragment.border_box.start.b = Au(0);
if !self.base.flags.is_absolutely_positioned() {
self.base.position.size.block = cur_b;
}
// Store the current set of floats in the flow so that flows that come later in the
// document can access them.
self.base.floats = floats.clone();
self.adjust_fragments_for_collapsed_margins_if_root();
if self.is_root_of_absolute_flow_tree() {
// Assign block-sizes for all flows in this absolute flow tree.
// This is preorder because the block-size of an absolute flow may depend on
@ -1038,6 +1056,15 @@ impl BlockFlow {
layout_context: layout_context,
});
}
// Don't remove the dirty bits yet if we're absolutely-positioned, since our final size
// has not been calculated yet. (See `calculate_absolute_block_size_and_margins` for that.)
// Also don't remove the dirty bits if we're a block formatting context since our inline
// size has not yet been computed. (See `assign_inline_position_for_formatting_context()`.)
if !self.base.flags.is_absolutely_positioned() &&
self.formatting_context_type() == NonformattingContext {
self.base.restyle_damage.remove(ReflowOutOfFlow | Reflow);
}
}
/// Add placement information about current float flow for use by the parent.
@ -1184,6 +1211,8 @@ impl BlockFlow {
let block_size = solution.block_size + self.fragment.border_padding.block_start_end();
self.fragment.border_box.size.block = block_size;
self.base.position.size.block = block_size;
self.base.restyle_damage.remove(ReflowOutOfFlow | Reflow);
}
// Our inline-size was set to the inline-size of the containing block by the flow's parent.
@ -1375,6 +1404,10 @@ impl BlockFlow {
fn assign_inline_position_for_formatting_context(&mut self) {
debug_assert!(self.formatting_context_type() != NonformattingContext);
if !self.base.restyle_damage.intersects(ReflowOutOfFlow | Reflow) {
return
}
let info = PlacementInfo {
size: LogicalSize::new(
self.fragment.style.writing_mode,
@ -1504,6 +1537,10 @@ impl Flow for BlockFlow {
fn assign_inline_sizes(&mut self, layout_context: &LayoutContext) {
let _scope = layout_debug_scope!("block::assign_inline_sizes {:x}", self.base.debug_id());
if !self.base.restyle_damage.intersects(ReflowOutOfFlow | Reflow) {
return
}
debug!("assign_inline_sizes({}): assigning inline_size for flow",
if self.is_float() {
"float"
@ -1576,12 +1613,16 @@ impl Flow for BlockFlow {
}
let is_formatting_context = self.formatting_context_type() != NonformattingContext;
if is_formatting_context {
if !self.base.flags.is_absolutely_positioned() && is_formatting_context {
self.assign_inline_position_for_formatting_context();
}
if self.base.flags.impacted_by_floats() {
self.assign_block_size(layout_context);
if self.base.restyle_damage.intersects(ReflowOutOfFlow | Reflow) {
self.assign_block_size(layout_context);
// Don't remove the restyle damage; `assign_block_size` decides whether that is
// appropriate (which in the case of e.g. absolutely-positioned flows, it is not).
}
return true
}
@ -1771,6 +1812,10 @@ impl Flow for BlockFlow {
self.base.validate_display_list_geometry();
}
}
fn repair_style(&mut self, new_style: &Arc<ComputedValues>) {
self.fragment.repair_style(new_style)
}
}
impl fmt::Show for BlockFlow {