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,6 +826,7 @@ impl BlockFlow {
margins_may_collapse: MarginsMayCollapseFlag) {
let _scope = layout_debug_scope!("assign_block_size_block_base {:x}", self.base.debug_id());
if self.base.restyle_damage.contains(Reflow) {
// Our current border-box position.
let mut cur_b = Au(0);
@ -884,10 +893,10 @@ impl BlockFlow {
// 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.)
// 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)
}
@ -917,15 +926,16 @@ impl BlockFlow {
// 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*.
// 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.
// 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;
@ -935,8 +945,8 @@ impl BlockFlow {
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).
// 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.
@ -954,22 +964,22 @@ impl BlockFlow {
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.
// 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);
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.
// 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);
}
@ -979,8 +989,8 @@ impl BlockFlow {
self.base.flags.set_needs_layer(true)
}
// Store the content block-size for use in calculating the absolute flow's dimensions
// later.
// 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;
@ -995,7 +1005,8 @@ impl BlockFlow {
loop {
match candidate_block_size_iterator.next() {
Some(candidate_block_size) => {
candidate_block_size_iterator.candidate_value = match candidate_block_size {
candidate_block_size_iterator.candidate_value =
match candidate_block_size {
Auto => block_size,
Specified(value) => value
}
@ -1026,6 +1037,13 @@ impl BlockFlow {
// 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);
}
}
if self.is_root_of_absolute_flow_tree() {
// Assign block-sizes for all flows in this absolute flow tree.
@ -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() {
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 {

View file

@ -27,7 +27,7 @@ use fragment::{InlineAbsoluteHypotheticalFragmentInfo, InlineBlockFragment};
use fragment::{InlineBlockFragmentInfo, InputFragment, SpecificFragmentInfo, TableCellFragment};
use fragment::{TableColumnFragment, TableColumnFragmentInfo, TableFragment, TableRowFragment};
use fragment::{TableWrapperFragment, UnscannedTextFragment, UnscannedTextFragmentInfo};
use incremental::RestyleDamage;
use incremental::{ReconstructFlow, RestyleDamage};
use inline::InlineFlow;
use parallel;
use table_wrapper::TableWrapperFlow;
@ -38,7 +38,7 @@ use table_rowgroup::TableRowGroupFlow;
use table_row::TableRowFlow;
use table_cell::TableCellFlow;
use text::TextRunScanner;
use util::{LayoutDataAccess, OpaqueNodeMethods, LayoutDataWrapper};
use util::{HasNewlyConstructedFlow, LayoutDataAccess, OpaqueNodeMethods, LayoutDataWrapper};
use wrapper::{PostorderNodeMutTraversal, TLayoutNode, ThreadSafeLayoutNode};
use wrapper::{Before, After, Normal};
@ -942,6 +942,44 @@ impl<'a> FlowConstructor<'a> {
FlowConstructionResult(flow, Descendants::new())
}
/// Attempts to perform incremental repair to account for recent changes to this node. This
/// can fail and return false, indicating that flows will need to be reconstructed.
///
/// TODO(pcwalton): Add some more fast paths, like toggling `display: none`, adding block kids
/// to block parents with no {ib} splits, adding out-of-flow kids, etc.
pub fn repair_if_possible(&mut self, node: &ThreadSafeLayoutNode) -> bool {
// We can skip reconstructing the flow if we don't have to reconstruct and none of our kids
// did either.
if node.restyle_damage().contains(ReconstructFlow) {
return false
}
let mut need_to_reconstruct = false;
for kid in node.children() {
if kid.flags().contains(HasNewlyConstructedFlow) {
kid.remove_flags(HasNewlyConstructedFlow);
need_to_reconstruct = true
}
}
if need_to_reconstruct {
return false
}
match node.swap_out_construction_result() {
NoConstructionResult => true,
FlowConstructionResult(mut flow, _) => {
// The node's flow is of the same type and has the same set of children and can
// therefore be repaired by simply propagating damage and style to the flow.
flow::mut_base(&mut *flow).restyle_damage.insert(node.restyle_damage());
flow.repair_style(node.style());
true
}
ConstructionItemConstructionResult(_) => {
false
}
}
}
}
impl<'a> PostorderNodeMutTraversal for FlowConstructor<'a> {
@ -1088,6 +1126,7 @@ impl<'a> PostorderNodeMutTraversal for FlowConstructor<'a> {
}
}
node.insert_flags(HasNewlyConstructedFlow);
true
}
}

View file

@ -5,11 +5,9 @@
//! High-level interface to CSS selector matching.
use css::node_style::StyledNode;
use incremental;
use incremental::RestyleDamage;
use incremental::{mod, RestyleDamage};
use util::{LayoutDataAccess, LayoutDataWrapper};
use wrapper::{LayoutElement, LayoutNode, ThreadSafeLayoutNode};
use wrapper::{TLayoutNode};
use wrapper::{LayoutElement, LayoutNode, TLayoutNode};
use script::dom::node::{TextNodeTypeId};
use servo_util::bloom::BloomFilter;
@ -339,8 +337,8 @@ pub enum StyleSharingResult<'ln> {
/// is shareable at all.
CannotShare(bool),
/// The node's style can be shared. The integer specifies the index in the LRU cache that was
/// hit.
StyleWasShared(uint),
/// hit and the damage that was done.
StyleWasShared(uint, RestyleDamage),
}
pub trait MatchMethods {
@ -385,7 +383,8 @@ trait PrivateMatchMethods {
style: &mut Option<Arc<ComputedValues>>,
applicable_declarations_cache: &mut
ApplicableDeclarationsCache,
shareable: bool) -> RestyleDamage;
shareable: bool)
-> RestyleDamage;
fn share_style_with_candidate_if_possible(&self,
parent_node: Option<LayoutNode>,
@ -400,7 +399,8 @@ impl<'ln> PrivateMatchMethods for LayoutNode<'ln> {
style: &mut Option<Arc<ComputedValues>>,
applicable_declarations_cache: &mut
ApplicableDeclarationsCache,
shareable: bool) -> RestyleDamage {
shareable: bool)
-> RestyleDamage {
let this_style;
let cacheable;
match parent_style {
@ -432,9 +432,10 @@ impl<'ln> PrivateMatchMethods for LayoutNode<'ln> {
applicable_declarations_cache.insert(applicable_declarations, this_style.clone());
}
let ret = incremental::compute_damage(&*style, this_style.deref());
// Calculate style difference and write.
let damage = incremental::compute_damage(style, &*this_style);
*style = Some(this_style);
ret
damage
}
@ -526,12 +527,11 @@ impl<'ln> MatchMethods for LayoutNode<'ln> {
Some(shared_style) => {
// Yay, cache hit. Share the style.
let mut layout_data_ref = self.mutate_layout_data();
let layout_data = layout_data_ref.as_mut().unwrap();
let style = &mut layout_data.shared_data.style;
layout_data.data.restyle_damage.insert(
incremental::compute_damage(&*style, &*shared_style));
let shared_data = &mut layout_data_ref.as_mut().unwrap().shared_data;
let style = &mut shared_data.style;
let damage = incremental::compute_damage(style, &*shared_style);
*style = Some(shared_style);
return StyleWasShared(i)
return StyleWasShared(i, damage)
}
None => {}
}
@ -585,8 +585,6 @@ impl<'ln> MatchMethods for LayoutNode<'ln> {
parent: Option<LayoutNode>,
applicable_declarations: &ApplicableDeclarations,
applicable_declarations_cache: &mut ApplicableDeclarationsCache) {
let mut restyle_damage = ThreadSafeLayoutNode::new(self).restyle_damage();
// Get our parent's style. This must be unsafe so that we don't touch the parent's
// borrow flags.
//
@ -602,10 +600,10 @@ impl<'ln> MatchMethods for LayoutNode<'ln> {
}
};
{
let mut layout_data_ref = self.mutate_layout_data();
let layout_data = layout_data_ref.as_mut().expect("no layout_data");
match &mut *layout_data_ref {
&None => fail!("no layout data"),
&Some(ref mut layout_data) => {
match self.type_id() {
Some(TextNodeTypeId) => {
// Text nodes get a copy of the parent style. This ensures
@ -613,40 +611,35 @@ impl<'ln> MatchMethods for LayoutNode<'ln> {
// CSS properties (such as vertical-align) are correctly
// set on the fragment(s).
let cloned_parent_style = parent_style.unwrap().clone();
restyle_damage.insert(
incremental::compute_damage(&layout_data.shared_data.style, &*cloned_parent_style));
layout_data.shared_data.style = Some(cloned_parent_style);
}
_ => {
restyle_damage.insert(
self.cascade_node_pseudo_element(
let mut damage = self.cascade_node_pseudo_element(
parent_style,
applicable_declarations.normal.as_slice(),
&mut layout_data.shared_data.style,
applicable_declarations_cache,
applicable_declarations.normal_shareable));
applicable_declarations.normal_shareable);
if applicable_declarations.before.len() > 0 {
restyle_damage.insert(
self.cascade_node_pseudo_element(
damage = damage | self.cascade_node_pseudo_element(
Some(layout_data.shared_data.style.as_ref().unwrap()),
applicable_declarations.before.as_slice(),
&mut layout_data.data.before_style,
applicable_declarations_cache,
false));
false);
}
if applicable_declarations.after.len() > 0 {
restyle_damage.insert(
self.cascade_node_pseudo_element(
damage = damage | self.cascade_node_pseudo_element(
Some(layout_data.shared_data.style.as_ref().unwrap()),
applicable_declarations.after.as_slice(),
&mut layout_data.data.after_style,
applicable_declarations_cache,
false));
false);
}
layout_data.data.restyle_damage = damage;
}
}
}
}
ThreadSafeLayoutNode::new(self).set_restyle_damage(restyle_damage);
}
}

View file

@ -5,7 +5,6 @@
// Style retrieval from DOM elements.
use css::node_util::NodeUtil;
use incremental::RestyleDamage;
use wrapper::ThreadSafeLayoutNode;
use style::ComputedValues;
@ -15,8 +14,6 @@ use sync::Arc;
pub trait StyledNode {
fn style<'a>(&'a self) -> &'a Arc<ComputedValues>;
fn unstyle(self);
fn restyle_damage(self) -> RestyleDamage;
fn set_restyle_damage(self, damage: RestyleDamage);
}
impl<'ln> StyledNode for ThreadSafeLayoutNode<'ln> {
@ -28,15 +25,4 @@ impl<'ln> StyledNode for ThreadSafeLayoutNode<'ln> {
fn unstyle(self) {
self.remove_css_select_results()
}
fn restyle_damage(self) -> RestyleDamage {
self.get_restyle_damage()
}
fn set_restyle_damage(self, damage: RestyleDamage) {
fn doit<N: NodeUtil>(n: N, damage: RestyleDamage) {
n.set_restyle_damage(damage);
}
doit(self, damage);
}
}

View file

@ -2,7 +2,6 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
use incremental::RestyleDamage;
use util::LayoutDataAccess;
use wrapper::ThreadSafeLayoutNode;
use wrapper::{After, Before, Normal};
@ -15,9 +14,6 @@ pub trait NodeUtil {
fn get_css_select_results<'a>(&'a self) -> &'a Arc<ComputedValues>;
fn have_css_select_results(&self) -> bool;
fn remove_css_select_results(self);
fn get_restyle_damage(self) -> RestyleDamage;
fn set_restyle_damage(self, damage: RestyleDamage);
}
impl<'ln> NodeUtil for ThreadSafeLayoutNode<'ln> {
@ -75,23 +71,5 @@ impl<'ln> NodeUtil for ThreadSafeLayoutNode<'ln> {
*style = None;
}
/// Get the description of how to account for recent style changes.
/// This is a simple bitfield and fine to copy by value.
fn get_restyle_damage(self) -> RestyleDamage {
let layout_data_ref = self.borrow_layout_data();
layout_data_ref
.as_ref().unwrap()
.data
.restyle_damage
}
/// Set the restyle damage field.
fn set_restyle_damage(self, damage: RestyleDamage) {
let mut layout_data_ref = self.mutate_layout_data();
match &mut *layout_data_ref {
&Some(ref mut layout_data) => layout_data.data.restyle_damage = damage,
_ => fail!("no layout data for this node"),
}
}
}

View file

@ -32,7 +32,7 @@ use floats::Floats;
use flow_list::{FlowList, FlowListIterator, MutFlowListIterator};
use flow_ref::FlowRef;
use fragment::{Fragment, TableRowFragment, TableCellFragment};
use incremental::RestyleDamage;
use incremental::{ReconstructFlow, Reflow, ReflowOutOfFlow, RestyleDamage};
use inline::InlineFlow;
use model::{CollapsibleMargins, IntrinsicISizes, MarginCollapseInfo};
use parallel::FlowParallelInfo;
@ -62,6 +62,8 @@ use std::raw;
use std::sync::atomics::{AtomicUint, SeqCst};
use std::slice::MutItems;
use style::computed_values::{clear, float, position, text_align};
use style::ComputedValues;
use sync::Arc;
/// Virtual methods that make up a float context.
///
@ -199,6 +201,7 @@ pub trait Flow: fmt::Show + ToString + Sync {
let impacted = base(&*self).flags.impacted_by_floats();
if impacted {
self.assign_block_size(layout_context);
mut_base(&mut *self).restyle_damage.remove(ReflowOutOfFlow | Reflow);
}
impacted
}
@ -289,6 +292,10 @@ pub trait Flow: fmt::Show + ToString + Sync {
LayerId(pointer, fragment_id)
}
}
/// Attempts to perform incremental fixup of this flow by replacing its fragment's style with
/// the new style. This can only succeed if the flow has exactly one fragment.
fn repair_style(&mut self, new_style: &Arc<ComputedValues>);
}
impl<'a, E, S: Encoder<E>> Encodable<S, E> for &'a Flow + 'a {
@ -420,8 +427,6 @@ pub trait MutableFlowUtils {
/// So, kids have their flow origin already set. In the case of absolute flow kids, they have
/// their hypothetical box position already set.
fn collect_static_block_offsets_from_children(self);
fn propagate_restyle_damage(self);
}
pub trait MutableOwnedFlowUtils {
@ -874,9 +879,13 @@ impl BaseFlow {
}
}
// New flows start out as fully damaged.
let mut damage = RestyleDamage::all();
damage.remove(ReconstructFlow);
BaseFlow {
ref_count: AtomicUint::new(1),
restyle_damage: RestyleDamage::all(),
restyle_damage: damage,
children: FlowList::new(),
intrinsic_inline_sizes: IntrinsicISizes::new(),
position: LogicalRect::zero(writing_mode),
@ -1209,47 +1218,6 @@ impl<'a> MutableFlowUtils for &'a mut Flow + 'a {
}
mut_base(self).abs_descendants.static_block_offsets = absolute_descendant_block_offsets
}
fn propagate_restyle_damage(self) {
struct DirtyFloats {
left: bool,
right: bool,
}
fn doit(flow: &mut Flow, down: RestyleDamage, dirty_floats: &mut DirtyFloats) -> RestyleDamage {
if base(flow).flags.clears_left() {
dirty_floats.left = false;
}
if base(flow).flags.clears_right() {
dirty_floats.right = false;
}
if base(flow).flags.floats_left() {
(*dirty_floats).left = true;
} else if base(flow).flags.floats_right() {
(*dirty_floats).right = true;
}
let mut my_damage = mut_base(flow).restyle_damage;
my_damage.insert(down);
if (*dirty_floats).left || (*dirty_floats).right {
my_damage = RestyleDamage::all();
}
let down_damage = my_damage.propagate_down();
for kid in child_iter(flow) {
my_damage.insert(doit(kid, down_damage, dirty_floats));
}
mut_base(flow).restyle_damage = my_damage;
my_damage.propagate_up()
}
doit(self, RestyleDamage::empty(), &mut DirtyFloats { left: false, right: false });
}
}
impl MutableOwnedFlowUtils for FlowRef {

View file

@ -1479,6 +1479,9 @@ impl Fragment {
}
}
pub fn repair_style(&mut self, new_style: &Arc<ComputedValues>) {
self.style = (*new_style).clone()
}
}
impl fmt::Show for Fragment {

View file

@ -2,8 +2,11 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
use flow::{mod, Flow};
use std::fmt;
use std::sync::Arc;
use style::computed_values::float;
use style::ComputedValues;
bitflags! {
@ -18,22 +21,67 @@ bitflags! {
#[doc = "bottom-up."]
static BubbleISizes = 0x02,
#[doc = "Recompute actual inline-sizes and block-sizes, only taking out-of-flow children \
into account. \
Propagates up the flow tree because the computation is top-down."]
static ReflowOutOfFlow = 0x04,
#[doc = "Recompute actual inline_sizes and block_sizes."]
#[doc = "Propagates up the flow tree because the computation is"]
#[doc = "top-down."]
static Reflow = 0x04
static Reflow = 0x08,
#[doc = "The entire flow needs to be reconstructed."]
static ReconstructFlow = 0x10
}
}
bitflags! {
flags SpecialRestyleDamage: u8 {
#[doc="If this flag is set, we need to reflow the entire document. This is more or less a \
temporary hack to deal with cases that we don't handle incrementally yet."]
static ReflowEntireDocument = 0x01,
}
}
impl RestyleDamage {
/// Elements of self which should also get set on any ancestor flow.
pub fn propagate_up(self) -> RestyleDamage {
self & Reflow
/// Supposing a flow has the given `position` property and this damage, returns the damage that
/// we should add to the *parent* of this flow.
pub fn damage_for_parent(self, child_is_absolutely_positioned: bool) -> RestyleDamage {
if child_is_absolutely_positioned {
self & (Repaint | ReflowOutOfFlow)
} else {
self & (Repaint | Reflow | ReflowOutOfFlow)
}
}
/// Elements of self which should also get set on any child flows.
pub fn propagate_down(self) -> RestyleDamage {
self & BubbleISizes
/// Supposing the *parent* of a flow with the given `position` property has this damage,
/// returns the damage that we should add to this flow.
pub fn damage_for_child(self,
parent_is_absolutely_positioned: bool,
child_is_absolutely_positioned: bool)
-> RestyleDamage {
match (parent_is_absolutely_positioned, child_is_absolutely_positioned) {
(false, true) => {
// Absolute children are out-of-flow and therefore insulated from changes.
//
// FIXME(pcwalton): Au contraire, if the containing block dimensions change!
self & Repaint
}
(true, false) => {
// Changing the position of an absolutely-positioned block requires us to reflow
// its kids.
if self.contains(ReflowOutOfFlow) {
self | Reflow
} else {
self
}
}
_ => {
// TODO(pcwalton): Take floatedness into account.
self & (Repaint | Reflow)
}
}
}
}
@ -44,7 +92,9 @@ impl fmt::Show for RestyleDamage {
let to_iter =
[ (Repaint, "Repaint")
, (BubbleISizes, "BubbleISizes")
, (ReflowOutOfFlow, "ReflowOutOfFlow")
, (Reflow, "Reflow")
, (ReconstructFlow, "ReconstructFlow")
];
for &(damage, damage_str) in to_iter.iter() {
@ -78,7 +128,7 @@ macro_rules! add_if_not_equal(
pub fn compute_damage(old: &Option<Arc<ComputedValues>>, new: &ComputedValues) -> RestyleDamage {
let old: &ComputedValues =
match old.as_ref() {
None => return Repaint | BubbleISizes | Reflow,
None => return RestyleDamage::all(),
Some(cv) => &**cv,
};
@ -90,23 +140,83 @@ pub fn compute_damage(old: &Option<Arc<ComputedValues>>, new: &ComputedValues) -
// FIXME: We can short-circuit more of this.
add_if_not_equal!(old, new, damage, [ Repaint ],
[ get_color.color, get_background.background_color,
add_if_not_equal!(old, new, damage,
[ Repaint ], [
get_color.color, get_background.background_color,
get_border.border_top_color, get_border.border_right_color,
get_border.border_bottom_color, get_border.border_left_color ]);
get_border.border_bottom_color, get_border.border_left_color
]);
add_if_not_equal!(old, new, damage, [ Repaint, BubbleISizes, Reflow ],
[ get_border.border_top_width, get_border.border_right_width,
add_if_not_equal!(old, new, damage,
[ Repaint, ReflowOutOfFlow ], [
get_positionoffsets.top, get_positionoffsets.left,
get_positionoffsets.right, get_positionoffsets.bottom
]);
add_if_not_equal!(old, new, damage,
[ Repaint, BubbleISizes, ReflowOutOfFlow, Reflow ], [
get_border.border_top_width, get_border.border_right_width,
get_border.border_bottom_width, get_border.border_left_width,
get_margin.margin_top, get_margin.margin_right,
get_margin.margin_bottom, get_margin.margin_left,
get_padding.padding_top, get_padding.padding_right,
get_padding.padding_bottom, get_padding.padding_left,
get_box.position, get_box.width, get_box.height, get_box.float, get_box.display,
get_box.width, get_box.height,
get_font.font_family, get_font.font_size, get_font.font_style, get_font.font_weight,
get_inheritedtext.text_align, get_text.text_decoration, get_inheritedbox.line_height ]);
get_inheritedtext.text_align, get_text.text_decoration, get_inheritedbox.line_height
]);
add_if_not_equal!(old, new, damage,
[ Repaint, BubbleISizes, ReflowOutOfFlow, Reflow, ReconstructFlow ],
[ get_box.float, get_box.display, get_box.position ]);
// FIXME: test somehow that we checked every CSS property
damage
}
pub trait LayoutDamageComputation {
fn compute_layout_damage(self) -> SpecialRestyleDamage;
fn reflow_entire_document(self);
}
impl<'a> LayoutDamageComputation for &'a mut Flow+'a {
fn compute_layout_damage(self) -> SpecialRestyleDamage {
let mut special_damage = SpecialRestyleDamage::empty();
let is_absolutely_positioned = flow::base(self).flags.is_absolutely_positioned();
{
let self_base = flow::mut_base(self);
for kid in self_base.children.iter_mut() {
let child_is_absolutely_positioned =
flow::base(kid).flags.is_absolutely_positioned();
flow::mut_base(kid).restyle_damage
.insert(self_base.restyle_damage.damage_for_child(
is_absolutely_positioned,
child_is_absolutely_positioned));
special_damage.insert(kid.compute_layout_damage());
self_base.restyle_damage
.insert(flow::base(kid).restyle_damage.damage_for_parent(
child_is_absolutely_positioned));
}
}
let self_base = flow::base(self);
if self_base.flags.float_kind() != float::none &&
self_base.restyle_damage.intersects(Reflow) {
special_damage.insert(ReflowEntireDocument);
}
special_damage
}
fn reflow_entire_document(self) {
let self_base = flow::mut_base(self);
self_base.restyle_damage.insert(RestyleDamage::all());
self_base.restyle_damage.remove(ReconstructFlow);
for kid in self_base.children.iter_mut() {
kid.reflow_entire_document();
}
}
}

View file

@ -12,6 +12,7 @@ use flow::{BaseFlow, FlowClass, Flow, InlineFlowClass, MutableFlowUtils};
use flow;
use fragment::{Fragment, InlineAbsoluteHypotheticalFragment, InlineBlockFragment};
use fragment::{ScannedTextFragment, ScannedTextFragmentInfo, SplitInfo};
use incremental::{Reflow, ReflowOutOfFlow};
use layout_debug;
use model::IntrinsicISizesContribution;
use text;
@ -1111,6 +1112,14 @@ impl Flow for InlineFlow {
line.bounds.size.block;
} // End of `lines.each` loop.
// Assign block sizes for any inline-block descendants.
for kid in self.base.child_iter() {
if flow::base(kid).flags.is_absolutely_positioned() || kid.is_float() {
continue
}
kid.assign_block_size_for_inorder_child_if_necessary(layout_context);
}
self.base.position.size.block = match self.lines.as_slice().last() {
Some(ref last_line) => last_line.bounds.start.b + last_line.bounds.size.block,
None => Au(0),
@ -1120,6 +1129,8 @@ impl Flow for InlineFlow {
self.base.floats.translate(LogicalSize::new(self.base.writing_mode,
Au(0),
-self.base.position.size.block));
self.base.restyle_damage.remove(ReflowOutOfFlow | Reflow);
}
fn compute_absolute_position(&mut self) {
@ -1210,6 +1221,8 @@ impl Flow for InlineFlow {
self.base.validate_display_list_geometry();
}
}
fn repair_style(&mut self, _: &Arc<ComputedValues>) {}
}
impl fmt::Show for InlineFlow {

View file

@ -8,10 +8,9 @@
use css::node_style::StyledNode;
use construct::FlowConstructionResult;
use context::SharedLayoutContext;
use flow::{Flow, ImmutableFlowUtils, MutableFlowUtils, MutableOwnedFlowUtils};
use flow;
use flow::{mod, Flow, ImmutableFlowUtils, MutableFlowUtils, MutableOwnedFlowUtils};
use flow_ref::FlowRef;
use incremental::{Reflow, Repaint};
use incremental::{LayoutDamageComputation, Reflow, ReflowEntireDocument, Repaint};
use layout_debug;
use parallel::UnsafeFlow;
use parallel;
@ -675,7 +674,10 @@ impl LayoutTask {
Some((&data.url, data.iframe, self.first_reflow.get())),
self.time_profiler_chan.clone(),
|| {
layout_root.propagate_restyle_damage();
if opts::get().nonincremental_layout ||
layout_root.compute_layout_damage().contains(ReflowEntireDocument) {
layout_root.reflow_entire_document()
}
});
// Verification of the flow tree, which ensures that all nodes were either marked as leaves

View file

@ -22,8 +22,9 @@ use servo_util::geometry::Au;
use servo_util::logical_geometry::LogicalRect;
use std::cmp::max;
use std::fmt;
use style::{ComputedValues, CSSFloat};
use style::computed_values::{LPA_Auto, LPA_Length, LPA_Percentage, table_layout};
use style::CSSFloat;
use sync::Arc;
/// A table flow corresponded to the table's internal table fragment under a table wrapper flow.
/// The properties `position`, `float`, and `margin-*` are used on the table wrapper fragment,
@ -322,6 +323,10 @@ impl Flow for TableFlow {
fn build_display_list(&mut self, layout_context: &LayoutContext) {
self.block_flow.build_display_list(layout_context);
}
fn repair_style(&mut self, new_style: &Arc<ComputedValues>) {
self.block_flow.repair_style(new_style)
}
}
impl fmt::Show for TableFlow {

View file

@ -14,6 +14,8 @@ use wrapper::ThreadSafeLayoutNode;
use servo_util::geometry::Au;
use std::fmt;
use style::ComputedValues;
use sync::Arc;
/// A table formatting context.
pub struct TableCaptionFlow {
@ -73,6 +75,10 @@ impl Flow for TableCaptionFlow {
debug!("build_display_list_table_caption: same process as block flow");
self.block_flow.build_display_list(layout_context)
}
fn repair_style(&mut self, new_style: &Arc<ComputedValues>) {
self.block_flow.repair_style(new_style)
}
}
impl fmt::Show for TableCaptionFlow {

View file

@ -17,6 +17,8 @@ use wrapper::ThreadSafeLayoutNode;
use servo_util::geometry::Au;
use std::fmt;
use style::ComputedValues;
use sync::Arc;
/// A table formatting context.
#[deriving(Encodable)]
@ -142,6 +144,10 @@ impl Flow for TableCellFlow {
fn build_display_list(&mut self, layout_context: &LayoutContext) {
self.block_flow.build_display_list(layout_context)
}
fn repair_style(&mut self, new_style: &Arc<ComputedValues>) {
self.block_flow.repair_style(new_style)
}
}
impl fmt::Show for TableCellFlow {

View file

@ -17,6 +17,8 @@ use servo_util::geometry::Au;
use std::cmp::max;
use std::fmt;
use style::computed_values::LengthOrPercentageOrAuto;
use style::ComputedValues;
use sync::Arc;
/// A table formatting context.
pub struct TableColGroupFlow {
@ -91,6 +93,8 @@ impl Flow for TableColGroupFlow {
// Table columns are invisible.
fn build_display_list(&mut self, _: &LayoutContext) {}
fn repair_style(&mut self, _: &Arc<ComputedValues>) {}
}
impl fmt::Show for TableColGroupFlow {

View file

@ -21,7 +21,9 @@ use wrapper::ThreadSafeLayoutNode;
use servo_util::geometry::Au;
use std::cmp::max;
use std::fmt;
use style::ComputedValues;
use style::computed_values::{LPA_Auto, LPA_Length, LPA_Percentage};
use sync::Arc;
/// A single row of a table.
#[deriving(Encodable)]
@ -245,6 +247,10 @@ impl Flow for TableRowFlow {
fn build_display_list(&mut self, layout_context: &LayoutContext) {
self.block_flow.build_display_list(layout_context)
}
fn repair_style(&mut self, new_style: &Arc<ComputedValues>) {
self.block_flow.repair_style(new_style)
}
}
impl fmt::Show for TableRowFlow {

View file

@ -20,6 +20,8 @@ use wrapper::ThreadSafeLayoutNode;
use servo_util::geometry::Au;
use std::fmt;
use style::ComputedValues;
use sync::Arc;
/// A table formatting context.
#[deriving(Encodable)]
@ -205,6 +207,10 @@ impl Flow for TableRowGroupFlow {
debug!("build_display_list_table_rowgroup: same process as block flow");
self.block_flow.build_display_list(layout_context)
}
fn repair_style(&mut self, new_style: &Arc<ComputedValues>) {
self.block_flow.repair_style(new_style)
}
}
impl fmt::Show for TableRowGroupFlow {

View file

@ -26,8 +26,9 @@ use wrapper::ThreadSafeLayoutNode;
use servo_util::geometry::Au;
use std::cmp::{max, min};
use std::fmt;
use style::CSSFloat;
use style::{ComputedValues, CSSFloat};
use style::computed_values::table_layout;
use sync::Arc;
#[deriving(Encodable)]
pub enum TableLayout {
@ -329,6 +330,10 @@ impl Flow for TableWrapperFlow {
fn build_display_list(&mut self, layout_context: &LayoutContext) {
self.block_flow.build_display_list(layout_context)
}
fn repair_style(&mut self, new_style: &Arc<ComputedValues>) {
self.block_flow.repair_style(new_style)
}
}
impl fmt::Show for TableWrapperFlow {

View file

@ -8,10 +8,10 @@ use css::node_style::StyledNode;
use css::matching::{ApplicableDeclarations, CannotShare, MatchMethods, StyleWasShared};
use construct::FlowConstructor;
use context::LayoutContext;
use flow::{Flow, ImmutableFlowUtils, MutableFlowUtils};
use flow::{Flow, MutableFlowUtils};
use flow::{PreorderFlowTraversal, PostorderFlowTraversal};
use flow;
use incremental::{RestyleDamage, BubbleISizes, Reflow};
use incremental::{RestyleDamage, BubbleISizes, Reflow, ReflowOutOfFlow};
use wrapper::{layout_node_to_unsafe_layout_node, LayoutNode};
use wrapper::{PostorderNodeMutTraversal, ThreadSafeLayoutNode, UnsafeLayoutNode};
use wrapper::{PreorderDomTraversal, PostorderDomTraversal};
@ -134,9 +134,10 @@ impl<'a> PreorderDomTraversal for RecalcStyleForNode<'a> {
// Just needs to be wrapped in an option for `match_node`.
let some_bf = Some(bf);
if node.is_dirty() || node.has_dirty_siblings() {
// Remove existing CSS styles from changed nodes, to force
// non-incremental reflow.
let nonincremental_layout = opts::get().nonincremental_layout;
if nonincremental_layout || node.is_dirty() {
// Remove existing CSS styles from nodes whose content has changed (e.g. text changed),
// to force non-incremental reflow.
if node.has_changed() {
let node = ThreadSafeLayoutNode::new(&node);
node.unstyle();
@ -161,6 +162,8 @@ impl<'a> PreorderDomTraversal for RecalcStyleForNode<'a> {
&some_bf,
&mut applicable_declarations,
&mut shareable);
} else {
ThreadSafeLayoutNode::new(&node).set_restyle_damage(RestyleDamage::all())
}
// Perform the CSS cascade.
@ -175,7 +178,10 @@ impl<'a> PreorderDomTraversal for RecalcStyleForNode<'a> {
style_sharing_candidate_cache.insert_if_possible(&node);
}
}
StyleWasShared(index) => style_sharing_candidate_cache.touch(index),
StyleWasShared(index, damage) => {
style_sharing_candidate_cache.touch(index);
ThreadSafeLayoutNode::new(&node).set_restyle_damage(damage);
}
}
}
@ -205,18 +211,16 @@ impl<'a> PostorderDomTraversal for ConstructFlows<'a> {
{
let tnode = ThreadSafeLayoutNode::new(&node);
// Always re-construct if incremental layout is turned off.
if opts::get().nonincremental_layout {
unsafe {
node.set_dirty_descendants(true);
}
}
if node.has_dirty_descendants() {
tnode.set_restyle_damage(RestyleDamage::all());
// Always reconstruct if incremental layout is turned off.
let nonincremental_layout = opts::get().nonincremental_layout;
if nonincremental_layout || node.has_dirty_descendants() {
let mut flow_constructor = FlowConstructor::new(self.layout_context);
if nonincremental_layout || !flow_constructor.repair_if_possible(&tnode) {
flow_constructor.process(&tnode);
debug!("Constructed flow for {:x}: {:x}", tnode.debug_id(), tnode.flow_debug_id());
debug!("Constructed flow for {:x}: {:x}",
tnode.debug_id(),
tnode.flow_debug_id());
}
}
// Reset the layout damage in this node. It's been propagated to the
@ -300,18 +304,12 @@ pub struct AssignISizes<'a> {
impl<'a> PreorderFlowTraversal for AssignISizes<'a> {
#[inline]
fn process(&self, flow: &mut Flow) {
if flow::base(flow).restyle_damage.contains(Reflow) {
flow.assign_inline_sizes(self.layout_context);
} else if flow.is_block_like() {
let block = flow.as_block();
block.propagate_and_compute_used_inline_size(self.layout_context);
}
}
#[inline]
fn should_process(&self, flow: &mut Flow) -> bool {
// TODO(cgaebel): Incremental inline size assignment.
flow::base(flow).restyle_damage.contains(Reflow) || true
flow::base(flow).restyle_damage.intersects(ReflowOutOfFlow | Reflow)
}
}
@ -326,26 +324,25 @@ pub struct AssignBSizesAndStoreOverflow<'a> {
impl<'a> PostorderFlowTraversal for AssignBSizesAndStoreOverflow<'a> {
#[inline]
fn process(&self, flow: &mut Flow) {
if !flow::base(flow).flags.impacted_by_floats() {
// Can't do anything with flows impacted by floats until we reach their inorder parent.
// NB: We must return without resetting the restyle bits for these, as we haven't actually
// reflowed anything!
if flow::base(flow).flags.impacted_by_floats() {
return
}
flow.assign_block_size(self.layout_context);
// Skip store-overflow for absolutely positioned flows. That will be
// done in a separate traversal.
if flow::base(flow).restyle_damage.contains(Reflow) {
if !flow.is_store_overflow_delayed() {
flow.store_overflow(self.layout_context);
}
}
}
flow::mut_base(flow).restyle_damage.remove(Reflow);
}
#[inline]
fn should_process(&self, flow: &mut Flow) -> bool {
// TODO(cgaebel): Incremental block size assignment.
flow::base(flow).restyle_damage.contains(Reflow) || true
flow::base(flow).restyle_damage.intersects(ReflowOutOfFlow | Reflow)
}
}

View file

@ -42,6 +42,9 @@ pub struct PrivateLayoutData {
/// Information needed during parallel traversals.
pub parallel: DomParallelInfo,
/// Various flags.
pub flags: LayoutDataFlags,
}
impl PrivateLayoutData {
@ -55,10 +58,18 @@ impl PrivateLayoutData {
before_flow_construction_result: NoConstructionResult,
after_flow_construction_result: NoConstructionResult,
parallel: DomParallelInfo::new(),
flags: LayoutDataFlags::empty(),
}
}
}
bitflags! {
flags LayoutDataFlags: u8 {
#[doc="Whether a flow has been newly constructed."]
static HasNewlyConstructedFlow = 0x01
}
}
pub struct LayoutDataWrapper {
pub chan: Option<LayoutChan>,
pub shared_data: SharedLayoutData,

View file

@ -32,7 +32,9 @@
use context::SharedLayoutContext;
use css::node_style::StyledNode;
use util::{LayoutDataAccess, LayoutDataWrapper, PrivateLayoutData, OpaqueNodeMethods};
use incremental::RestyleDamage;
use util::{LayoutDataAccess, LayoutDataFlags, LayoutDataWrapper, OpaqueNodeMethods};
use util::{PrivateLayoutData};
use gfx::display_list::OpaqueNode;
use script::dom::bindings::cell::{Ref, RefMut};
@ -803,7 +805,17 @@ impl<'ln> ThreadSafeLayoutNode<'ln> {
layout_data_wrapper_ref.data.after_style.is_some()
}
/// Borrows the layout data without checking. Fails on a conflicting borrow.
#[inline(always)]
fn borrow_layout_data_unchecked<'a>(&'a self) -> *const Option<LayoutDataWrapper> {
unsafe {
mem::transmute(self.get().layout_data_unchecked())
}
}
/// Borrows the layout data immutably. Fails on a conflicting borrow.
///
/// TODO(pcwalton): Make this private. It will let us avoid borrow flag checks in some cases.
#[inline(always)]
pub fn borrow_layout_data<'a>(&'a self) -> Ref<'a,Option<LayoutDataWrapper>> {
unsafe {
@ -812,6 +824,8 @@ impl<'ln> ThreadSafeLayoutNode<'ln> {
}
/// Borrows the layout data mutably. Fails on a conflicting borrow.
///
/// TODO(pcwalton): Make this private. It will let us avoid borrow flag checks in some cases.
#[inline(always)]
pub fn mutate_layout_data<'a>(&'a self) -> RefMut<'a,Option<LayoutDataWrapper>> {
unsafe {
@ -887,6 +901,50 @@ impl<'ln> ThreadSafeLayoutNode<'ln> {
}
}
}
/// Get the description of how to account for recent style changes.
/// This is a simple bitfield and fine to copy by value.
pub fn restyle_damage(self) -> RestyleDamage {
let layout_data_ref = self.borrow_layout_data();
layout_data_ref.as_ref().unwrap().data.restyle_damage
}
/// Set the restyle damage field.
pub fn set_restyle_damage(self, damage: RestyleDamage) {
let mut layout_data_ref = self.mutate_layout_data();
match &mut *layout_data_ref {
&Some(ref mut layout_data) => layout_data.data.restyle_damage = damage,
_ => fail!("no layout data for this node"),
}
}
/// Returns the layout data flags for this node.
pub fn flags(self) -> LayoutDataFlags {
unsafe {
match *self.borrow_layout_data_unchecked() {
None => fail!(),
Some(ref layout_data) => layout_data.data.flags,
}
}
}
/// Adds the given flags to this node.
pub fn insert_flags(self, new_flags: LayoutDataFlags) {
let mut layout_data_ref = self.mutate_layout_data();
match &mut *layout_data_ref {
&Some(ref mut layout_data) => layout_data.data.flags.insert(new_flags),
_ => fail!("no layout data for this node"),
}
}
/// Removes the given flags from this node.
pub fn remove_flags(self, flags: LayoutDataFlags) {
let mut layout_data_ref = self.mutate_layout_data();
match &mut *layout_data_ref {
&Some(ref mut layout_data) => layout_data.data.flags.remove(flags),
_ => fail!("no layout data for this node"),
}
}
}
pub struct ThreadSafeLayoutNodeChildrenIterator<'a> {