Reconstruct the box tree incrementally in some case

This commit is contained in:
Anthony Ramine 2020-06-01 16:06:07 +02:00
parent c4ea4b1d77
commit a30bdc16dd
2 changed files with 157 additions and 15 deletions

View file

@ -12,6 +12,7 @@ use crate::dom_traversal::{iter_child_nodes, Contents, NodeExt};
use crate::element_data::LayoutBox; use crate::element_data::LayoutBox;
use crate::flow::construct::ContainsFloats; use crate::flow::construct::ContainsFloats;
use crate::flow::float::FloatBox; use crate::flow::float::FloatBox;
use crate::flow::inline::InlineLevelBox;
use crate::flow::{BlockContainer, BlockFormattingContext, BlockLevelBox}; use crate::flow::{BlockContainer, BlockFormattingContext, BlockLevelBox};
use crate::formatting_contexts::IndependentFormattingContext; use crate::formatting_contexts::IndependentFormattingContext;
use crate::fragments::Fragment; use crate::fragments::Fragment;
@ -22,9 +23,11 @@ use crate::positioned::PositioningContext;
use crate::replaced::ReplacedContent; use crate::replaced::ReplacedContent;
use crate::sizing::ContentSizesRequest; use crate::sizing::ContentSizesRequest;
use crate::style_ext::ComputedValuesExt; use crate::style_ext::ComputedValuesExt;
use crate::style_ext::{Display, DisplayGeneratingBox}; use crate::style_ext::{Display, DisplayGeneratingBox, DisplayInside};
use crate::wrapper::GetStyleAndLayoutData;
use crate::DefiniteContainingBlock; use crate::DefiniteContainingBlock;
use app_units::Au; use app_units::Au;
use atomic_refcell::AtomicRef;
use euclid::default::{Point2D, Rect, Size2D}; use euclid::default::{Point2D, Rect, Size2D};
use fxhash::FxHashSet; use fxhash::FxHashSet;
use gfx_traits::print_tree::PrintTree; use gfx_traits::print_tree::PrintTree;
@ -88,6 +91,141 @@ impl BoxTree {
canvas_background: CanvasBackground::for_root_element(context, root_element), canvas_background: CanvasBackground::for_root_element(context, root_element),
} }
} }
/// This method attempts to incrementally update the box tree from an
/// arbitrary node that is not necessarily the document's root element.
///
/// If the node is not a valid candidate for incremental update, the method
/// loops over its parent. The only valid candidates for now are absolutely
/// positioned boxes which don't change their outside display mode (i.e. it
/// will not attempt to update from an absolutely positioned inline element
/// which became an absolutely positioned block element). The value `true`
/// is returned if an incremental update could be done, and `false`
/// otherwise.
///
/// There are various pain points that need to be taken care of to extend
/// the set of valid candidates:
/// * it is not obvious how to incrementally check whether a block
/// formatting context still contains floats or not;
/// * the propagation of text decorations towards node descendants is
/// hard to do incrementally with our current representation of boxes
/// * how intrinsic content sizes are computed eagerly makes it hard
/// to update those sizes for ancestors of the node from which we
/// made an incremental update.
pub fn update<'dom, Node>(context: &LayoutContext, mut dirty_node: Node) -> bool
where
Node: 'dom + Copy + LayoutNode<'dom> + Send + Sync,
{
enum UpdatePoint {
AbsolutelyPositionedBlockLevelBox(ArcRefCell<BlockLevelBox>),
AbsolutelyPositionedInlineLevelBox(ArcRefCell<InlineLevelBox>),
}
fn update_point<'dom, Node>(
node: Node,
) -> Option<(Arc<ComputedValues>, DisplayInside, UpdatePoint)>
where
Node: NodeExt<'dom>,
{
if !node.is_element() {
return None;
}
if node.type_id() == LayoutNodeType::Element(LayoutElementType::HTMLBodyElement) {
// This can require changes to the canvas background.
return None;
}
// Don't update unstyled nodes.
let data = node.get_style_and_layout_data()?;
// Don't update nodes that have pseudo-elements.
let element_data = data.style_data.element_data.borrow();
if !element_data.styles.pseudos.is_empty() {
return None;
}
let layout_data = data.layout_data.borrow();
if layout_data.pseudo_before_box.borrow().is_some() {
return None;
}
if layout_data.pseudo_after_box.borrow().is_some() {
return None;
}
let primary_style = element_data.styles.primary();
let box_style = primary_style.get_box();
if !box_style.position.is_absolutely_positioned() {
return None;
}
let display_inside = match Display::from(box_style.display) {
Display::GeneratingBox(DisplayGeneratingBox::OutsideInside { inside, .. }) => {
inside
},
_ => return None,
};
let update_point =
match &*AtomicRef::filter_map(layout_data.self_box.borrow(), Option::as_ref)? {
LayoutBox::DisplayContents => return None,
LayoutBox::BlockLevel(block_level_box) => match &*block_level_box.borrow() {
BlockLevelBox::OutOfFlowAbsolutelyPositionedBox(_)
if box_style.position.is_absolutely_positioned() =>
{
UpdatePoint::AbsolutelyPositionedBlockLevelBox(block_level_box.clone())
},
_ => return None,
},
LayoutBox::InlineLevel(inline_level_box) => match &*inline_level_box.borrow() {
InlineLevelBox::OutOfFlowAbsolutelyPositionedBox(_)
if box_style.position.is_absolutely_positioned() =>
{
UpdatePoint::AbsolutelyPositionedInlineLevelBox(
inline_level_box.clone(),
)
},
_ => return None,
},
};
Some((primary_style.clone(), display_inside, update_point))
}
loop {
if let Some((primary_style, display_inside, update_point)) = update_point(dirty_node) {
let contents = ReplacedContent::for_element(dirty_node)
.map_or(Contents::OfElement, Contents::Replaced);
let out_of_flow_absolutely_positioned_box =
Arc::new(AbsolutelyPositionedBox::construct(
context,
dirty_node,
primary_style,
display_inside,
contents,
));
match update_point {
UpdatePoint::AbsolutelyPositionedBlockLevelBox(block_level_box) => {
*block_level_box.borrow_mut() =
BlockLevelBox::OutOfFlowAbsolutelyPositionedBox(
out_of_flow_absolutely_positioned_box,
);
},
UpdatePoint::AbsolutelyPositionedInlineLevelBox(inline_level_box) => {
*inline_level_box.borrow_mut() =
InlineLevelBox::OutOfFlowAbsolutelyPositionedBox(
out_of_flow_absolutely_positioned_box,
);
},
}
return true;
}
dirty_node = match dirty_node.parent_node() {
Some(parent) => parent,
None => return false,
};
}
}
} }
fn construct_for_root_element<'dom>( fn construct_for_root_element<'dom>(

View file

@ -1081,39 +1081,43 @@ impl LayoutThread {
let rayon_pool = STYLE_THREAD_POOL.pool(); let rayon_pool = STYLE_THREAD_POOL.pool();
let rayon_pool = rayon_pool.as_ref(); let rayon_pool = rayon_pool.as_ref();
let box_tree = if token.should_traverse() { if token.should_traverse() {
driver::traverse_dom(&traversal, token, rayon_pool); let dirty_root = driver::traverse_dom(&traversal, token, rayon_pool).as_node();
let root_node = root_element.as_node(); let root_node = root_element.as_node();
let build_box_tree = || BoxTree::construct(traversal.context(), root_node); let mut box_tree = self.box_tree.borrow_mut();
let box_tree = if let Some(pool) = rayon_pool { let box_tree = &mut *box_tree;
let mut build_box_tree = || {
if !BoxTree::update(traversal.context(), dirty_root) {
*box_tree = Some(Arc::new(BoxTree::construct(traversal.context(), root_node)));
}
};
if let Some(pool) = rayon_pool {
pool.install(build_box_tree) pool.install(build_box_tree)
} else { } else {
build_box_tree() build_box_tree()
}; };
Some(Arc::new(box_tree))
} else {
None
};
layout_context = traversal.destroy();
if let Some(box_tree) = box_tree {
let viewport_size = Size2D::new( let viewport_size = Size2D::new(
self.viewport_size.width.to_f32_px(), self.viewport_size.width.to_f32_px(),
self.viewport_size.height.to_f32_px(), self.viewport_size.height.to_f32_px(),
); );
let run_layout = || box_tree.layout(&layout_context, viewport_size); let run_layout = || {
box_tree
.as_ref()
.unwrap()
.layout(traversal.context(), viewport_size)
};
let fragment_tree = Arc::new(if let Some(pool) = rayon_pool { let fragment_tree = Arc::new(if let Some(pool) = rayon_pool {
pool.install(run_layout) pool.install(run_layout)
} else { } else {
run_layout() run_layout()
}); });
*self.box_tree.borrow_mut() = Some(box_tree);
*self.fragment_tree.borrow_mut() = Some(fragment_tree); *self.fragment_tree.borrow_mut() = Some(fragment_tree);
} }
layout_context = traversal.destroy();
for element in elements_with_snapshot { for element in elements_with_snapshot {
unsafe { element.unset_snapshot_flags() } unsafe { element.unset_snapshot_flags() }
} }