diff --git a/components/layout/animation.rs b/components/layout/animation.rs index b14771e5de9..7fdc452ccc3 100644 --- a/components/layout/animation.rs +++ b/components/layout/animation.rs @@ -13,7 +13,6 @@ use script_traits::{AnimationState, ConstellationControlMsg, LayoutMsg as Conste use std::collections::HashMap; use std::sync::mpsc::Receiver; use style::animation::{Animation, update_style_for_animation}; -use style::dom::TRestyleDamage; use style::selector_parser::RestyleDamage; use style::timer::Timer; diff --git a/components/layout/context.rs b/components/layout/context.rs index cb5b9efb0ef..b8d7c8680df 100644 --- a/components/layout/context.rs +++ b/components/layout/context.rs @@ -18,6 +18,7 @@ use net_traits::image_cache_thread::{ImageCacheChan, ImageCacheThread, ImageResp use net_traits::image_cache_thread::{ImageOrMetadataAvailable, UsePlaceholder}; use parking_lot::RwLock; use servo_url::ServoUrl; +use std::borrow::Borrow; use std::cell::{RefCell, RefMut}; use std::collections::HashMap; use std::hash::BuildHasherDefault; @@ -88,6 +89,12 @@ pub struct SharedLayoutContext { BuildHasherDefault>>>, } +impl Borrow for SharedLayoutContext { + fn borrow(&self) -> &SharedStyleContext { + &self.style_context + } +} + pub struct LayoutContext<'a> { pub shared: &'a SharedLayoutContext, cached_local_layout_context: Rc, diff --git a/components/layout/flow.rs b/components/layout/flow.rs index df7a6510ff2..34fcd3bf300 100644 --- a/components/layout/flow.rs +++ b/components/layout/flow.rs @@ -50,7 +50,6 @@ use std::sync::Arc; use std::sync::atomic::Ordering; use style::computed_values::{clear, float, overflow_x, position, text_align}; use style::context::SharedStyleContext; -use style::dom::TRestyleDamage; use style::logical_geometry::{LogicalRect, LogicalSize, WritingMode}; use style::properties::ServoComputedValues; use style::selector_parser::RestyleDamage; diff --git a/components/layout/fragment.rs b/components/layout/fragment.rs index 104c45fd918..f2ba5e83916 100644 --- a/components/layout/fragment.rs +++ b/components/layout/fragment.rs @@ -44,7 +44,6 @@ use style::computed_values::{overflow_wrap, overflow_x, position, text_decoratio use style::computed_values::{transform_style, vertical_align, white_space, word_break, z_index}; use style::computed_values::content::ContentItem; use style::context::SharedStyleContext; -use style::dom::TRestyleDamage; use style::logical_geometry::{Direction, LogicalMargin, LogicalRect, LogicalSize, WritingMode}; use style::properties::ServoComputedValues; use style::selector_parser::RestyleDamage; diff --git a/components/layout/generated_content.rs b/components/layout/generated_content.rs index d3eed080a8f..fe5ca470506 100644 --- a/components/layout/generated_content.rs +++ b/components/layout/generated_content.rs @@ -19,7 +19,6 @@ use std::collections::{HashMap, LinkedList}; use std::sync::Arc; use style::computed_values::{display, list_style_type}; use style::computed_values::content::ContentItem; -use style::dom::TRestyleDamage; use style::properties::ServoComputedValues; use style::selector_parser::RestyleDamage; use style::servo::restyle_damage::RESOLVE_GENERATED_CONTENT; diff --git a/components/layout/incremental.rs b/components/layout/incremental.rs index e38f55bec08..d24cda3307b 100644 --- a/components/layout/incremental.rs +++ b/components/layout/incremental.rs @@ -4,7 +4,6 @@ use flow::{self, AFFECTS_COUNTERS, Flow, HAS_COUNTER_AFFECTING_CHILDREN, IS_ABSOLUTELY_POSITIONED}; use style::computed_values::float; -use style::dom::TRestyleDamage; use style::selector_parser::RestyleDamage; use style::servo::restyle_damage::{REFLOW, RECONSTRUCT_FLOW}; diff --git a/components/layout/traversal.rs b/components/layout/traversal.rs index e20d3ab98ef..04ea73056aa 100644 --- a/components/layout/traversal.rs +++ b/components/layout/traversal.rs @@ -15,7 +15,7 @@ use std::mem; use style::atomic_refcell::AtomicRefCell; use style::context::{LocalStyleContext, SharedStyleContext, StyleContext}; use style::data::ElementData; -use style::dom::{StylingMode, TElement, TNode}; +use style::dom::{TElement, TNode}; use style::selector_parser::RestyleDamage; use style::servo::restyle_damage::{BUBBLE_ISIZES, REFLOW, REFLOW_OUT_OF_FLOW, REPAINT}; use style::traversal::{DomTraversalContext, recalc_style_at, remove_from_bloom_filter}; @@ -73,14 +73,15 @@ impl<'lc, N> DomTraversalContext for RecalcStyleAndConstructFlows<'lc> } } - fn process_preorder(&self, node: N, data: &mut PerLevelTraversalData) { + fn process_preorder(&self, node: N, traversal_data: &mut PerLevelTraversalData) { // FIXME(pcwalton): Stop allocating here. Ideally this should just be // done by the HTML parser. node.initialize_data(); if !node.is_text_node() { let el = node.as_element().unwrap(); - recalc_style_at::<_, _, Self>(&self.context, data, el); + let mut data = el.mutate_data().unwrap(); + recalc_style_at::<_, _, Self>(&self.context, traversal_data, el, &mut data); } } @@ -88,19 +89,13 @@ impl<'lc, N> DomTraversalContext for RecalcStyleAndConstructFlows<'lc> construct_flows_at(&self.context, self.root, node); } - fn should_traverse_child(child: N) -> bool { - match child.as_element() { - // Elements should be traversed if they need styling or flow construction. - Some(el) => el.styling_mode() != StylingMode::Stop || - el.as_node().to_threadsafe().restyle_damage() != RestyleDamage::empty(), - - // Text nodes never need styling. However, there are two cases they may need - // flow construction: - // (1) They child doesn't yet have layout data (preorder traversal initializes it). - // (2) The parent element has restyle damage (so the text flow also needs fixup). - None => child.get_raw_data().is_none() || - child.parent_node().unwrap().to_threadsafe().restyle_damage() != RestyleDamage::empty(), - } + fn text_node_needs_traversal(node: N) -> bool { + // Text nodes never need styling. However, there are two cases they may need + // flow construction: + // (1) They child doesn't yet have layout data (preorder traversal initializes it). + // (2) The parent element has restyle damage (so the text flow also needs fixup). + node.get_raw_data().is_none() || + node.parent_node().unwrap().to_threadsafe().restyle_damage() != RestyleDamage::empty() } unsafe fn ensure_element_data(element: &N::ConcreteElement) -> &AtomicRefCell { diff --git a/components/layout_thread/lib.rs b/components/layout_thread/lib.rs index 04f35183ff4..934a2e3a092 100644 --- a/components/layout_thread/lib.rs +++ b/components/layout_thread/lib.rs @@ -106,7 +106,7 @@ use std::sync::mpsc::{Receiver, Sender, channel}; use style::animation::Animation; use style::context::{LocalStyleContextCreationInfo, ReflowGoal, SharedStyleContext}; use style::data::StoredRestyleHint; -use style::dom::{StylingMode, TElement, TNode}; +use style::dom::{TElement, TNode}; use style::error_reporting::{ParseErrorReporter, StdoutErrorReporter}; use style::logical_geometry::LogicalPoint; use style::media_queries::{Device, MediaType}; @@ -116,6 +116,7 @@ use style::stylesheets::{Origin, Stylesheet, UserAgentStylesheets}; use style::stylist::Stylist; use style::thread_state; use style::timer::Timer; +use style::traversal::DomTraversalContext; use util::geometry::max_rect; use util::opts; use util::prefs::PREFS; @@ -1122,7 +1123,7 @@ impl LayoutThread { None => continue, }; let mut style_data = &mut data.base.style_data; - debug_assert!(!style_data.is_restyle()); + debug_assert!(style_data.has_current_styles()); let mut restyle_data = match style_data.restyle() { Some(d) => d, None => continue, @@ -1131,7 +1132,9 @@ impl LayoutThread { // Stash the data on the element for processing by the style system. restyle_data.hint = restyle.hint.into(); restyle_data.damage = restyle.damage; - restyle_data.snapshot = restyle.snapshot; + if let Some(s) = restyle.snapshot { + restyle_data.snapshot.ensure(move || s); + } debug!("Noting restyle for {:?}: {:?}", el, restyle_data); } } @@ -1142,7 +1145,9 @@ impl LayoutThread { data.reflow_info.goal); let dom_depth = Some(0); // This is always the root node. - if element.styling_mode() != StylingMode::Stop { + let token = > + ::pre_traverse(element, &shared_layout_context.style_context.stylist, /* skip_root = */ false); + if token.should_traverse() { // Recalculate CSS styles and rebuild flows and fragments. profile(time::ProfilerCategory::LayoutStyleRecalc, self.profiler_metadata(), @@ -1152,11 +1157,11 @@ impl LayoutThread { if let (true, Some(traversal)) = (self.parallel_flag, self.parallel_traversal.as_mut()) { // Parallel mode parallel::traverse_dom::( - element.as_node(), dom_depth, &shared_layout_context, traversal); + element, dom_depth, &shared_layout_context, token, traversal); } else { // Sequential mode sequential::traverse_dom::( - element.as_node(), &shared_layout_context); + element, &shared_layout_context, token); } }); // TODO(pcwalton): Measure energy usage of text shaping, perhaps? diff --git a/components/script/dom/element.rs b/components/script/dom/element.rs index d9831d79a69..67e5584b0f0 100644 --- a/components/script/dom/element.rs +++ b/components/script/dom/element.rs @@ -97,7 +97,6 @@ use std::sync::Arc; use std::sync::atomic::{AtomicUsize, Ordering}; use style::attr::{AttrValue, LengthOrPercentageOrAuto}; use style::context::ReflowGoal; -use style::dom::TRestyleDamage; use style::element_state::*; use style::matching::{common_style_affecting_attributes, rare_style_affecting_attributes}; use style::parser::ParserContextExtraData; diff --git a/components/script/layout_wrapper.rs b/components/script/layout_wrapper.rs index 338edc19730..ab69e8f2cab 100644 --- a/components/script/layout_wrapper.rs +++ b/components/script/layout_wrapper.rs @@ -816,7 +816,7 @@ impl<'ln> ThreadSafeLayoutNode for ServoThreadSafeLayoutNode<'ln> { debug_assert!(self.is_text_node()); let parent = self.node.parent_node().unwrap().as_element().unwrap(); let parent_data = parent.get_data().unwrap().borrow(); - parent_data.current_styles().primary.values.clone() + parent_data.styles().primary.values.clone() } fn debug_id(self) -> usize { diff --git a/components/script_layout_interface/wrapper_traits.rs b/components/script_layout_interface/wrapper_traits.rs index 1d1f32bf4b6..6887fb4f416 100644 --- a/components/script_layout_interface/wrapper_traits.rs +++ b/components/script_layout_interface/wrapper_traits.rs @@ -317,7 +317,7 @@ pub trait ThreadSafeLayoutElement: Clone + Copy + Sized + Debug + if self.get_style_data() .unwrap() .borrow() - .current_styles().pseudos + .styles().pseudos .contains_key(&PseudoElement::Before) { Some(self.with_pseudo(PseudoElementType::Before(None))) } else { @@ -330,7 +330,7 @@ pub trait ThreadSafeLayoutElement: Clone + Copy + Sized + Debug + if self.get_style_data() .unwrap() .borrow() - .current_styles().pseudos + .styles().pseudos .contains_key(&PseudoElement::After) { Some(self.with_pseudo(PseudoElementType::After(None))) } else { @@ -371,7 +371,7 @@ pub trait ThreadSafeLayoutElement: Clone + Copy + Sized + Debug + fn style(&self, context: &SharedStyleContext) -> Arc { match self.get_pseudo_element_type() { PseudoElementType::Normal => self.get_style_data().unwrap().borrow() - .current_styles().primary.values.clone(), + .styles().primary.values.clone(), other => { // Precompute non-eagerly-cascaded pseudo-element styles if not // cached before. @@ -383,14 +383,14 @@ pub trait ThreadSafeLayoutElement: Clone + Copy + Sized + Debug + if !self.get_style_data() .unwrap() .borrow() - .current_styles().pseudos.contains_key(&style_pseudo) { + .styles().pseudos.contains_key(&style_pseudo) { let mut data = self.get_style_data().unwrap().borrow_mut(); let new_style = context.stylist.precomputed_values_for_pseudo( &style_pseudo, - Some(&data.current_styles().primary.values), + Some(&data.styles().primary.values), false); - data.current_styles_mut().pseudos + data.styles_mut().pseudos .insert(style_pseudo.clone(), new_style.unwrap()); } } @@ -398,22 +398,22 @@ pub trait ThreadSafeLayoutElement: Clone + Copy + Sized + Debug + if !self.get_style_data() .unwrap() .borrow() - .current_styles().pseudos.contains_key(&style_pseudo) { + .styles().pseudos.contains_key(&style_pseudo) { let mut data = self.get_style_data().unwrap().borrow_mut(); let new_style = context.stylist .lazily_compute_pseudo_element_style( self, &style_pseudo, - &data.current_styles().primary.values); - data.current_styles_mut().pseudos + &data.styles().primary.values); + data.styles_mut().pseudos .insert(style_pseudo.clone(), new_style.unwrap()); } } } self.get_style_data().unwrap().borrow() - .current_styles().pseudos.get(&style_pseudo) + .styles().pseudos.get(&style_pseudo) .unwrap().values.clone() } } @@ -422,9 +422,9 @@ pub trait ThreadSafeLayoutElement: Clone + Copy + Sized + Debug + #[inline] fn selected_style(&self) -> Arc { let data = self.get_style_data().unwrap().borrow(); - data.current_styles().pseudos + data.styles().pseudos .get(&PseudoElement::Selection).map(|s| s) - .unwrap_or(&data.current_styles().primary) + .unwrap_or(&data.styles().primary) .values.clone() } @@ -440,9 +440,9 @@ pub trait ThreadSafeLayoutElement: Clone + Copy + Sized + Debug + let data = self.get_style_data().unwrap().borrow(); match self.get_pseudo_element_type() { PseudoElementType::Normal - => data.current_styles().primary.values.clone(), + => data.styles().primary.values.clone(), other - => data.current_styles().pseudos + => data.styles().pseudos .get(&other.style_pseudo_element()).unwrap().values.clone(), } } diff --git a/components/style/context.rs b/components/style/context.rs index 6785d752478..d5819555cf6 100644 --- a/components/style/context.rs +++ b/components/style/context.rs @@ -38,14 +38,6 @@ pub struct SharedStyleContext { /// Screen sized changed? pub screen_size_changed: bool, - /// Skip the root during traversal? - /// - /// This is used in Gecko to style newly-appended children without restyling - /// the parent. It would be cleaner to add an API to allow us to enqueue the - /// children directly from glue.rs. - #[cfg(feature = "gecko")] - pub skip_root: bool, - /// The CSS selector stylist. pub stylist: Arc, diff --git a/components/style/data.rs b/components/style/data.rs index b3049bba144..6aa68dc4ef2 100644 --- a/components/style/data.rs +++ b/components/style/data.rs @@ -4,10 +4,10 @@ //! Per-node data used in style calculation. -use dom::TRestyleDamage; +use dom::TElement; use properties::ComputedValues; use properties::longhands::display::computed_value as display; -use restyle_hints::RestyleHint; +use restyle_hints::{RESTYLE_LATER_SIBLINGS, RestyleHint}; use rule_tree::StrongRuleNode; use selector_parser::{PseudoElement, RestyleDamage, Snapshot}; use std::collections::HashMap; @@ -16,6 +16,8 @@ use std::hash::BuildHasherDefault; use std::mem; use std::ops::{Deref, DerefMut}; use std::sync::Arc; +use stylist::Stylist; +use thread_state; #[derive(Clone)] pub struct ComputedStyle { @@ -133,7 +135,7 @@ impl StoredRestyleHint { /// Propagates this restyle hint to a child element. pub fn propagate(&self) -> Self { StoredRestyleHint { - restyle_self: self.descendants == DescendantRestyleHint::Empty, + restyle_self: self.descendants != DescendantRestyleHint::Empty, descendants: self.descendants.propagate(), } } @@ -183,10 +185,50 @@ impl From for StoredRestyleHint { } } +// We really want to store an Option here, but we can't drop Gecko +// Snapshots off-main-thread. So we make a convenient little wrapper to provide +// the semantics of Option, while deferring the actual drop. +static NO_SNAPSHOT: Option = None; + #[derive(Debug)] -pub enum RestyleDataStyles { - Previous(ElementStyles), - New(ElementStyles), +pub struct SnapshotOption { + snapshot: Option, + destroyed: bool, +} + +impl SnapshotOption { + pub fn empty() -> Self { + SnapshotOption { + snapshot: None, + destroyed: false, + } + } + + pub fn destroy(&mut self) { + self.destroyed = true; + debug_assert!(self.is_none()); + } + + pub fn ensure Snapshot>(&mut self, create: F) -> &mut Snapshot { + debug_assert!(thread_state::get().is_layout()); + if self.is_none() { + self.snapshot = Some(create()); + self.destroyed = false; + } + + self.snapshot.as_mut().unwrap() + } +} + +impl Deref for SnapshotOption { + type Target = Option; + fn deref(&self) -> &Option { + if self.destroyed { + &NO_SNAPSHOT + } else { + &self.snapshot + } + } } /// Transient data used by the restyle algorithm. This structure is instantiated @@ -194,54 +236,71 @@ pub enum RestyleDataStyles { /// processing. #[derive(Debug)] pub struct RestyleData { - pub styles: RestyleDataStyles, + pub styles: ElementStyles, pub hint: StoredRestyleHint, + pub recascade: bool, pub damage: RestyleDamage, - pub snapshot: Option, + pub snapshot: SnapshotOption, } impl RestyleData { - fn new(previous: ElementStyles) -> Self { + fn new(styles: ElementStyles) -> Self { RestyleData { - styles: RestyleDataStyles::Previous(previous), + styles: styles, hint: StoredRestyleHint::default(), + recascade: false, damage: RestyleDamage::empty(), - snapshot: None, + snapshot: SnapshotOption::empty(), } } - pub fn get_current_styles(&self) -> Option<&ElementStyles> { - use self::RestyleDataStyles::*; - match self.styles { - Previous(_) => None, - New(ref x) => Some(x), + /// Expands the snapshot (if any) into a restyle hint. Returns true if later siblings + /// must be restyled. + pub fn expand_snapshot(&mut self, element: E, stylist: &Stylist) -> bool { + if self.snapshot.is_none() { + return false; } + + // Compute the hint. + let state = element.get_state(); + let mut hint = stylist.compute_restyle_hint(&element, + self.snapshot.as_ref().unwrap(), + state); + + // If the hint includes a directive for later siblings, strip it out and + // notify the caller to modify the base hint for future siblings. + let later_siblings = hint.contains(RESTYLE_LATER_SIBLINGS); + hint.remove(RESTYLE_LATER_SIBLINGS); + + // Insert the hint. + self.hint.insert(&hint.into()); + + // Destroy the snapshot. + self.snapshot.destroy(); + + later_siblings } - pub fn current_styles(&self) -> &ElementStyles { - self.get_current_styles().unwrap() + pub fn has_current_styles(&self) -> bool { + !(self.hint.restyle_self || self.recascade || self.snapshot.is_some()) } - pub fn current_styles_mut(&mut self) -> &mut ElementStyles { - use self::RestyleDataStyles::*; - match self.styles { - New(ref mut x) => x, - Previous(_) => panic!("Calling current_styles_mut before styling"), - } + pub fn styles(&self) -> &ElementStyles { + &self.styles } - pub fn current_or_previous_styles(&self) -> &ElementStyles { - use self::RestyleDataStyles::*; - match self.styles { - Previous(ref x) => x, - New(ref x) => x, - } + pub fn styles_mut(&mut self) -> &mut ElementStyles { + &mut self.styles } fn finish_styling(&mut self, styles: ElementStyles, damage: RestyleDamage) { - debug_assert!(self.get_current_styles().is_none()); - self.styles = RestyleDataStyles::New(styles); + debug_assert!(!self.has_current_styles()); + debug_assert!(self.snapshot.is_none(), "Traversal should have expanded snapshots"); + self.styles = styles; self.damage |= damage; + // The hint and recascade bits get cleared by the traversal code. This + // is a bit confusing, and we should simplify it when we separate matching + // from cascading. } } @@ -363,10 +422,7 @@ impl ElementData { let old = mem::replace(self, ElementData::new(None)); let styles = match old { ElementData::Initial(i) => i.unwrap(), - ElementData::Restyle(r) => match r.styles { - RestyleDataStyles::New(n) => n, - RestyleDataStyles::Previous(_) => panic!("Never restyled element"), - }, + ElementData::Restyle(r) => r.styles, ElementData::Persistent(_) => unreachable!(), }; *self = ElementData::Persistent(styles); @@ -380,7 +436,7 @@ impl ElementData { RestyleDamage::rebuild_and_reflow() }, Restyle(ref r) => { - debug_assert!(r.get_current_styles().is_some()); + debug_assert!(r.has_current_styles()); r.damage }, Persistent(_) => RestyleDamage::empty(), @@ -400,7 +456,7 @@ impl ElementData { RestyleDamage::rebuild_and_reflow() }, Restyle(ref r) => { - if r.get_current_styles().is_none() { + if !r.has_current_styles() { error!("Accessing damage on dirty element"); } r.damage @@ -409,61 +465,41 @@ impl ElementData { } } - pub fn current_styles(&self) -> &ElementStyles { - self.get_current_styles().unwrap() + /// Returns true if this element's style is up-to-date and has no potential + /// invalidation. + pub fn has_current_styles(&self) -> bool { + use self::ElementData::*; + match *self { + Initial(ref x) => x.is_some(), + Restyle(ref x) => x.has_current_styles(), + Persistent(_) => true, + } } - pub fn get_current_styles(&self) -> Option<&ElementStyles> { + pub fn get_styles(&self) -> Option<&ElementStyles> { use self::ElementData::*; match *self { Initial(ref x) => x.as_ref(), - Restyle(ref x) => x.get_current_styles(), + Restyle(ref x) => Some(x.styles()), Persistent(ref x) => Some(x), } } - pub fn current_styles_mut(&mut self) -> &mut ElementStyles { + pub fn styles(&self) -> &ElementStyles { + self.get_styles().expect("Calling styles() on unstyled ElementData") + } + + pub fn get_styles_mut(&mut self) -> Option<&mut ElementStyles> { use self::ElementData::*; match *self { - Initial(ref mut x) => x.as_mut().unwrap(), - Restyle(ref mut x) => x.current_styles_mut(), - Persistent(ref mut x) => x, + Initial(ref mut x) => x.as_mut(), + Restyle(ref mut x) => Some(x.styles_mut()), + Persistent(ref mut x) => Some(x), } } - pub fn previous_styles(&self) -> Option<&ElementStyles> { - use self::ElementData::*; - use self::RestyleDataStyles::*; - match *self { - Initial(_) => None, - Restyle(ref x) => match x.styles { - Previous(ref styles) => Some(styles), - New(_) => panic!("Calling previous_styles after finish_styling"), - }, - Persistent(_) => panic!("Calling previous_styles on Persistent ElementData"), - } - } - - pub fn previous_styles_mut(&mut self) -> Option<&mut ElementStyles> { - use self::ElementData::*; - use self::RestyleDataStyles::*; - match *self { - Initial(_) => None, - Restyle(ref mut x) => match x.styles { - Previous(ref mut styles) => Some(styles), - New(_) => panic!("Calling previous_styles after finish_styling"), - }, - Persistent(_) => panic!("Calling previous_styles on Persistent ElementData"), - } - } - - pub fn current_or_previous_styles(&self) -> &ElementStyles { - use self::ElementData::*; - match *self { - Initial(ref x) => x.as_ref().unwrap(), - Restyle(ref x) => x.current_or_previous_styles(), - Persistent(ref x) => x, - } + pub fn styles_mut(&mut self) -> &mut ElementStyles { + self.get_styles_mut().expect("Calling styles_mut() on unstyled ElementData") } pub fn finish_styling(&mut self, styles: ElementStyles, damage: RestyleDamage) { diff --git a/components/style/dom.rs b/components/style/dom.rs index 5c66bbc0e0f..8431f5526c3 100644 --- a/components/style/dom.rs +++ b/components/style/dom.rs @@ -8,17 +8,15 @@ use {Atom, Namespace, LocalName}; use atomic_refcell::{AtomicRef, AtomicRefCell, AtomicRefMut}; -use data::{ElementStyles, ElementData}; +use data::ElementData; use element_state::ElementState; use parking_lot::RwLock; use properties::{ComputedValues, PropertyDeclarationBlock}; -use selector_parser::{ElementExt, PseudoElement, RestyleDamage}; +use selector_parser::{ElementExt, PreExistingComputedValues, PseudoElement}; use sink::Push; use std::fmt::Debug; -use std::ops::{BitOr, BitOrAssign}; use std::sync::Arc; use stylist::ApplicableDeclarationBlock; -use util::opts; pub use style_traits::UnsafeNode; @@ -43,42 +41,6 @@ impl OpaqueNode { } } -#[derive(Clone, Copy, Debug, PartialEq)] -pub enum StylingMode { - /// The node has never been styled before, and needs a full style computation. - Initial, - /// The node has been styled before, but needs some amount of recomputation. - Restyle, - /// The node does not need any style processing, but one or more of its - /// descendants do. - Traverse, - /// No nodes in this subtree require style processing. - Stop, -} - -pub trait TRestyleDamage : BitOr + BitOrAssign + Copy + Debug + PartialEq { - /// The source for our current computed values in the cascade. This is a - /// ComputedValues in Servo and a StyleContext in Gecko. - /// - /// This is needed because Gecko has a few optimisations for the calculation - /// of the difference depending on which values have been used during - /// layout. - /// - /// This should be obtained via TNode::existing_style_for_restyle_damage - type PreExistingComputedValues; - - fn compute(old: &Self::PreExistingComputedValues, - new: &Arc) -> Self; - - fn empty() -> Self; - - fn rebuild_and_reflow() -> Self; - - fn is_empty(&self) -> bool { - *self == Self::empty() - } -} - /// Simple trait to provide basic information about the type of an element. /// /// We avoid exposing the full type id, since computing it in the general case @@ -174,7 +136,7 @@ pub trait TElement : PartialEq + Debug + Sized + Copy + Clone + ElementExt + Pre fn existing_style_for_restyle_damage<'a>(&'a self, current_computed_values: Option<&'a Arc>, pseudo: Option<&PseudoElement>) - -> Option<&'a ::PreExistingComputedValues>; + -> Option<&'a PreExistingComputedValues>; /// Returns true if this element may have a descendant needing style processing. /// @@ -201,60 +163,11 @@ pub trait TElement : PartialEq + Debug + Sized + Copy + Clone + ElementExt + Pre /// traversal. Returns the number of children left to process. fn did_process_child(&self) -> isize; - /// Returns true if this element's current style is display:none. Only valid - /// to call after styling. + /// Returns true if this element's style is display:none. fn is_display_none(&self) -> bool { - self.borrow_data().unwrap().current_styles().is_display_none() - } - - /// Returns true if this node has a styled layout frame that owns the style. - fn frame_has_style(&self) -> bool { false } - - /// Returns the styles from the layout frame that owns them, if any. - /// - /// FIXME(bholley): Once we start dropping ElementData from nodes when - /// creating frames, we'll want to teach this method to actually get - /// style data from the frame. - fn get_styles_from_frame(&self) -> Option { None } - - /// Returns the styling mode for this node. This is only valid to call before - /// and during restyling, before finish_styling is invoked. - /// - /// See the comments around StylingMode. - fn styling_mode(&self) -> StylingMode { - use self::StylingMode::*; - - // Non-incremental layout impersonates Initial. - if opts::get().nonincremental_layout { - return Initial; - } - - // Compute the default result if this node doesn't require processing. - let mode_for_descendants = if self.has_dirty_descendants() { - Traverse - } else { - Stop - }; - - match self.borrow_data() { - // No element data, no style on the frame. - None if !self.frame_has_style() => Initial, - // No element data, style on the frame. - None => mode_for_descendants, - // We have element data. Decide below. - Some(d) => match *d { - ElementData::Restyle(_) => Restyle, - ElementData::Persistent(_) => mode_for_descendants, - ElementData::Initial(None) => Initial, - // We previously computed the initial style for this element - // and then never consumed it. This is arguably a bug, since - // it means we either styled an element unnecessarily, or missed - // an opportunity to coalesce style traversals. However, this - // happens now for various reasons, so we just let it slide and - // treat it as persistent for now. - ElementData::Initial(Some(_)) => mode_for_descendants, - }, - } + let data = self.borrow_data().unwrap(); + debug_assert!(data.has_current_styles()); + data.styles().is_display_none() } /// Gets a reference to the ElementData container. diff --git a/components/style/gecko/restyle_damage.rs b/components/style/gecko/restyle_damage.rs index 53523cac04a..a1c7fc2c4e9 100644 --- a/components/style/gecko/restyle_damage.rs +++ b/components/style/gecko/restyle_damage.rs @@ -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 dom::TRestyleDamage; use gecko_bindings::bindings; use gecko_bindings::structs; use gecko_bindings::structs::{nsChangeHint, nsStyleContext}; @@ -22,17 +21,17 @@ impl GeckoRestyleDamage { pub fn as_change_hint(&self) -> nsChangeHint { self.0 } -} -impl TRestyleDamage for GeckoRestyleDamage { - type PreExistingComputedValues = nsStyleContext; - - fn empty() -> Self { + pub fn empty() -> Self { GeckoRestyleDamage(nsChangeHint(0)) } - fn compute(source: &nsStyleContext, - new_style: &Arc) -> Self { + pub fn is_empty(&self) -> bool { + self.0 == nsChangeHint(0) + } + + pub fn compute(source: &nsStyleContext, + new_style: &Arc) -> Self { let context = source as *const nsStyleContext as *mut nsStyleContext; let hint = unsafe { bindings::Gecko_CalcStyleDifference(context, @@ -41,7 +40,7 @@ impl TRestyleDamage for GeckoRestyleDamage { GeckoRestyleDamage(hint) } - fn rebuild_and_reflow() -> Self { + pub fn rebuild_and_reflow() -> Self { GeckoRestyleDamage(structs::nsChangeHint_nsChangeHint_ReconstructFrame) } } diff --git a/components/style/gecko/snapshot.rs b/components/style/gecko/snapshot.rs index bcb0874c853..08d5288a8d0 100644 --- a/components/style/gecko/snapshot.rs +++ b/components/style/gecko/snapshot.rs @@ -17,6 +17,10 @@ use string_cache::Atom; #[derive(Debug)] pub struct GeckoElementSnapshot(bindings::ServoElementSnapshotOwned); +// FIXME(bholley): Add support for *OwnedConst type, and then we get Sync +// automatically. +unsafe impl Sync for GeckoElementSnapshot {} + impl Drop for GeckoElementSnapshot { fn drop(&mut self) { unsafe { diff --git a/components/style/gecko/traversal.rs b/components/style/gecko/traversal.rs index cc6bed8f79b..43e3ab8b861 100644 --- a/components/style/gecko/traversal.rs +++ b/components/style/gecko/traversal.rs @@ -5,7 +5,7 @@ use atomic_refcell::AtomicRefCell; use context::{LocalStyleContext, SharedStyleContext, StyleContext}; use data::ElementData; -use dom::{NodeInfo, OpaqueNode, StylingMode, TElement, TNode}; +use dom::{NodeInfo, OpaqueNode, TNode}; use gecko::context::StandaloneStyleContext; use gecko::wrapper::{GeckoElement, GeckoNode}; use std::mem; @@ -13,26 +13,25 @@ use traversal::{DomTraversalContext, PerLevelTraversalData, recalc_style_at}; pub struct RecalcStyleOnly<'lc> { context: StandaloneStyleContext<'lc>, - root: OpaqueNode, } impl<'lc, 'ln> DomTraversalContext> for RecalcStyleOnly<'lc> { type SharedContext = SharedStyleContext; #[allow(unsafe_code)] - fn new<'a>(shared: &'a Self::SharedContext, root: OpaqueNode) -> Self { + fn new<'a>(shared: &'a Self::SharedContext, _root: OpaqueNode) -> Self { // See the comment in RecalcStyleAndConstructFlows::new for an explanation of why this is // necessary. let shared_lc: &'lc Self::SharedContext = unsafe { mem::transmute(shared) }; RecalcStyleOnly { context: StandaloneStyleContext::new(shared_lc), - root: root, } } - fn process_preorder(&self, node: GeckoNode<'ln>, data: &mut PerLevelTraversalData) { - if node.is_element() && (!self.context.shared_context().skip_root || node.opaque() != self.root) { + fn process_preorder(&self, node: GeckoNode<'ln>, traversal_data: &mut PerLevelTraversalData) { + if node.is_element() { let el = node.as_element().unwrap(); - recalc_style_at::<_, _, Self>(&self.context, data, el); + let mut data = unsafe { el.ensure_data() }.borrow_mut(); + recalc_style_at::<_, _, Self>(&self.context, traversal_data, el, &mut data); } } @@ -41,14 +40,7 @@ impl<'lc, 'ln> DomTraversalContext> for RecalcStyleOnly<'lc> { } /// We don't use the post-order traversal for anything. - fn needs_postorder_traversal(&self) -> bool { false } - - fn should_traverse_child(child: GeckoNode<'ln>) -> bool { - match child.as_element() { - Some(el) => el.styling_mode() != StylingMode::Stop, - None => false, // Gecko restyle doesn't need to traverse text nodes. - } - } + fn needs_postorder_traversal() -> bool { false } unsafe fn ensure_element_data<'a>(element: &'a GeckoElement<'ln>) -> &'a AtomicRefCell { element.ensure_data() diff --git a/components/style/gecko/wrapper.rs b/components/style/gecko/wrapper.rs index 4d9683241a8..f8629a24fe3 100644 --- a/components/style/gecko/wrapper.rs +++ b/components/style/gecko/wrapper.rs @@ -260,10 +260,10 @@ impl<'le> GeckoElement<'le> { } pub fn get_pseudo_style(&self, pseudo: &PseudoElement) -> Option> { - // NB: Gecko sometimes resolves pseudos after an element has already been - // marked for restyle. We should consider fixing this, but for now just allow - // it with current_or_previous_styles. - self.borrow_data().and_then(|data| data.current_or_previous_styles().pseudos + // FIXME(bholley): Gecko sometimes resolves pseudos after an element has + // already been marked for restyle. We should consider fixing this, and + // then assert has_current_styles here. + self.borrow_data().and_then(|data| data.styles().pseudos .get(pseudo).map(|c| c.values.clone())) } @@ -273,8 +273,7 @@ impl<'le> GeckoElement<'le> { Some(x) => x, None => { debug!("Creating ElementData for {:?}", self); - let existing = self.get_styles_from_frame(); - let ptr = Box::into_raw(Box::new(AtomicRefCell::new(ElementData::new(existing)))); + let ptr = Box::into_raw(Box::new(AtomicRefCell::new(ElementData::new(None)))); self.0.mServoData.set(ptr); unsafe { &* ptr } }, diff --git a/components/style/matching.rs b/components/style/matching.rs index 3b0a396507d..26b563291ba 100644 --- a/components/style/matching.rs +++ b/components/style/matching.rs @@ -13,7 +13,7 @@ use cache::LRUCache; use cascade_info::CascadeInfo; use context::{SharedStyleContext, StyleContext}; use data::{ComputedStyle, ElementData, ElementStyles, PseudoStyles}; -use dom::{TElement, TNode, TRestyleDamage, UnsafeNode}; +use dom::{TElement, TNode, UnsafeNode}; use properties::{CascadeFlags, ComputedValues, SHAREABLE, SKIP_ROOT_AND_ITEM_BASED_DISPLAY_FIXUP, cascade}; use properties::longhands::display::computed_value as display; use rule_tree::StrongRuleNode; @@ -186,7 +186,8 @@ fn element_matches_candidate(element: &E, } let data = candidate_element.borrow_data().unwrap(); - let current_styles = data.current_styles(); + debug_assert!(data.has_current_styles()); + let current_styles = data.styles(); Ok(current_styles.primary.clone()) } @@ -600,7 +601,8 @@ pub trait MatchMethods : TElement { // can decide more easily if it knows that it's a child of // replaced content, or similar stuff! let damage = { - let previous_values = data.previous_styles().map(|x| &x.primary.values); + debug_assert!(!data.has_current_styles()); + let previous_values = data.get_styles().map(|x| &x.primary.values); match self.existing_style_for_restyle_damage(previous_values, None) { Some(ref source) => RestyleDamage::compute(source, &shared_style.values), None => RestyleDamage::rebuild_and_reflow(), @@ -730,13 +732,17 @@ pub trait MatchMethods : TElement { { // Get our parent's style. let parent_data = parent.as_ref().map(|x| x.borrow_data().unwrap()); - let parent_style = parent_data.as_ref().map(|x| &x.current_styles().primary.values); + let parent_style = parent_data.as_ref().map(|d| { + debug_assert!(d.has_current_styles()); + &d.styles().primary.values + }); let mut new_styles; let mut possibly_expired_animations = vec![]; let damage = { - let (old_primary, old_pseudos) = match data.previous_styles_mut() { + debug_assert!(!data.has_current_styles()); + let (old_primary, old_pseudos) = match data.get_styles_mut() { None => (None, None), Some(previous) => { // Update animations before the cascade. This may modify the diff --git a/components/style/parallel.rs b/components/style/parallel.rs index 1b9258e7aca..a93c0006f5f 100644 --- a/components/style/parallel.rs +++ b/components/style/parallel.rs @@ -9,15 +9,16 @@ use dom::{OpaqueNode, TElement, TNode, UnsafeNode}; use rayon; use std::sync::atomic::Ordering; -use traversal::{DomTraversalContext, PerLevelTraversalData}; +use traversal::{DomTraversalContext, PerLevelTraversalData, PreTraverseToken}; use traversal::{STYLE_SHARING_CACHE_HITS, STYLE_SHARING_CACHE_MISSES}; use util::opts; pub const CHUNK_SIZE: usize = 64; -pub fn traverse_dom(root: N, +pub fn traverse_dom(root: N::ConcreteElement, known_root_dom_depth: Option, shared_context: &C::SharedContext, + token: PreTraverseToken, queue: &rayon::ThreadPool) where N: TNode, C: DomTraversalContext @@ -27,15 +28,26 @@ pub fn traverse_dom(root: N, STYLE_SHARING_CACHE_MISSES.store(0, Ordering::SeqCst); } - let nodes = vec![root.to_unsafe()].into_boxed_slice(); - let data = PerLevelTraversalData { - current_dom_depth: known_root_dom_depth, + // Handle root skipping. We don't currently support it in conjunction with + // bottom-up traversal. If we did, we'd need to put it on the context to make + // it available to the bottom-up phase. + debug_assert!(!token.should_skip_root() || !C::needs_postorder_traversal()); + let (nodes, depth) = if token.should_skip_root() { + let mut children = vec![]; + C::traverse_children(root, |kid| children.push(kid.to_unsafe())); + (children, known_root_dom_depth.map(|x| x + 1)) + } else { + (vec![root.as_node().to_unsafe()], known_root_dom_depth) }; - let root = root.opaque(); + + let data = PerLevelTraversalData { + current_dom_depth: depth, + }; + + let root = root.as_node().opaque(); queue.install(|| { rayon::scope(|scope| { - let nodes = nodes; - top_down_dom::(&nodes, root, data, scope, shared_context); + traverse_nodes::<_, C>(nodes, root, data, scope, shared_context); }); }); @@ -79,7 +91,7 @@ fn top_down_dom<'a, 'scope, N, C>(unsafe_nodes: &'a [UnsafeNode], // Reset the count of children if we need to do a bottom-up traversal // after the top up. - if context.needs_postorder_traversal() { + if C::needs_postorder_traversal() { if children_to_process == 0 { // If there were no more children, start walking back up. bottom_up_dom::(root, *unsafe_node, shared_context) @@ -99,7 +111,30 @@ fn top_down_dom<'a, 'scope, N, C>(unsafe_nodes: &'a [UnsafeNode], *depth += 1; } - for chunk in discovered_child_nodes.chunks(CHUNK_SIZE) { + traverse_nodes::<_, C>(discovered_child_nodes, root, data, scope, shared_context); +} + +fn traverse_nodes<'a, 'scope, N, C>(nodes: Vec, root: OpaqueNode, + data: PerLevelTraversalData, + scope: &'a rayon::Scope<'scope>, + shared_context: &'scope C::SharedContext) + where N: TNode, + C: DomTraversalContext, +{ + if nodes.is_empty() { + return; + } + + // Optimization: traverse directly and avoid a heap-allocating spawn() call if + // we're only pushing one work unit. + if nodes.len() <= CHUNK_SIZE { + let nodes = nodes.into_boxed_slice(); + top_down_dom::(&nodes, root, data, scope, shared_context); + return; + } + + // General case. + for chunk in nodes.chunks(CHUNK_SIZE) { let nodes = chunk.iter().cloned().collect::>().into_boxed_slice(); let data = data.clone(); scope.spawn(move |scope| { diff --git a/components/style/restyle_hints.rs b/components/style/restyle_hints.rs index 03dc39d14ec..3f29d437609 100644 --- a/components/style/restyle_hints.rs +++ b/components/style/restyle_hints.rs @@ -446,9 +446,6 @@ impl DependencySet { -> RestyleHint where E: ElementExt + Clone { - debug!("About to calculate restyle hint for element. Deps: {}", - self.len()); - let state_changes = snapshot.state() .map_or_else(ElementState::empty, |old_state| current_state ^ old_state); let attrs_changed = snapshot.has_attrs(); @@ -458,21 +455,25 @@ impl DependencySet { } let mut hint = RestyleHint::empty(); - let snapshot = ElementWrapper::new_with_snapshot(el.clone(), snapshot); + let snapshot_el = ElementWrapper::new_with_snapshot(el.clone(), snapshot); - Self::compute_partial_hint(&self.common_deps, el, &snapshot, + Self::compute_partial_hint(&self.common_deps, el, &snapshot_el, &state_changes, attrs_changed, &mut hint); if !state_changes.is_empty() { - Self::compute_partial_hint(&self.state_deps, el, &snapshot, + Self::compute_partial_hint(&self.state_deps, el, &snapshot_el, &state_changes, attrs_changed, &mut hint); } if attrs_changed { - Self::compute_partial_hint(&self.attr_deps, el, &snapshot, + Self::compute_partial_hint(&self.attr_deps, el, &snapshot_el, &state_changes, attrs_changed, &mut hint); } + debug!("Calculated restyle hint: {:?}. (Element={:?}, State={:?}, Snapshot={:?}, {} Deps)", + hint, el, current_state, snapshot, self.len()); + trace!("Deps: {:?}", self); + hint } diff --git a/components/style/selector_parser.rs b/components/style/selector_parser.rs index 1c281439256..9b444f17474 100644 --- a/components/style/selector_parser.rs +++ b/components/style/selector_parser.rs @@ -8,6 +8,7 @@ use cssparser::Parser as CssParser; use matching::{common_style_affecting_attributes, CommonStyleAffectingAttributeMode}; use selectors::Element; use selectors::parser::{AttrSelector, SelectorList}; +use std::fmt::Debug; use stylesheets::{Origin, Namespaces}; pub type AttrValue = ::AttrValue; @@ -30,6 +31,12 @@ pub use servo::restyle_damage::ServoRestyleDamage as RestyleDamage; #[cfg(feature = "gecko")] pub use gecko::restyle_damage::GeckoRestyleDamage as RestyleDamage; +#[cfg(feature = "servo")] +pub type PreExistingComputedValues = ::std::sync::Arc<::properties::ServoComputedValues>; + +#[cfg(feature = "gecko")] +pub type PreExistingComputedValues = ::gecko_bindings::structs::nsStyleContext; + #[cfg_attr(feature = "servo", derive(HeapSizeOf))] pub struct SelectorParser<'a> { pub stylesheet_origin: Origin, @@ -99,7 +106,7 @@ impl PseudoElementCascadeType { } } -pub trait ElementExt: Element { +pub trait ElementExt: Element + Debug { fn is_link(&self) -> bool; fn matches_user_and_author_rules(&self) -> bool; diff --git a/components/style/sequential.rs b/components/style/sequential.rs index 0e5253b2073..01632a2d917 100644 --- a/components/style/sequential.rs +++ b/components/style/sequential.rs @@ -4,14 +4,17 @@ //! Implements sequential traversal over the DOM tree. -use dom::TNode; -use traversal::{DomTraversalContext, PerLevelTraversalData}; +use dom::{TElement, TNode}; +use traversal::{DomTraversalContext, PerLevelTraversalData, PreTraverseToken}; -pub fn traverse_dom(root: N, - shared: &C::SharedContext) +pub fn traverse_dom(root: N::ConcreteElement, + shared: &C::SharedContext, + token: PreTraverseToken) where N: TNode, C: DomTraversalContext { + debug_assert!(token.should_traverse()); + fn doit<'a, N, C>(context: &'a C, node: N, data: &mut PerLevelTraversalData) where N: TNode, C: DomTraversalContext @@ -29,7 +32,7 @@ pub fn traverse_dom(root: N, } } - if context.needs_postorder_traversal() { + if C::needs_postorder_traversal() { context.process_postorder(node); } } @@ -37,8 +40,13 @@ pub fn traverse_dom(root: N, let mut data = PerLevelTraversalData { current_dom_depth: None, }; - let context = C::new(shared, root.opaque()); - doit::(&context, root, &mut data); + let context = C::new(shared, root.as_node().opaque()); + + if token.should_skip_root() { + C::traverse_children(root, |kid| doit::(&context, kid, &mut data)); + } else { + doit::(&context, root.as_node(), &mut data); + } // Clear the local LRU cache since we store stateful elements inside. context.local_context().style_sharing_candidate_cache.borrow_mut().clear(); diff --git a/components/style/servo/restyle_damage.rs b/components/style/servo/restyle_damage.rs index 6cabb7eb485..f7635d80a64 100644 --- a/components/style/servo/restyle_damage.rs +++ b/components/style/servo/restyle_damage.rs @@ -3,7 +3,6 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ use computed_values::display; -use dom::TRestyleDamage; use heapsize::HeapSizeOf; use properties::ServoComputedValues; use std::fmt; @@ -53,16 +52,9 @@ impl HeapSizeOf for ServoRestyleDamage { fn heap_size_of_children(&self) -> usize { 0 } } -impl TRestyleDamage for ServoRestyleDamage { - /// For Servo the style source is always the computed values. - type PreExistingComputedValues = Arc; - - fn empty() -> Self { - ServoRestyleDamage::empty() - } - - fn compute(old: &Arc, - new: &Arc) -> ServoRestyleDamage { +impl ServoRestyleDamage { + pub fn compute(old: &Arc, + new: &Arc) -> ServoRestyleDamage { compute_damage(old, new) } @@ -72,13 +64,11 @@ impl TRestyleDamage for ServoRestyleDamage { /// Use this instead of `ServoRestyleDamage::all()` because /// `ServoRestyleDamage::all()` will result in unnecessary sequential resolution /// of generated content. - fn rebuild_and_reflow() -> ServoRestyleDamage { + pub fn rebuild_and_reflow() -> ServoRestyleDamage { REPAINT | REPOSITION | STORE_OVERFLOW | BUBBLE_ISIZES | REFLOW_OUT_OF_FLOW | REFLOW | RECONSTRUCT_FLOW } -} -impl ServoRestyleDamage { /// 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) -> ServoRestyleDamage { diff --git a/components/style/servo/selector_parser.rs b/components/style/servo/selector_parser.rs index 34eb8c2df7c..2403e702063 100644 --- a/components/style/servo/selector_parser.rs +++ b/components/style/servo/selector_parser.rs @@ -13,6 +13,7 @@ use selectors::{Element, MatchAttrGeneric}; use selectors::parser::AttrSelector; use std::borrow::Cow; use std::fmt; +use std::fmt::Debug; /// NB: If you add to this list, be sure to update `each_pseudo_element` too. #[derive(Clone, Debug, PartialEq, Eq, Hash)] @@ -398,7 +399,7 @@ impl MatchAttrGeneric for ServoElementSnapshot { } } -impl> ElementExt for E { +impl + Debug> ElementExt for E { fn is_link(&self) -> bool { self.match_non_ts_pseudo_class(NonTSPseudoClass::AnyLink) } diff --git a/components/style/traversal.rs b/components/style/traversal.rs index 685c33b265f..5e0712f2114 100644 --- a/components/style/traversal.rs +++ b/components/style/traversal.rs @@ -7,14 +7,18 @@ use atomic_refcell::{AtomicRefCell, AtomicRefMut}; use bloom::StyleBloom; use context::{LocalStyleContext, SharedStyleContext, StyleContext}; -use data::{ElementData, RestyleData, StoredRestyleHint}; -use dom::{OpaqueNode, StylingMode, TElement, TNode}; +use data::{ElementData, StoredRestyleHint}; +use dom::{OpaqueNode, TElement, TNode}; use matching::{MatchMethods, StyleSharingResult}; -use restyle_hints::{RESTYLE_DESCENDANTS, RESTYLE_LATER_SIBLINGS, RESTYLE_SELF}; +use restyle_hints::{RESTYLE_DESCENDANTS, RESTYLE_SELF}; +use selector_parser::RestyleDamage; +use selectors::Element; use selectors::matching::StyleRelations; +use std::borrow::Borrow; use std::cell::RefCell; -use std::marker::PhantomData; +use std::mem; use std::sync::atomic::{AtomicUsize, ATOMIC_USIZE_INIT, Ordering}; +use stylist::Stylist; use util::opts; /// Every time we do another layout, the old bloom filters are invalid. This is @@ -36,7 +40,7 @@ thread_local!( pub fn take_thread_local_bloom_filter(context: &SharedStyleContext) -> StyleBloom { - debug!("{} taking bf", ::tid::tid()); + trace!("{} taking bf", ::tid::tid()); STYLE_BLOOM.with(|style_bloom| { style_bloom.borrow_mut().take() @@ -45,7 +49,7 @@ pub fn take_thread_local_bloom_filter(context: &SharedStyleContext) } pub fn put_thread_local_bloom_filter(bf: StyleBloom) { - debug!("[{}] putting bloom filter back", ::tid::tid()); + trace!("[{}] putting bloom filter back", ::tid::tid()); STYLE_BLOOM.with(move |style_bloom| { debug_assert!(style_bloom.borrow().is_none(), @@ -64,7 +68,7 @@ pub fn remove_from_bloom_filter<'a, E, C>(context: &C, root: OpaqueNode, element where E: TElement, C: StyleContext<'a> { - debug!("[{}] remove_from_bloom_filter", ::tid::tid()); + trace!("[{}] remove_from_bloom_filter", ::tid::tid()); // We may have arrived to `reconstruct_flows` without entering in style // recalc at all due to our optimizations, nor that it's up to date, so we @@ -96,8 +100,25 @@ pub struct PerLevelTraversalData { pub current_dom_depth: Option, } +/// This structure exists to enforce that callers invoke pre_traverse, and also +/// to pass information from the pre-traversal into the primary traversal. +pub struct PreTraverseToken { + traverse: bool, + skip_root: bool, +} + +impl PreTraverseToken { + pub fn should_traverse(&self) -> bool { + self.traverse + } + + pub fn should_skip_root(&self) -> bool { + self.skip_root + } +} + pub trait DomTraversalContext { - type SharedContext: Sync + 'static; + type SharedContext: Sync + 'static + Borrow; fn new<'a>(&'a Self::SharedContext, OpaqueNode) -> Self; @@ -113,24 +134,113 @@ pub trait DomTraversalContext { /// performed. /// /// If it's false, then process_postorder has no effect at all. - fn needs_postorder_traversal(&self) -> bool { true } + fn needs_postorder_traversal() -> bool { true } - /// Returns true if traversal should visit the given child. - fn should_traverse_child(child: N) -> bool; + /// Must be invoked before traversing the root element to determine whether + /// a traversal is needed. Returns a token that allows the caller to prove + /// that the call happened. + /// + /// The skip_root parameter is used in Gecko to style newly-appended children + /// without restyling the parent. + fn pre_traverse(root: N::ConcreteElement, stylist: &Stylist, skip_root: bool) + -> PreTraverseToken + { + // If we should skip the root, traverse unconditionally. + if skip_root { + return PreTraverseToken { + traverse: true, + skip_root: true, + }; + } + + // Expand the snapshot, if any. This is normally handled by the parent, so + // we need a special case for the root. + // + // Expanding snapshots here may create a LATER_SIBLINGS restyle hint, which + // we will drop on the floor. This is fine, because we don't traverse roots + // with siblings. + debug_assert!(root.next_sibling_element().is_none()); + if let Some(mut data) = root.mutate_data() { + if let Some(r) = data.as_restyle_mut() { + let _later_siblings = r.expand_snapshot(root, stylist); + } + } + + PreTraverseToken { + traverse: Self::node_needs_traversal(root.as_node()), + skip_root: false, + } + } + + /// Returns true if traversal should visit a text node. The style system never + /// processes text nodes, but Servo overrides this to visit them for flow + /// construction when necessary. + fn text_node_needs_traversal(node: N) -> bool { debug_assert!(node.is_text_node()); false } + + /// Returns true if traversal is needed for the given node and subtree. + fn node_needs_traversal(node: N) -> bool { + // Non-incremental layout visits every node. + if cfg!(feature = "servo") && opts::get().nonincremental_layout { + return true; + } + + match node.as_element() { + None => Self::text_node_needs_traversal(node), + Some(el) => { + // If the dirty descendants bit is set, we need to traverse no + // matter what. Skip examining the ElementData. + if el.has_dirty_descendants() { + return true; + } + + // Check the element data. If it doesn't exist, we need to visit + // the element. + let data = match el.borrow_data() { + Some(d) => d, + None => return true, + }; + + // Check what kind of element data we have. If it's Initial or Persistent, + // we're done. + let restyle = match *data { + ElementData::Initial(ref i) => return i.is_none(), + ElementData::Persistent(_) => return false, + ElementData::Restyle(ref r) => r, + }; + + // Check whether we have any selector matching or re-cascading to + // do in this subtree. + debug_assert!(restyle.snapshot.is_none(), "Snapshots should already be expanded"); + if !restyle.hint.is_empty() || restyle.recascade { + return true; + } + + // Servo uses the post-order traversal for flow construction, so + // we need to traverse any element with damage so that we can perform + // fixup / reconstruction on our way back up the tree. + if cfg!(feature = "servo") && restyle.damage != RestyleDamage::empty() { + return true; + } + + false + }, + } + } /// Helper for the traversal implementations to select the children that /// should be enqueued for processing. fn traverse_children(parent: N::ConcreteElement, mut f: F) { - use dom::StylingMode::Restyle; - if parent.is_display_none() { return; } for kid in parent.as_node().children() { - if Self::should_traverse_child(kid) { - if kid.as_element().map_or(false, |el| el.styling_mode() == Restyle) { + if Self::node_needs_traversal(kid) { + let el = kid.as_element(); + if el.as_ref().and_then(|el| el.borrow_data()) + .map_or(false, |d| d.is_restyle()) + { unsafe { parent.set_dirty_descendants(); } } f(kid); @@ -206,61 +316,75 @@ pub fn style_element_in_display_none_subtree<'a, E, C, F>(element: E, #[inline] #[allow(unsafe_code)] pub fn recalc_style_at<'a, E, C, D>(context: &'a C, - data: &mut PerLevelTraversalData, - element: E) + traversal_data: &mut PerLevelTraversalData, + element: E, + mut data: &mut AtomicRefMut) where E: TElement, C: StyleContext<'a>, D: DomTraversalContext { - let mode = element.styling_mode(); - let should_compute = element.borrow_data().map_or(true, |d| d.get_current_styles().is_none()); - debug!("recalc_style_at: {:?} (should_compute={:?} mode={:?}, data={:?})", - element, should_compute, mode, element.borrow_data()); + debug_assert!(data.as_restyle().map_or(true, |r| r.snapshot.is_none()), + "Snapshots should be expanded by the caller"); - let (computed_display_none, propagated_hint) = if should_compute { - compute_style::<_, _, D>(context, data, element) - } else { - (false, StoredRestyleHint::empty()) + let compute_self = !data.has_current_styles(); + let mut inherited_style_changed = false; + + debug!("recalc_style_at: {:?} (compute_self={:?}, dirty_descendants={:?}, data={:?})", + element, compute_self, element.has_dirty_descendants(), data); + + // Compute style for this element if necessary. + if compute_self { + inherited_style_changed = compute_style::<_, _, D>(context, &mut data, traversal_data, element); + } + + // Now that matching and cascading is done, clear the bits corresponding to + // those operations and compute the propagated restyle hint. + let empty_hint = StoredRestyleHint::empty(); + let propagated_hint = match data.as_restyle_mut() { + None => empty_hint, + Some(r) => { + r.recascade = false; + mem::replace(&mut r.hint, empty_hint).propagate() + }, }; + debug_assert!(data.has_current_styles()); + trace!("propagated_hint={:?}, inherited_style_changed={:?}", propagated_hint, inherited_style_changed); - // Preprocess children, computing restyle hints and handling sibling relationships. - // - // We don't need to do this if we're not traversing children, or if we're performing - // initial styling. - let will_traverse_children = !computed_display_none && - (mode == StylingMode::Restyle || - mode == StylingMode::Traverse); - if will_traverse_children { - preprocess_children::<_, _, D>(context, element, propagated_hint, - mode == StylingMode::Restyle); + // Preprocess children, propagating restyle hints and handling sibling relationships. + if !data.styles().is_display_none() && + (element.has_dirty_descendants() || !propagated_hint.is_empty() || inherited_style_changed) { + preprocess_children::<_, _, D>(context, element, propagated_hint, inherited_style_changed); } } +// Computes style, returning true if the inherited styles changed for this +// element. +// +// FIXME(bholley): This should differentiate between matching and cascading, +// since we have separate bits for each now. fn compute_style<'a, E, C, D>(context: &'a C, - data: &mut PerLevelTraversalData, - element: E) -> (bool, StoredRestyleHint) + mut data: &mut AtomicRefMut, + traversal_data: &mut PerLevelTraversalData, + element: E) -> bool where E: TElement, C: StyleContext<'a>, - D: DomTraversalContext + D: DomTraversalContext, { let shared_context = context.shared_context(); let mut bf = take_thread_local_bloom_filter(shared_context); // Ensure the bloom filter is up to date. let dom_depth = bf.insert_parents_recovering(element, - data.current_dom_depth, + traversal_data.current_dom_depth, shared_context.generation); // Update the dom depth with the up-to-date dom depth. // // Note that this is always the same than the pre-existing depth, but it can // change from unknown to known at this step. - data.current_dom_depth = Some(dom_depth); + traversal_data.current_dom_depth = Some(dom_depth); bf.assert_complete(element); - let mut data = unsafe { D::ensure_element_data(&element).borrow_mut() }; - debug_assert!(!data.is_persistent()); - // Check to see whether we can share a style with someone. let style_sharing_candidate_cache = &mut context.local_context().style_sharing_candidate_cache.borrow_mut(); @@ -304,7 +428,7 @@ fn compute_style<'a, E, C, D>(context: &'a C, // Add ourselves to the LRU cache. if let Some(element) = shareable_element { style_sharing_candidate_cache.insert_if_possible(&element, - &data.current_styles().primary.values, + &data.styles().primary.values, relations); } } @@ -318,7 +442,7 @@ fn compute_style<'a, E, C, D>(context: &'a C, // If we're restyling this element to display:none, throw away all style data // in the subtree, notify the caller to early-return. - let display_none = data.current_styles().is_display_none(); + let display_none = data.styles().is_display_none(); if display_none { debug!("New element style is display:none - clearing data from descendants."); clear_descendant_data(element, &|e| unsafe { D::clear_element_data(&e) }); @@ -331,13 +455,16 @@ fn compute_style<'a, E, C, D>(context: &'a C, // complexity. put_thread_local_bloom_filter(bf); - (display_none, data.as_restyle().map_or(StoredRestyleHint::empty(), |r| r.hint.propagate())) + // FIXME(bholley): Compute this accurately from the call to CalcStyleDifference. + let inherited_styles_changed = true; + + inherited_styles_changed } fn preprocess_children<'a, E, C, D>(context: &'a C, element: E, mut propagated_hint: StoredRestyleHint, - restyled_parent: bool) + parent_inherited_style_changed: bool) where E: TElement, C: StyleContext<'a>, D: DomTraversalContext @@ -350,41 +477,33 @@ fn preprocess_children<'a, E, C, D>(context: &'a C, None => continue, }; - // Set up our lazy child restyle data. - let mut child_data = unsafe { LazyRestyleData::::new(&child) }; + let mut child_data = unsafe { D::ensure_element_data(&child).borrow_mut() }; + if child_data.is_unstyled_initial() { + continue; + } + + let mut restyle_data = match child_data.restyle() { + Some(d) => d, + None => continue, + }; // Propagate the parent and sibling restyle hint. if !propagated_hint.is_empty() { - child_data.ensure().map(|d| d.hint.insert(&propagated_hint)); + restyle_data.hint.insert(&propagated_hint); } - // Handle element snashots. - if child_data.has_snapshot() { - // Compute the restyle hint. - let mut restyle_data = child_data.ensure().unwrap(); - let mut hint = context.shared_context().stylist - .compute_restyle_hint(&child, - restyle_data.snapshot.as_ref().unwrap(), - child.get_state()); - - // If the hint includes a directive for later siblings, strip - // it out and modify the base hint for future siblings. - if hint.contains(RESTYLE_LATER_SIBLINGS) { - hint.remove(RESTYLE_LATER_SIBLINGS); - propagated_hint.insert(&(RESTYLE_SELF | RESTYLE_DESCENDANTS).into()); - } - - // Insert the hint. - if !hint.is_empty() { - restyle_data.hint.insert(&hint.into()); - } + // Handle element snapshots. + let stylist = &context.shared_context().stylist; + let later_siblings = restyle_data.expand_snapshot(child, stylist); + if later_siblings { + propagated_hint.insert(&(RESTYLE_SELF | RESTYLE_DESCENDANTS).into()); } - // If we restyled this node, conservatively mark all our children as - // needing a re-cascade. Once we have the rule tree, we will be able - // to distinguish between re-matching and re-cascading. - if restyled_parent { - child_data.ensure(); + // If properties that we inherited from the parent changed, we need to recascade. + // + // FIXME(bholley): Need to handle explicitly-inherited reset properties somewhere. + if parent_inherited_style_changed { + restyle_data.recascade = true; } } } @@ -404,60 +523,3 @@ pub fn clear_descendant_data(el: E, clear_data: &F) { unsafe { el.unset_dirty_descendants(); } } - -/// Various steps in the child preparation algorithm above may cause us to lazily -/// instantiate the ElementData on the child. Encapsulate that logic into a -/// convenient abstraction. -struct LazyRestyleData<'b, E: TElement + 'b, D: DomTraversalContext> { - data: Option>, - element: &'b E, - phantom: PhantomData, -} - -impl<'b, E: TElement, D: DomTraversalContext> LazyRestyleData<'b, E, D> { - /// This may lazily instantiate ElementData, and is therefore only safe to - /// call on an element for which we have exclusive access. - unsafe fn new(element: &'b E) -> Self { - LazyRestyleData { - data: None, - element: element, - phantom: PhantomData, - } - } - - fn ensure(&mut self) -> Option<&mut RestyleData> { - if self.data.is_none() { - let mut d = unsafe { D::ensure_element_data(self.element).borrow_mut() }; - d.restyle(); - self.data = Some(d); - } - - self.data.as_mut().unwrap().as_restyle_mut() - } - - /// Checks for the existence of an element snapshot without lazily instantiating - /// anything. This allows the traversal to cheaply pass through already-styled - /// nodes when they don't need a restyle. - fn has_snapshot(&self) -> bool { - // If there's no element data, we're done. - let raw_data = self.element.get_data(); - if raw_data.is_none() { - debug_assert!(self.data.is_none()); - return false; - } - - // If there is element data, we still may not have committed to processing - // the node. Carefully get a reference to the data. - let maybe_tmp_borrow; - let borrow_ref = match self.data { - Some(ref d) => d, - None => { - maybe_tmp_borrow = raw_data.unwrap().borrow_mut(); - &maybe_tmp_borrow - } - }; - - // Check for a snapshot. - borrow_ref.as_restyle().map_or(false, |d| d.snapshot.is_some()) - } -} diff --git a/ports/geckolib/glue.rs b/ports/geckolib/glue.rs index 36c309dcdd0..1fa492ec6b3 100644 --- a/ports/geckolib/glue.rs +++ b/ports/geckolib/glue.rs @@ -18,7 +18,7 @@ use style::arc_ptr_eq; use style::atomic_refcell::AtomicRefMut; use style::context::{LocalStyleContextCreationInfo, ReflowGoal, SharedStyleContext}; use style::data::{ElementData, RestyleData}; -use style::dom::{StylingMode, TElement, TNode, TRestyleDamage}; +use style::dom::{TElement, TNode}; use style::error_reporting::StdoutErrorReporter; use style::gecko::context::StandaloneStyleContext; use style::gecko::context::clear_local_context; @@ -58,7 +58,7 @@ use style::string_cache::Atom; use style::stylesheets::{CssRule, CssRules, Origin, Stylesheet, StyleRule}; use style::thread_state; use style::timer::Timer; -use style::traversal::{recalc_style_at, PerLevelTraversalData}; +use style::traversal::{recalc_style_at, DomTraversalContext, PerLevelTraversalData}; use style_traits::ToCss; /* @@ -106,7 +106,6 @@ fn create_shared_context(mut per_doc_data: &mut AtomicRefMut(element.as_node(), &shared_style_context); + sequential::traverse_dom::<_, RecalcStyleOnly>(element, &shared_style_context, token); } else { - parallel::traverse_dom::<_, RecalcStyleOnly>(element.as_node(), known_depth, &shared_style_context, + parallel::traverse_dom::<_, RecalcStyleOnly>(element, known_depth, + &shared_style_context, token, per_doc_data.work_queue.as_mut().unwrap()); } } @@ -735,10 +736,7 @@ pub extern "C" fn Servo_Element_GetSnapshot(element: RawGeckoElementBorrowed) -> let element = GeckoElement(element); let mut data = unsafe { element.ensure_data().borrow_mut() }; let snapshot = if let Some(restyle_data) = unsafe { maybe_restyle(&mut data, element) } { - if restyle_data.snapshot.is_none() { - restyle_data.snapshot = Some(element.create_snapshot()); - } - restyle_data.snapshot.as_mut().unwrap().borrow_mut_raw() + restyle_data.snapshot.ensure(|| element.create_snapshot()).borrow_mut_raw() } else { ptr::null_mut() }; @@ -757,8 +755,6 @@ pub extern "C" fn Servo_NoteExplicitHints(element: RawGeckoElementBorrowed, debug!("Servo_NoteExplicitHints: {:?}, restyle_hint={:?}, change_hint={:?}", element, restyle_hint, change_hint); - let restore_current_style = restyle_hint.0 == 0 && data.get_current_styles().is_some(); - if let Some(restyle_data) = unsafe { maybe_restyle(&mut data, element) } { let restyle_hint: RestyleHint = restyle_hint.into(); restyle_data.hint.insert(&restyle_hint.into()); @@ -766,21 +762,6 @@ pub extern "C" fn Servo_NoteExplicitHints(element: RawGeckoElementBorrowed, } else { debug!("(Element not styled, discarding hints)"); } - - // If we had up-to-date style before and only posted a change hint, - // avoid invalidating that style. - // - // This allows for posting explicit change hints during restyle between - // the servo style traversal and the gecko post-traversal (i.e. during the - // call to CreateNeedeFrames in ServoRestyleManager::ProcessPendingRestyles). - // - // FIXME(bholley): The is a very inefficient and hacky way of doing this, - // we should fix the ElementData restyle() API to be more granular so that it - // does the right thing automatically. - if restore_current_style { - let styles = data.previous_styles().unwrap().clone(); - data.finish_styling(styles, GeckoRestyleDamage::empty()); - } } #[no_mangle] @@ -817,12 +798,14 @@ pub extern "C" fn Servo_ResolveStyle(element: RawGeckoElementBorrowed, let element = GeckoElement(element); debug!("Servo_ResolveStyle: {:?}, consume={:?}, compute={:?}", element, consume, compute); + let mut data = unsafe { element.ensure_data() }.borrow_mut(); + if compute == structs::LazyComputeBehavior::Allow { - let should_compute = unsafe { element.ensure_data() }.borrow().get_current_styles().is_none(); + let should_compute = !data.has_current_styles(); if should_compute { debug!("Performing manual style computation"); if let Some(parent) = element.parent_element() { - if parent.borrow_data().map_or(true, |d| d.get_current_styles().is_none()) { + if parent.borrow_data().map_or(true, |d| !d.has_current_styles()) { error!("Attempting manual style computation with unstyled parent"); return Arc::new(ComputedValues::initial_values().clone()).into_strong(); } @@ -832,10 +815,11 @@ pub extern "C" fn Servo_ResolveStyle(element: RawGeckoElementBorrowed, let shared_style_context = create_shared_context(&mut per_doc_data); let context = StandaloneStyleContext::new(&shared_style_context); - let mut data = PerLevelTraversalData { + let mut traversal_data = PerLevelTraversalData { current_dom_depth: None, }; - recalc_style_at::<_, _, RecalcStyleOnly>(&context, &mut data, element); + + recalc_style_at::<_, _, RecalcStyleOnly>(&context, &mut traversal_data, element, &mut data); // The element was either unstyled or needed restyle. If it was unstyled, it may have // additional unstyled children that subsequent traversals won't find now that the style @@ -846,19 +830,17 @@ pub extern "C" fn Servo_ResolveStyle(element: RawGeckoElementBorrowed, } } - let data = element.mutate_data(); - let values = match data.as_ref().and_then(|d| d.get_current_styles()) { - Some(x) => x.primary.values.clone(), - None => { - error!("Resolving style on unstyled element with lazy computation forbidden."); - return Arc::new(ComputedValues::initial_values().clone()).into_strong(); - } - }; + if !data.has_current_styles() { + error!("Resolving style on unstyled element with lazy computation forbidden."); + return Arc::new(ComputedValues::initial_values().clone()).into_strong(); + } + + let values = data.styles().primary.values.clone(); if consume == structs::ConsumeStyleBehavior::Consume { // FIXME(bholley): Once we start storing style data on frames, we'll want to // drop the data here instead. - data.unwrap().persist(); + data.persist(); } values.into_strong()