mirror of
https://github.com/servo/servo.git
synced 2025-06-18 13:24:29 +00:00
First attempt at incremental layout
For now we only prune the bubble_widths traversal, because of inability to reuse FloatContexts. Other limitations are likewise marked with FIXME comments.
This commit is contained in:
parent
f582a76b4b
commit
ea5fb8c4a3
8 changed files with 312 additions and 2 deletions
|
@ -6,6 +6,7 @@
|
|||
|
||||
use css::node_util::NodeUtil;
|
||||
use css::select_handler::NodeSelectHandler;
|
||||
use layout::incremental;
|
||||
|
||||
use script::dom::node::{AbstractNode, LayoutView};
|
||||
use newcss::complete::CompleteSelectResults;
|
||||
|
@ -31,6 +32,13 @@ impl MatchMethods for AbstractNode<LayoutView> {
|
|||
let incomplete_results = select_ctx.select_style(self, &select_handler);
|
||||
// Combine this node's results with its parent's to resolve all inherited values
|
||||
let complete_results = compose_results(*self, incomplete_results);
|
||||
|
||||
// If there was an existing style, compute the damage that
|
||||
// incremental layout will need to fix.
|
||||
if self.have_css_select_results() {
|
||||
let damage = incremental::compute_damage(self, self.get_css_select_results(), &complete_results);
|
||||
self.set_restyle_damage(damage);
|
||||
}
|
||||
self.set_css_select_results(complete_results);
|
||||
}
|
||||
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
// Style retrieval from DOM elements.
|
||||
|
||||
use css::node_util::NodeUtil;
|
||||
use layout::incremental::RestyleDamage;
|
||||
|
||||
use newcss::complete::CompleteStyle;
|
||||
use script::dom::node::{AbstractNode, LayoutView};
|
||||
|
@ -12,6 +13,7 @@ use script::dom::node::{AbstractNode, LayoutView};
|
|||
/// Node mixin providing `style` method that returns a `NodeStyle`
|
||||
pub trait StyledNode {
|
||||
fn style(&self) -> CompleteStyle;
|
||||
fn restyle_damage(&self) -> RestyleDamage;
|
||||
}
|
||||
|
||||
impl StyledNode for AbstractNode<LayoutView> {
|
||||
|
@ -20,4 +22,8 @@ impl StyledNode for AbstractNode<LayoutView> {
|
|||
let results = self.get_css_select_results();
|
||||
results.computed_style()
|
||||
}
|
||||
|
||||
fn restyle_damage(&self) -> RestyleDamage {
|
||||
self.get_restyle_damage()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use layout::aux::LayoutAuxMethods;
|
||||
use layout::incremental::RestyleDamage;
|
||||
|
||||
use std::cast::transmute;
|
||||
use newcss::complete::CompleteSelectResults;
|
||||
|
@ -11,6 +12,10 @@ use script::dom::node::{AbstractNode, LayoutView};
|
|||
pub trait NodeUtil<'self> {
|
||||
fn get_css_select_results(self) -> &'self CompleteSelectResults;
|
||||
fn set_css_select_results(self, decl: CompleteSelectResults);
|
||||
fn have_css_select_results(self) -> bool;
|
||||
|
||||
fn get_restyle_damage(self) -> RestyleDamage;
|
||||
fn set_restyle_damage(self, damage: RestyleDamage);
|
||||
}
|
||||
|
||||
impl<'self> NodeUtil<'self> for AbstractNode<LayoutView> {
|
||||
|
@ -32,6 +37,11 @@ impl<'self> NodeUtil<'self> for AbstractNode<LayoutView> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Does this node have a computed style yet?
|
||||
fn have_css_select_results(self) -> bool {
|
||||
self.has_layout_data() && self.layout_data().style.is_some()
|
||||
}
|
||||
|
||||
/// Update the computed style of an HTML element with a style specified by CSS.
|
||||
fn set_css_select_results(self, decl: CompleteSelectResults) {
|
||||
if !self.has_layout_data() {
|
||||
|
@ -40,4 +50,30 @@ impl<'self> NodeUtil<'self> for AbstractNode<LayoutView> {
|
|||
|
||||
self.layout_data().style = Some(decl);
|
||||
}
|
||||
|
||||
/// 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 {
|
||||
// For DOM elements, if we haven't computed damage yet, assume the worst.
|
||||
// Other nodes don't have styles.
|
||||
let default = if self.is_element() {
|
||||
RestyleDamage::all()
|
||||
} else {
|
||||
RestyleDamage::none()
|
||||
};
|
||||
|
||||
if !self.has_layout_data() {
|
||||
return default;
|
||||
}
|
||||
self.layout_data().restyle_damage.get_or_default(default)
|
||||
}
|
||||
|
||||
/// Set the restyle damage field.
|
||||
fn set_restyle_damage(self, damage: RestyleDamage) {
|
||||
if !self.has_layout_data() {
|
||||
fail!(~"set_restyle_damage() called on a node without aux data!");
|
||||
}
|
||||
|
||||
self.layout_data().restyle_damage = Some(damage);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
//! Code for managing the layout data in the DOM.
|
||||
|
||||
use layout::flow::FlowContext;
|
||||
use layout::incremental::RestyleDamage;
|
||||
|
||||
use newcss::complete::CompleteSelectResults;
|
||||
use script::dom::node::{AbstractNode, LayoutView};
|
||||
|
@ -15,6 +16,9 @@ pub struct LayoutData {
|
|||
/// The results of CSS styling for this node.
|
||||
style: Option<CompleteSelectResults>,
|
||||
|
||||
/// Description of how to account for recent style changes.
|
||||
restyle_damage: Option<RestyleDamage>,
|
||||
|
||||
/// The CSS flow that this node is associated with.
|
||||
flow: Option<FlowContext>,
|
||||
}
|
||||
|
@ -24,6 +28,7 @@ impl LayoutData {
|
|||
pub fn new() -> LayoutData {
|
||||
LayoutData {
|
||||
style: None,
|
||||
restyle_damage: None,
|
||||
flow: None,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -32,6 +32,8 @@ use layout::context::LayoutContext;
|
|||
use layout::display_list_builder::{DisplayListBuilder, ExtraDisplayListData};
|
||||
use layout::inline::{InlineFlowData};
|
||||
use layout::float_context::{FloatContext, Invalid, FloatType};
|
||||
use layout::incremental::RestyleDamage;
|
||||
use css::node_style::StyledNode;
|
||||
|
||||
use std::cell::Cell;
|
||||
use std::uint;
|
||||
|
@ -155,6 +157,7 @@ impl TreeNodeRef<FlowData> for FlowContext {
|
|||
/// `CommonFlowInfo`?
|
||||
pub struct FlowData {
|
||||
node: AbstractNode<LayoutView>,
|
||||
restyle_damage: RestyleDamage,
|
||||
|
||||
parent: Option<FlowContext>,
|
||||
first_child: Option<FlowContext>,
|
||||
|
@ -223,6 +226,7 @@ impl FlowData {
|
|||
pub fn new(id: int, node: AbstractNode<LayoutView>) -> FlowData {
|
||||
FlowData {
|
||||
node: node,
|
||||
restyle_damage: node.restyle_damage(),
|
||||
|
||||
parent: None,
|
||||
first_child: None,
|
||||
|
@ -262,6 +266,15 @@ impl<'self> FlowContext {
|
|||
}
|
||||
}
|
||||
|
||||
/// A convenience method to return the restyle damage of this flow. Fails if the flow is
|
||||
/// currently being borrowed mutably.
|
||||
#[inline(always)]
|
||||
pub fn restyle_damage(&self) -> RestyleDamage {
|
||||
do self.with_base |info| {
|
||||
info.restyle_damage
|
||||
}
|
||||
}
|
||||
|
||||
pub fn inline(&self) -> @mut InlineFlowData {
|
||||
match *self {
|
||||
InlineFlow(info) => info,
|
||||
|
@ -446,7 +459,8 @@ impl<'self> FlowContext {
|
|||
};
|
||||
|
||||
do self.with_base |base| {
|
||||
fmt!("f%? %? floats %? size %?", base.id, repr, base.num_floats, base.position)
|
||||
fmt!("f%? %? floats %? size %? damage %?", base.id, repr, base.num_floats,
|
||||
base.position, base.restyle_damage)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
198
src/components/main/layout/incremental.rs
Normal file
198
src/components/main/layout/incremental.rs
Normal file
|
@ -0,0 +1,198 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* 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 newcss::complete::CompleteSelectResults;
|
||||
|
||||
use script::dom::node::{AbstractNode, LayoutView};
|
||||
|
||||
/// Individual layout actions that may be necessary after restyling.
|
||||
///
|
||||
/// If you add to this enum, also add the value to RestyleDamage::all below.
|
||||
/// (FIXME: do this automatically)
|
||||
pub enum RestyleEffect {
|
||||
/// Repaint the node itself.
|
||||
/// Currently unused; need to decide how this propagates.
|
||||
Repaint = 0x01,
|
||||
|
||||
/// Recompute intrinsic widths (minimum and preferred).
|
||||
/// Propagates down the flow tree because the computation is
|
||||
/// bottom-up.
|
||||
BubbleWidths = 0x02,
|
||||
|
||||
/// Recompute actual widths and heights.
|
||||
/// Propagates up the flow tree because the computation is
|
||||
/// top-down.
|
||||
Reflow = 0x04,
|
||||
}
|
||||
|
||||
/// A set of RestyleEffects.
|
||||
// FIXME: Switch to librustc/util/enum_set.rs if that gets moved into
|
||||
// libextra (Rust #8054)
|
||||
pub struct RestyleDamage {
|
||||
priv bits: int
|
||||
}
|
||||
|
||||
// Provide literal syntax of the form restyle_damage!(Repaint, Reflow)
|
||||
macro_rules! restyle_damage(
|
||||
( $($damage:ident),* ) => (
|
||||
RestyleDamage::none() $( .add($damage) )*
|
||||
)
|
||||
)
|
||||
|
||||
impl RestyleDamage {
|
||||
pub fn none() -> RestyleDamage {
|
||||
RestyleDamage { bits: 0 }
|
||||
}
|
||||
|
||||
pub fn all() -> RestyleDamage {
|
||||
restyle_damage!(Repaint, BubbleWidths, Reflow)
|
||||
}
|
||||
|
||||
/// Effects of resizing the window.
|
||||
pub fn for_resize() -> RestyleDamage {
|
||||
RestyleDamage::all()
|
||||
}
|
||||
|
||||
pub fn is_empty(self) -> bool {
|
||||
self.bits == 0
|
||||
}
|
||||
|
||||
pub fn is_nonempty(self) -> bool {
|
||||
self.bits != 0
|
||||
}
|
||||
|
||||
pub fn add(self, effect: RestyleEffect) -> RestyleDamage {
|
||||
RestyleDamage { bits: self.bits | (effect as int) }
|
||||
}
|
||||
|
||||
pub fn has(self, effect: RestyleEffect) -> bool {
|
||||
(self.bits & (effect as int)) != 0
|
||||
}
|
||||
|
||||
pub fn lacks(self, effect: RestyleEffect) -> bool {
|
||||
(self.bits & (effect as int)) == 0
|
||||
}
|
||||
|
||||
pub fn union(self, other: RestyleDamage) -> RestyleDamage {
|
||||
RestyleDamage { bits: self.bits | other.bits }
|
||||
}
|
||||
|
||||
pub fn union_in_place(&mut self, other: RestyleDamage) {
|
||||
self.bits = self.bits | other.bits;
|
||||
}
|
||||
|
||||
pub fn intersect(self, other: RestyleDamage) -> RestyleDamage {
|
||||
RestyleDamage { bits: self.bits & other.bits }
|
||||
}
|
||||
|
||||
/// Elements of self which should also get set on any ancestor flow.
|
||||
pub fn propagate_up(self) -> RestyleDamage {
|
||||
self.intersect(restyle_damage!(Reflow))
|
||||
}
|
||||
|
||||
/// Elements of self which should also get set on any child flows.
|
||||
pub fn propagate_down(self) -> RestyleDamage {
|
||||
self.intersect(restyle_damage!(BubbleWidths))
|
||||
}
|
||||
}
|
||||
|
||||
// NB: We need the braces inside the RHS due to Rust #8012. This particular
|
||||
// version of this macro might be safe anyway, but we want to avoid silent
|
||||
// breakage on modifications.
|
||||
macro_rules! add_if_not_equal(
|
||||
([ $($effect:ident),* ], [ $($getter:ident),* ]) => ({
|
||||
if $( (old.$getter() != new.$getter()) )||* {
|
||||
damage.union_in_place( restyle_damage!( $($effect),* ) );
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
pub fn compute_damage(node: &AbstractNode<LayoutView>,
|
||||
old_results: &CompleteSelectResults, new_results: &CompleteSelectResults)
|
||||
-> RestyleDamage {
|
||||
let old = old_results.computed_style();
|
||||
let new = new_results.computed_style();
|
||||
let mut damage = RestyleDamage::none();
|
||||
|
||||
// This checks every CSS property, as enumerated in
|
||||
// impl<'self> CssComputedStyle<'self>
|
||||
// in src/support/netsurfcss/rust-netsurfcss/netsurfcss.rc.
|
||||
|
||||
// FIXME: We can short-circuit more of this.
|
||||
|
||||
add_if_not_equal!([ Repaint ],
|
||||
[ color, background_color, border_top_color, border_right_color,
|
||||
border_bottom_color, border_left_color ]);
|
||||
|
||||
add_if_not_equal!([ Repaint, BubbleWidths, Reflow ],
|
||||
[ border_top_width, border_right_width, border_bottom_width,
|
||||
border_left_width, margin_top, margin_right, margin_bottom, margin_left,
|
||||
padding_top, padding_right, padding_bottom, padding_left, position,
|
||||
width, height, float, font_family, font_size, font_style, font_weight,
|
||||
text_align, text_decoration, line_height ]);
|
||||
|
||||
// Handle 'display' specially because it has this 'is_root' parameter.
|
||||
let is_root = node.is_root();
|
||||
if old.display(is_root) != new.display(is_root) {
|
||||
damage.union_in_place(restyle_damage!(Repaint, BubbleWidths, Reflow));
|
||||
}
|
||||
|
||||
// FIXME: test somehow that we checked every CSS property
|
||||
|
||||
damage
|
||||
}
|
||||
|
||||
|
||||
#[cfg(test)]
|
||||
mod restyle_damage_tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn none_is_empty() {
|
||||
let d = RestyleDamage::none();
|
||||
assert!(!d.has(Repaint));
|
||||
assert!(!d.has(BubbleWidths));
|
||||
assert!(d.lacks(Repaint));
|
||||
assert!(d.lacks(BubbleWidths));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn all_is_full() {
|
||||
let d = RestyleDamage::all();
|
||||
assert!(d.has(Repaint));
|
||||
assert!(d.has(BubbleWidths));
|
||||
assert!(!d.lacks(Repaint));
|
||||
assert!(!d.lacks(BubbleWidths));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_add() {
|
||||
assert!(RestyleDamage::none().add(BubbleWidths).has(BubbleWidths));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_union() {
|
||||
let d = restyle_damage!(Repaint).union(restyle_damage!(BubbleWidths));
|
||||
assert!(d.has(Repaint));
|
||||
assert!(d.has(BubbleWidths));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_union_in_place() {
|
||||
let mut d = restyle_damage!(Repaint);
|
||||
d.union_in_place(restyle_damage!(BubbleWidths));
|
||||
assert!(d.has(Repaint));
|
||||
assert!(d.has(BubbleWidths));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_intersect() {
|
||||
let x = restyle_damage!(Repaint, BubbleWidths);
|
||||
let y = restyle_damage!(Repaint, Reflow);
|
||||
let d = x.intersect(y);
|
||||
assert!(d.has(Repaint));
|
||||
assert!(d.lacks(BubbleWidths));
|
||||
assert!(d.lacks(Reflow));
|
||||
}
|
||||
}
|
|
@ -13,6 +13,7 @@ use layout::box_builder::LayoutTreeBuilder;
|
|||
use layout::context::LayoutContext;
|
||||
use layout::display_list_builder::{DisplayListBuilder};
|
||||
use layout::flow::FlowContext;
|
||||
use layout::incremental::{RestyleDamage, BubbleWidths};
|
||||
|
||||
use std::cast::transmute;
|
||||
use std::cell::Cell;
|
||||
|
@ -193,6 +194,8 @@ impl LayoutTask {
|
|||
self.doc_url = Some(doc_url);
|
||||
let screen_size = Size2D(Au::from_px(data.window_size.width as int),
|
||||
Au::from_px(data.window_size.height as int));
|
||||
let resized = self.screen_size != Some(screen_size);
|
||||
debug!("resized: %?", resized);
|
||||
self.screen_size = Some(screen_size);
|
||||
|
||||
// Create a layout context for use throughout the following passes.
|
||||
|
@ -227,20 +230,59 @@ impl LayoutTask {
|
|||
layout_root
|
||||
};
|
||||
|
||||
// Propagate restyle damage up and down the tree, as appropriate.
|
||||
// FIXME: Merge this with flow tree building and/or the other traversals.
|
||||
for layout_root.traverse_preorder |flow| {
|
||||
// Also set any damage implied by resize.
|
||||
if resized {
|
||||
do flow.with_mut_base |base| {
|
||||
base.restyle_damage.union_in_place(RestyleDamage::for_resize());
|
||||
}
|
||||
}
|
||||
|
||||
let prop = flow.with_base(|base| base.restyle_damage.propagate_down());
|
||||
if prop.is_nonempty() {
|
||||
for flow.each_child |kid_ctx| {
|
||||
do kid_ctx.with_mut_base |kid| {
|
||||
kid.restyle_damage.union_in_place(prop);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for layout_root.traverse_postorder |flow| {
|
||||
do flow.with_base |base| {
|
||||
match base.parent {
|
||||
None => {},
|
||||
Some(parent_ctx) => {
|
||||
let prop = base.restyle_damage.propagate_up();
|
||||
do parent_ctx.with_mut_base |parent| {
|
||||
parent.restyle_damage.union_in_place(prop);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
debug!("layout: constructed Flow tree");
|
||||
debug!("%?", layout_root.dump());
|
||||
|
||||
// Perform the primary layout passes over the flow tree to compute the locations of all
|
||||
// the boxes.
|
||||
do profile(time::LayoutMainCategory, self.profiler_chan.clone()) {
|
||||
for layout_root.traverse_postorder |flow| {
|
||||
for layout_root.traverse_postorder_prune(|f| f.restyle_damage().lacks(BubbleWidths)) |flow| {
|
||||
flow.bubble_widths(&mut layout_ctx);
|
||||
};
|
||||
|
||||
// FIXME: We want to do
|
||||
// for layout_root.traverse_preorder_prune(|f| f.restyle_damage().lacks(Reflow)) |flow| {
|
||||
// but FloatContext values can't be reused, so we need to recompute them every time.
|
||||
for layout_root.traverse_preorder |flow| {
|
||||
flow.assign_widths(&mut layout_ctx);
|
||||
};
|
||||
|
||||
// For now, this is an inorder traversal
|
||||
// FIXME: prune this traversal as well
|
||||
layout_root.assign_height(&mut layout_ctx);
|
||||
}
|
||||
|
||||
|
|
|
@ -82,6 +82,7 @@ pub mod layout {
|
|||
pub mod model;
|
||||
pub mod text;
|
||||
pub mod util;
|
||||
pub mod incremental;
|
||||
mod aux;
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue