diff --git a/components/layout_thread/lib.rs b/components/layout_thread/lib.rs index 9994eb54bdc..ea834095e6e 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::{TElement, TNode}; +use style::dom::{ShowSubtree, ShowSubtreeDataAndPrimaryValues, TElement, TNode}; use style::error_reporting::{ParseErrorReporter, StdoutErrorReporter}; use style::logical_geometry::LogicalPoint; use style::media_queries::{Device, MediaType}; @@ -1035,9 +1035,7 @@ impl LayoutThread { debug!("layout: processing reflow request for: {:?} ({}) (query={:?})", element, self.url, data.query_type); - if log_enabled!(log::LogLevel::Debug) { - element.as_node().dump(); - } + debug!("{:?}", ShowSubtree(element.as_node())); let initial_viewport = data.window_size.initial_viewport; let old_viewport_size = self.viewport_size; @@ -1181,7 +1179,7 @@ impl LayoutThread { } if opts::get().dump_style_tree { - element.as_node().dump_style(); + println!("{:?}", ShowSubtreeDataAndPrimaryValues(element.as_node())); } if opts::get().dump_rule_tree { diff --git a/components/script/layout_wrapper.rs b/components/script/layout_wrapper.rs index ab69e8f2cab..0fa54e6fe75 100644 --- a/components/script/layout_wrapper.rs +++ b/components/script/layout_wrapper.rs @@ -86,7 +86,11 @@ impl<'ln> Debug for ServoLayoutNode<'ln> { if let Some(el) = self.as_element() { el.fmt(f) } else { - write!(f, "{:?} ({:#x})", self.type_id(), self.opaque().0) + if self.is_text_node() { + write!(f, " ({:#x})", self.opaque().0) + } else { + write!(f, " ({:#x})", self.opaque().0) + } } } } @@ -156,15 +160,6 @@ impl<'ln> TNode for ServoLayoutNode<'ln> { transmute(node) } - fn dump(self) { - self.dump_indent(0); - } - - fn dump_style(self) { - println!("\nDOM with computed styles:"); - self.dump_style_indent(0); - } - fn children(self) -> LayoutIterator> { LayoutIterator(ServoChildrenIterator { current: self.first_child(), @@ -290,54 +285,6 @@ impl<'le> GetLayoutData for ServoThreadSafeLayoutElement<'le> { } impl<'ln> ServoLayoutNode<'ln> { - fn dump_indent(self, indent: u32) { - let mut s = String::new(); - for _ in 0..indent { - s.push_str(" "); - } - - s.push_str(&self.debug_str()); - println!("{}", s); - - for kid in self.children() { - kid.dump_indent(indent + 1); - } - } - - fn dump_style_indent(self, indent: u32) { - if self.is_element() { - let mut s = String::new(); - for _ in 0..indent { - s.push_str(" "); - } - s.push_str(&self.debug_style_str()); - println!("{}", s); - } - - for kid in self.children() { - kid.dump_style_indent(indent + 1); - } - } - - fn debug_str(self) -> String { - format!("{:?}: dirty_descendants={}", - self.script_type_id(), - self.as_element().map_or(false, |el| el.has_dirty_descendants())) - } - - fn debug_style_str(self) -> String { - let maybe_element = self.as_element(); - let maybe_data = match maybe_element { - Some(ref el) => el.borrow_data(), - None => None, - }; - if let Some(data) = maybe_data { - format!("{:?}: {:?}", self.script_type_id(), &*data) - } else { - format!("{:?}: style_data=None", self.script_type_id()) - } - } - /// Returns the interior of this node as a `LayoutJS`. This is highly unsafe for layout to /// call and as such is marked `unsafe`. pub unsafe fn get_jsmanaged(&self) -> &LayoutJS { diff --git a/components/style/build_gecko.rs b/components/style/build_gecko.rs index a96a92e9871..119d3b30f9d 100644 --- a/components/style/build_gecko.rs +++ b/components/style/build_gecko.rs @@ -220,7 +220,7 @@ mod bindings { "mozilla::ConsumeStyleBehavior", "mozilla::LazyComputeBehavior", "mozilla::css::SheetParsingMode", - "mozilla::SkipRootBehavior", + "mozilla::TraversalRootBehavior", "mozilla::DisplayItemClip", // Needed because bindgen generates // specialization tests for this even // though it shouldn't. @@ -444,7 +444,7 @@ mod bindings { "ThreadSafePrincipalHolder", "ConsumeStyleBehavior", "LazyComputeBehavior", - "SkipRootBehavior", + "TraversalRootBehavior", "FontFamilyList", "FontFamilyType", "ServoElementSnapshot", diff --git a/components/style/dom.rs b/components/style/dom.rs index 8431f5526c3..75069ce9305 100644 --- a/components/style/dom.rs +++ b/components/style/dom.rs @@ -14,6 +14,7 @@ use parking_lot::RwLock; use properties::{ComputedValues, PropertyDeclarationBlock}; use selector_parser::{ElementExt, PreExistingComputedValues, PseudoElement}; use sink::Push; +use std::fmt; use std::fmt::Debug; use std::sync::Arc; use stylist::ApplicableDeclarationBlock; @@ -67,17 +68,13 @@ impl Iterator for LayoutIterator where T: Iterator, I: NodeInfo } } -pub trait TNode : Sized + Copy + Clone + NodeInfo { +pub trait TNode : Sized + Copy + Clone + Debug + NodeInfo { type ConcreteElement: TElement; type ConcreteChildrenIterator: Iterator; fn to_unsafe(&self) -> UnsafeNode; unsafe fn from_unsafe(n: &UnsafeNode) -> Self; - fn dump(self); - - fn dump_style(self); - /// Returns an iterator over this node's children. fn children(self) -> LayoutIterator; @@ -103,6 +100,90 @@ pub trait TNode : Sized + Copy + Clone + NodeInfo { fn parent_node(&self) -> Option; } +/// Wrapper to output the ElementData along with the node when formatting for +/// Debug. +pub struct ShowData(pub N); +impl Debug for ShowData { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fmt_with_data(f, self.0) + } +} + +/// Wrapper to output the primary computed values along with the node when +/// formatting for Debug. This is very verbose. +pub struct ShowDataAndPrimaryValues(pub N); +impl Debug for ShowDataAndPrimaryValues { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fmt_with_data_and_primary_values(f, self.0) + } +} + +/// Wrapper to output the subtree rather than the single node when formatting +/// for Debug. +pub struct ShowSubtree(pub N); +impl Debug for ShowSubtree { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + try!(writeln!(f, "DOM Subtree:")); + fmt_subtree(f, &|f, n| write!(f, "{:?}", n), self.0, 1) + } +} + +/// Wrapper to output the subtree along with the ElementData when formatting +/// for Debug. +pub struct ShowSubtreeData(pub N); +impl Debug for ShowSubtreeData { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + try!(writeln!(f, "DOM Subtree:")); + fmt_subtree(f, &|f, n| fmt_with_data(f, n), self.0, 1) + } +} + +/// Wrapper to output the subtree along with the ElementData and primary +/// ComputedValues when formatting for Debug. This is extremely verbose. +pub struct ShowSubtreeDataAndPrimaryValues(pub N); +impl Debug for ShowSubtreeDataAndPrimaryValues { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + try!(writeln!(f, "DOM Subtree:")); + fmt_subtree(f, &|f, n| fmt_with_data_and_primary_values(f, n), self.0, 1) + } +} + +fn fmt_with_data(f: &mut fmt::Formatter, n: N) -> fmt::Result { + if let Some(el) = n.as_element() { + write!(f, "{:?} dd={} data={:?}", el, el.has_dirty_descendants(), el.borrow_data()) + } else { + write!(f, "{:?}", n) + } +} + +fn fmt_with_data_and_primary_values(f: &mut fmt::Formatter, n: N) -> fmt::Result { + if let Some(el) = n.as_element() { + let dd = el.has_dirty_descendants(); + let data = el.borrow_data(); + let styles = data.as_ref().and_then(|d| d.get_styles()); + let values = styles.map(|s| &s.primary.values); + write!(f, "{:?} dd={} data={:?} values={:?}", el, dd, &data, values) + } else { + write!(f, "{:?}", n) + } +} + +fn fmt_subtree(f: &mut fmt::Formatter, stringify: &F, n: N, indent: u32) + -> fmt::Result + where F: Fn(&mut fmt::Formatter, N) -> fmt::Result +{ + for _ in 0..indent { + try!(write!(f, " ")); + } + try!(stringify(f, n)); + for kid in n.children() { + try!(writeln!(f, "")); + try!(fmt_subtree(f, stringify, kid, indent + 1)); + } + + Ok(()) +} + pub trait PresentationalHintsSynthetizer { fn synthesize_presentational_hints_for_legacy_attributes(&self, hints: &mut V) where V: Push; @@ -163,10 +244,10 @@ 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 style is display:none. + /// Returns true if this element's style is display:none. Panics if + /// the element has no style. fn is_display_none(&self) -> bool { let data = self.borrow_data().unwrap(); - debug_assert!(data.has_current_styles()); data.styles().is_display_none() } diff --git a/components/style/gecko/wrapper.rs b/components/style/gecko/wrapper.rs index f8629a24fe3..4f9ef32305a 100644 --- a/components/style/gecko/wrapper.rs +++ b/components/style/gecko/wrapper.rs @@ -50,6 +50,20 @@ use stylist::ApplicableDeclarationBlock; #[derive(Clone, Copy)] pub struct GeckoNode<'ln>(pub &'ln RawGeckoNode); +impl<'ln> fmt::Debug for GeckoNode<'ln> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + if let Some(el) = self.as_element() { + el.fmt(f) + } else { + if self.is_text_node() { + write!(f, " ({:#x})", self.opaque().0) + } else { + write!(f, " ({:#x})", self.opaque().0) + } + } + } +} + impl<'ln> GeckoNode<'ln> { fn from_content(content: &'ln nsIContent) -> Self { GeckoNode(&content._base) @@ -102,19 +116,6 @@ impl<'ln> TNode for GeckoNode<'ln> { GeckoNode(&*(n.0 as *mut RawGeckoNode)) } - fn dump(self) { - if self.is_text_node() { - println!("Text ({:?})", &self.0 as *const _); - } else { - let el = self.as_element().unwrap(); - println!("Element {} ({:?})", el.get_local_name(), &el.0 as *const _); - } - } - - fn dump_style(self) { - unimplemented!() - } - fn children(self) -> LayoutIterator> { let maybe_iter = unsafe { Gecko_MaybeCreateStyleChildrenIterator(self.0) }; if let Some(iter) = maybe_iter.into_owned_opt() { @@ -211,7 +212,7 @@ impl<'le> fmt::Debug for GeckoElement<'le> { if let Some(id) = self.get_id() { try!(write!(f, " id={}", id)); } - write!(f, "> ({:?})", self.0 as *const _) + write!(f, "> ({:#x})", self.as_node().opaque().0) } } diff --git a/components/style/gecko_bindings/bindings.rs b/components/style/gecko_bindings/bindings.rs index d7ad23ff955..877dab3ef51 100644 --- a/components/style/gecko_bindings/bindings.rs +++ b/components/style/gecko_bindings/bindings.rs @@ -70,7 +70,7 @@ use gecko_bindings::structs::ThreadSafeURIHolder; use gecko_bindings::structs::ThreadSafePrincipalHolder; use gecko_bindings::structs::ConsumeStyleBehavior; use gecko_bindings::structs::LazyComputeBehavior; -use gecko_bindings::structs::SkipRootBehavior; +use gecko_bindings::structs::TraversalRootBehavior; use gecko_bindings::structs::FontFamilyList; use gecko_bindings::structs::FontFamilyType; use gecko_bindings::structs::ServoElementSnapshot; @@ -1197,7 +1197,7 @@ extern "C" { extern "C" { pub fn Servo_TraverseSubtree(root: RawGeckoElementBorrowed, set: RawServoStyleSetBorrowed, - skip_root: SkipRootBehavior); + behavior: TraversalRootBehavior); } extern "C" { pub fn Servo_AssertTreeIsClean(root: RawGeckoElementBorrowed); diff --git a/components/style/gecko_bindings/structs_debug.rs b/components/style/gecko_bindings/structs_debug.rs index 25960347510..d2081917576 100644 --- a/components/style/gecko_bindings/structs_debug.rs +++ b/components/style/gecko_bindings/structs_debug.rs @@ -2493,7 +2493,7 @@ pub mod root { } #[repr(i32)] #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] - pub enum SkipRootBehavior { Skip = 0, DontSkip = 1, } + pub enum TraversalRootBehavior { Normal = 0, UnstyledChildrenOnly = 1, } pub mod a11y { #[allow(unused_imports)] use self::super::super::super::root; diff --git a/components/style/gecko_bindings/structs_release.rs b/components/style/gecko_bindings/structs_release.rs index 587d932ba69..c6fdf8d127d 100644 --- a/components/style/gecko_bindings/structs_release.rs +++ b/components/style/gecko_bindings/structs_release.rs @@ -2475,7 +2475,7 @@ pub mod root { } #[repr(i32)] #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] - pub enum SkipRootBehavior { Skip = 0, DontSkip = 1, } + pub enum TraversalRootBehavior { Normal = 0, UnstyledChildrenOnly = 1, } pub mod a11y { #[allow(unused_imports)] use self::super::super::super::root; diff --git a/components/style/matching.rs b/components/style/matching.rs index 26b563291ba..807deeac209 100644 --- a/components/style/matching.rs +++ b/components/style/matching.rs @@ -733,7 +733,6 @@ 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(|d| { - debug_assert!(d.has_current_styles()); &d.styles().primary.values }); diff --git a/components/style/parallel.rs b/components/style/parallel.rs index a93c0006f5f..3bc888a5230 100644 --- a/components/style/parallel.rs +++ b/components/style/parallel.rs @@ -28,13 +28,17 @@ pub fn traverse_dom(root: N::ConcreteElement, STYLE_SHARING_CACHE_MISSES.store(0, Ordering::SeqCst); } - // 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() { + // Handle Gecko's eager initial styling. 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. + let (nodes, depth) = if token.traverse_unstyled_children_only() { + debug_assert!(!C::needs_postorder_traversal()); let mut children = vec![]; - C::traverse_children(root, |kid| children.push(kid.to_unsafe())); + for kid in root.as_node().children() { + if kid.as_element().map_or(false, |el| el.get_data().is_none()) { + 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) diff --git a/components/style/sequential.rs b/components/style/sequential.rs index 01632a2d917..614b3d6509d 100644 --- a/components/style/sequential.rs +++ b/components/style/sequential.rs @@ -42,8 +42,12 @@ pub fn traverse_dom(root: N::ConcreteElement, }; let context = C::new(shared, root.as_node().opaque()); - if token.should_skip_root() { - C::traverse_children(root, |kid| doit::(&context, kid, &mut data)); + if token.traverse_unstyled_children_only() { + for kid in root.as_node().children() { + if kid.as_element().map_or(false, |el| el.get_data().is_none()) { + doit::(&context, kid, &mut data); + } + } } else { doit::(&context, root.as_node(), &mut data); } diff --git a/components/style/traversal.rs b/components/style/traversal.rs index 5e0712f2114..5de8c726348 100644 --- a/components/style/traversal.rs +++ b/components/style/traversal.rs @@ -104,7 +104,7 @@ pub struct PerLevelTraversalData { /// to pass information from the pre-traversal into the primary traversal. pub struct PreTraverseToken { traverse: bool, - skip_root: bool, + unstyled_children_only: bool, } impl PreTraverseToken { @@ -112,8 +112,8 @@ impl PreTraverseToken { self.traverse } - pub fn should_skip_root(&self) -> bool { - self.skip_root + pub fn traverse_unstyled_children_only(&self) -> bool { + self.unstyled_children_only } } @@ -140,16 +140,16 @@ pub trait DomTraversalContext { /// 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) + /// The unstyled_children_only parameter is used in Gecko to style newly- + /// appended children without restyling the parent. + fn pre_traverse(root: N::ConcreteElement, stylist: &Stylist, + unstyled_children_only: bool) -> PreTraverseToken { - // If we should skip the root, traverse unconditionally. - if skip_root { + if unstyled_children_only { return PreTraverseToken { traverse: true, - skip_root: true, + unstyled_children_only: true, }; } @@ -157,18 +157,18 @@ pub trait DomTraversalContext { // 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()); + // we will drop on the floor. To prevent missed restyles, we assert against + // restyling a root with later siblings. if let Some(mut data) = root.mutate_data() { if let Some(r) = data.as_restyle_mut() { + debug_assert!(root.next_sibling_element().is_none()); let _later_siblings = r.expand_snapshot(root, stylist); } } PreTraverseToken { traverse: Self::node_needs_traversal(root.as_node()), - skip_root: false, + unstyled_children_only: false, } } diff --git a/ports/geckolib/glue.rs b/ports/geckolib/glue.rs index 3e6bcdbf446..63016b1a50a 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::{TElement, TNode}; +use style::dom::{ShowSubtreeData, TElement, TNode}; use style::error_reporting::StdoutErrorReporter; use style::gecko::context::StandaloneStyleContext; use style::gecko::context::clear_local_context; @@ -118,7 +118,7 @@ fn create_shared_context(mut per_doc_data: &mut AtomicRefMut () { + behavior: structs::TraversalRootBehavior) -> () { let element = GeckoElement(root); debug!("Servo_TraverseSubtree: {:?}", element); - traverse_subtree(element, raw_data, skip_root == structs::SkipRootBehavior::Skip); + traverse_subtree(element, raw_data, + behavior == structs::TraversalRootBehavior::UnstyledChildrenOnly); } #[no_mangle]