style: Distinguish between the tree structures used for traversal and selector matching.

This patch renames TNode::parent_element to traversal_parent, since it returns
the parent from the perspective of traversal (which in Gecko uses the
flattened tree).  It also renames TNode::children to traversal_children
for the saem reason.

We keep parent_element and children functions on TNode to use for selector
matching, which must be done on the real DOM tree structure.
This commit is contained in:
Cameron McCormack 2017-06-09 12:05:39 +08:00
parent c465dd0375
commit c533097e20
11 changed files with 102 additions and 67 deletions

View file

@ -165,12 +165,26 @@ impl<'ln> TNode for ServoLayoutNode<'ln> {
transmute(node) transmute(node)
} }
fn children(self) -> LayoutIterator<ServoChildrenIterator<'ln>> { fn parent_node(&self) -> Option<Self> {
unsafe {
self.node.parent_node_ref().map(|node| self.new_with_this_lifetime(&node))
}
}
fn children(&self) -> LayoutIterator<ServoChildrenIterator<'ln>> {
LayoutIterator(ServoChildrenIterator { LayoutIterator(ServoChildrenIterator {
current: self.first_child(), current: self.first_child(),
}) })
} }
fn traversal_parent(&self) -> Option<ServoLayoutElement<'ln>> {
self.parent_element()
}
fn traversal_children(&self) -> LayoutIterator<ServoChildrenIterator<'ln>> {
self.children()
}
fn opaque(&self) -> OpaqueNode { fn opaque(&self) -> OpaqueNode {
unsafe { self.get_jsmanaged().opaque() } unsafe { self.get_jsmanaged().opaque() }
} }
@ -199,12 +213,6 @@ impl<'ln> TNode for ServoLayoutNode<'ln> {
self.node.set_flag(CAN_BE_FRAGMENTED, value) self.node.set_flag(CAN_BE_FRAGMENTED, value)
} }
fn parent_node(&self) -> Option<ServoLayoutNode<'ln>> {
unsafe {
self.node.parent_node_ref().map(|node| self.new_with_this_lifetime(&node))
}
}
fn is_in_doc(&self) -> bool { fn is_in_doc(&self) -> bool {
unsafe { (*self.node.unsafe_get()).is_in_doc() } unsafe { (*self.node.unsafe_get()).is_in_doc() }
} }

View file

@ -89,7 +89,7 @@ impl<E: TElement> StyleBloom<E> {
pub fn push(&mut self, element: E) { pub fn push(&mut self, element: E) {
if cfg!(debug_assertions) { if cfg!(debug_assertions) {
if self.elements.is_empty() { if self.elements.is_empty() {
assert!(element.parent_element().is_none()); assert!(element.traversal_parent().is_none());
} }
} }
self.push_internal(element); self.push_internal(element);
@ -139,7 +139,7 @@ impl<E: TElement> StyleBloom<E> {
pub fn rebuild(&mut self, mut element: E) { pub fn rebuild(&mut self, mut element: E) {
self.clear(); self.clear();
while let Some(parent) = element.parent_element() { while let Some(parent) = element.traversal_parent() {
self.push_internal(parent); self.push_internal(parent);
element = parent; element = parent;
} }
@ -155,7 +155,7 @@ impl<E: TElement> StyleBloom<E> {
pub fn assert_complete(&self, mut element: E) { pub fn assert_complete(&self, mut element: E) {
if cfg!(debug_assertions) { if cfg!(debug_assertions) {
let mut checked = 0; let mut checked = 0;
while let Some(parent) = element.parent_element() { while let Some(parent) = element.traversal_parent() {
assert_eq!(parent, *self.elements[self.elements.len() - 1 - checked]); assert_eq!(parent, *self.elements[self.elements.len() - 1 - checked]);
element = parent; element = parent;
checked += 1; checked += 1;
@ -190,7 +190,7 @@ impl<E: TElement> StyleBloom<E> {
return; return;
} }
let parent_element = match element.parent_element() { let traversal_parent = match element.traversal_parent() {
Some(parent) => parent, Some(parent) => parent,
None => { None => {
// Yay, another easy case. // Yay, another easy case.
@ -199,7 +199,7 @@ impl<E: TElement> StyleBloom<E> {
} }
}; };
if self.current_parent() == Some(parent_element) { if self.current_parent() == Some(traversal_parent) {
// Ta da, cache hit, we're all done. // Ta da, cache hit, we're all done.
return; return;
} }
@ -232,8 +232,8 @@ impl<E: TElement> StyleBloom<E> {
} }
// Now let's try to find a common parent in the bloom filter chain, // Now let's try to find a common parent in the bloom filter chain,
// starting with parent_element. // starting with traversal_parent.
let mut common_parent = parent_element; let mut common_parent = traversal_parent;
let mut common_parent_depth = element_depth - 1; let mut common_parent_depth = element_depth - 1;
// Let's collect the parents we are going to need to insert once we've // Let's collect the parents we are going to need to insert once we've
@ -247,7 +247,7 @@ impl<E: TElement> StyleBloom<E> {
// reverse the slice. // reverse the slice.
parents_to_insert.push(common_parent); parents_to_insert.push(common_parent);
common_parent = common_parent =
common_parent.parent_element().expect("We were lied"); common_parent.traversal_parent().expect("We were lied to");
common_parent_depth -= 1; common_parent_depth -= 1;
} }
@ -269,7 +269,7 @@ impl<E: TElement> StyleBloom<E> {
while **self.elements.last().unwrap() != common_parent { while **self.elements.last().unwrap() != common_parent {
parents_to_insert.push(common_parent); parents_to_insert.push(common_parent);
self.pop().unwrap(); self.pop().unwrap();
common_parent = match common_parent.parent_element() { common_parent = match common_parent.traversal_parent() {
Some(parent) => parent, Some(parent) => parent,
None => { None => {
debug_assert!(self.elements.is_empty()); debug_assert!(self.elements.is_empty());

View file

@ -108,17 +108,27 @@ pub trait TNode : Sized + Copy + Clone + Debug + NodeInfo {
/// Get a node back from an `UnsafeNode`. /// Get a node back from an `UnsafeNode`.
unsafe fn from_unsafe(n: &UnsafeNode) -> Self; unsafe fn from_unsafe(n: &UnsafeNode) -> Self;
/// Returns an iterator over this node's children. /// Get this node's parent node.
fn children(self) -> LayoutIterator<Self::ConcreteChildrenIterator>; fn parent_node(&self) -> Option<Self>;
/// Converts self into an `OpaqueNode`.
fn opaque(&self) -> OpaqueNode;
/// Get this node's parent element if present. /// Get this node's parent element if present.
fn parent_element(&self) -> Option<Self::ConcreteElement> { fn parent_element(&self) -> Option<Self::ConcreteElement> {
self.parent_node().and_then(|n| n.as_element()) self.parent_node().and_then(|n| n.as_element())
} }
/// Returns an iterator over this node's children.
fn children(&self) -> LayoutIterator<Self::ConcreteChildrenIterator>;
/// Get this node's parent element from the perspective of a restyle
/// traversal.
fn traversal_parent(&self) -> Option<Self::ConcreteElement>;
/// Get this node's children from the perspective of a restyle traversal.
fn traversal_children(&self) -> LayoutIterator<Self::ConcreteChildrenIterator>;
/// Converts self into an `OpaqueNode`.
fn opaque(&self) -> OpaqueNode;
/// A debug id, only useful, mm... for debugging. /// A debug id, only useful, mm... for debugging.
fn debug_id(self) -> usize; fn debug_id(self) -> usize;
@ -138,9 +148,6 @@ pub trait TNode : Sized + Copy + Clone + Debug + NodeInfo {
/// Set whether this node can be fragmented. /// Set whether this node can be fragmented.
unsafe fn set_can_be_fragmented(&self, value: bool); unsafe fn set_can_be_fragmented(&self, value: bool);
/// Get this node's parent node.
fn parent_node(&self) -> Option<Self>;
/// Whether this node is in the document right now needed to clear the /// Whether this node is in the document right now needed to clear the
/// restyle data appropriately on some forced restyles. /// restyle data appropriately on some forced restyles.
fn is_in_doc(&self) -> bool; fn is_in_doc(&self) -> bool;
@ -222,7 +229,7 @@ fn fmt_subtree<F, N: TNode>(f: &mut fmt::Formatter, stringify: &F, n: N, indent:
try!(write!(f, " ")); try!(write!(f, " "));
} }
try!(stringify(f, n)); try!(stringify(f, n));
for kid in n.children() { for kid in n.traversal_children() {
try!(writeln!(f, "")); try!(writeln!(f, ""));
try!(fmt_subtree(f, stringify, kid, indent + 1)); try!(fmt_subtree(f, stringify, kid, indent + 1));
} }
@ -256,7 +263,7 @@ pub unsafe fn raw_note_descendants<E, B>(element: E) -> bool
break; break;
} }
B::set(el); B::set(el);
curr = el.parent_element(); curr = el.traversal_parent();
} }
// Note: We disable this assertion on servo because of bugs. See the // Note: We disable this assertion on servo because of bugs. See the
@ -297,7 +304,7 @@ pub trait TElement : Eq + PartialEq + Debug + Hash + Sized + Copy + Clone +
fn depth(&self) -> usize { fn depth(&self) -> usize {
let mut depth = 0; let mut depth = 0;
let mut curr = *self; let mut curr = *self;
while let Some(parent) = curr.parent_element() { while let Some(parent) = curr.traversal_parent() {
depth += 1; depth += 1;
curr = parent; curr = parent;
} }
@ -315,11 +322,18 @@ pub trait TElement : Eq + PartialEq + Debug + Hash + Sized + Copy + Clone +
} }
} }
/// Get this node's parent element from the perspective of a restyle
/// traversal.
fn traversal_parent(&self) -> Option<Self> {
self.as_node().traversal_parent()
}
/// Returns the parent element we should inherit from. /// Returns the parent element we should inherit from.
/// ///
/// This is pretty much always the parent element itself, except in the case /// This is pretty much always the parent element itself, except in the case
/// of Gecko's Native Anonymous Content, which may need to find the closest /// of Gecko's Native Anonymous Content, which uses the traversal parent
/// non-NAC ancestor. /// (i.e. the flattened tree parent) and which also may need to find the
/// closest non-NAC ancestor.
fn inheritance_parent(&self) -> Option<Self> { fn inheritance_parent(&self) -> Option<Self> {
self.parent_element() self.parent_element()
} }
@ -442,7 +456,7 @@ pub trait TElement : Eq + PartialEq + Debug + Hash + Sized + Copy + Clone +
let mut current = Some(*self); let mut current = Some(*self);
while let Some(el) = current { while let Some(el) = current {
if !B::has(el) { return false; } if !B::has(el) { return false; }
current = el.parent_element(); current = el.traversal_parent();
} }
true true

View file

@ -204,6 +204,16 @@ impl<'ln> GeckoNode<'ln> {
true true
} }
fn flattened_tree_parent(&self) -> Option<Self> {
let fast_path = self.flattened_tree_parent_is_parent();
debug_assert!(fast_path == unsafe { bindings::Gecko_FlattenedTreeParentIsParent(self.0) });
if fast_path {
unsafe { self.0.mParent.as_ref().map(GeckoNode) }
} else {
unsafe { bindings::Gecko_GetFlattenedTreeParentNode(self.0).map(GeckoNode) }
}
}
/// This logic is duplicated in Gecko's nsIContent::IsRootOfNativeAnonymousSubtree. /// This logic is duplicated in Gecko's nsIContent::IsRootOfNativeAnonymousSubtree.
fn is_root_of_native_anonymous_subtree(&self) -> bool { fn is_root_of_native_anonymous_subtree(&self) -> bool {
use gecko_bindings::structs::NODE_IS_NATIVE_ANONYMOUS_ROOT; use gecko_bindings::structs::NODE_IS_NATIVE_ANONYMOUS_ROOT;
@ -240,12 +250,24 @@ impl<'ln> TNode for GeckoNode<'ln> {
GeckoNode(&*(n.0 as *mut RawGeckoNode)) GeckoNode(&*(n.0 as *mut RawGeckoNode))
} }
fn children(self) -> LayoutIterator<GeckoChildrenIterator<'ln>> { fn parent_node(&self) -> Option<Self> {
unsafe { self.0.mParent.as_ref().map(GeckoNode) }
}
fn children(&self) -> LayoutIterator<GeckoChildrenIterator<'ln>> {
LayoutIterator(self.dom_children())
}
fn traversal_parent(&self) -> Option<GeckoElement<'ln>> {
self.flattened_tree_parent().and_then(|n| n.as_element())
}
fn traversal_children(&self) -> LayoutIterator<GeckoChildrenIterator<'ln>> {
let maybe_iter = unsafe { Gecko_MaybeCreateStyleChildrenIterator(self.0) }; let maybe_iter = unsafe { Gecko_MaybeCreateStyleChildrenIterator(self.0) };
if let Some(iter) = maybe_iter.into_owned_opt() { if let Some(iter) = maybe_iter.into_owned_opt() {
LayoutIterator(GeckoChildrenIterator::GeckoIterator(iter)) LayoutIterator(GeckoChildrenIterator::GeckoIterator(iter))
} else { } else {
LayoutIterator(GeckoChildrenIterator::Current(self.first_child())) LayoutIterator(self.dom_children())
} }
} }
@ -277,16 +299,6 @@ impl<'ln> TNode for GeckoNode<'ln> {
// Maybe this isnt useful for Gecko? // Maybe this isnt useful for Gecko?
} }
fn parent_node(&self) -> Option<Self> {
let fast_path = self.flattened_tree_parent_is_parent();
debug_assert!(fast_path == unsafe { bindings::Gecko_FlattenedTreeParentIsParent(self.0) });
if fast_path {
unsafe { self.0.mParent.as_ref().map(GeckoNode) }
} else {
unsafe { bindings::Gecko_GetFlattenedTreeParentNode(self.0).map(GeckoNode) }
}
}
fn is_in_doc(&self) -> bool { fn is_in_doc(&self) -> bool {
unsafe { bindings::Gecko_IsInDocument(self.0) } unsafe { bindings::Gecko_IsInDocument(self.0) }
} }
@ -644,9 +656,10 @@ impl<'le> TElement for GeckoElement<'le> {
fn inheritance_parent(&self) -> Option<Self> { fn inheritance_parent(&self) -> Option<Self> {
if self.is_native_anonymous() { if self.is_native_anonymous() {
return self.closest_non_native_anonymous_ancestor(); self.closest_non_native_anonymous_ancestor()
} else {
self.as_node().flattened_tree_parent().and_then(|n| n.as_element())
} }
return self.parent_element();
} }
fn closest_non_native_anonymous_ancestor(&self) -> Option<Self> { fn closest_non_native_anonymous_ancestor(&self) -> Option<Self> {

View file

@ -166,7 +166,7 @@ impl StylesheetInvalidationSet {
let mut any_children_invalid = false; let mut any_children_invalid = false;
for child in element.as_node().children() { for child in element.as_node().traversal_children() {
let child = match child.as_element() { let child = match child.as_element() {
Some(e) => e, Some(e) => e,
None => continue, None => continue,

View file

@ -230,7 +230,7 @@ trait PrivateMatchMethods: TElement {
fn layout_parent(&self) -> Self { fn layout_parent(&self) -> Self {
let mut current = self.clone(); let mut current = self.clone();
loop { loop {
current = match current.parent_element() { current = match current.traversal_parent() {
Some(el) => el, Some(el) => el,
None => return current, None => return current,
}; };

View file

@ -64,7 +64,7 @@ pub fn traverse_dom<E, D>(traversal: &D,
// it on the context to make it available to the bottom-up phase. // it on the context to make it available to the bottom-up phase.
let depth = if token.traverse_unstyled_children_only() { let depth = if token.traverse_unstyled_children_only() {
debug_assert!(!D::needs_postorder_traversal()); debug_assert!(!D::needs_postorder_traversal());
for kid in root.as_node().children() { for kid in root.as_node().traversal_children() {
if kid.as_element().map_or(false, |el| el.get_data().is_none()) { if kid.as_element().map_or(false, |el| el.get_data().is_none()) {
nodes.push(unsafe { SendNode::new(kid) }); nodes.push(unsafe { SendNode::new(kid) });
} }

View file

@ -32,7 +32,7 @@ pub fn traverse_dom<E, D>(traversal: &D,
let root_depth = root.depth(); let root_depth = root.depth();
if token.traverse_unstyled_children_only() { if token.traverse_unstyled_children_only() {
for kid in root.as_node().children() { for kid in root.as_node().traversal_children() {
if kid.as_element().map_or(false, |el| el.get_data().is_none()) { if kid.as_element().map_or(false, |el| el.get_data().is_none()) {
discovered.push_back(WorkItem(kid, root_depth + 1)); discovered.push_back(WorkItem(kid, root_depth + 1));
} }

View file

@ -185,10 +185,10 @@ impl ValidationData {
// filter, to avoid thrashing the filter. // filter, to avoid thrashing the filter.
let bloom_to_use = if bloom_known_valid { let bloom_to_use = if bloom_known_valid {
debug_assert_eq!(bloom.current_parent(), debug_assert_eq!(bloom.current_parent(),
element.parent_element()); element.traversal_parent());
Some(bloom.filter()) Some(bloom.filter())
} else { } else {
if bloom.current_parent() == element.parent_element() { if bloom.current_parent() == element.traversal_parent() {
Some(bloom.filter()) Some(bloom.filter())
} else { } else {
None None
@ -344,7 +344,7 @@ impl<E: TElement> StyleSharingTarget<E> {
return StyleSharingResult::CannotShare; return StyleSharingResult::CannotShare;
} }
debug_assert_eq!(bloom_filter.current_parent(), debug_assert_eq!(bloom_filter.current_parent(),
self.element.parent_element()); self.element.traversal_parent());
let result = cache let result = cache
.share_style_if_possible(shared_context, .share_style_if_possible(shared_context,
@ -500,7 +500,7 @@ impl<E: TElement> StyleSharingCandidateCache<E> {
dom_depth: usize) { dom_depth: usize) {
use selectors::matching::AFFECTED_BY_PRESENTATIONAL_HINTS; use selectors::matching::AFFECTED_BY_PRESENTATIONAL_HINTS;
let parent = match element.parent_element() { let parent = match element.traversal_parent() {
Some(element) => element, Some(element) => element,
None => { None => {
debug!("Failing to insert to the cache: no parent element"); debug!("Failing to insert to the cache: no parent element");
@ -572,7 +572,7 @@ impl<E: TElement> StyleSharingCandidateCache<E> {
return StyleSharingResult::CannotShare return StyleSharingResult::CannotShare
} }
if target.parent_element().is_none() { if target.traversal_parent().is_none() {
debug!("{:?} Cannot share style: element has no parent", debug!("{:?} Cannot share style: element has no parent",
target.element); target.element);
return StyleSharingResult::CannotShare return StyleSharingResult::CannotShare
@ -644,8 +644,8 @@ impl<E: TElement> StyleSharingCandidateCache<E> {
// Check that we have the same parent, or at least the same pointer // Check that we have the same parent, or at least the same pointer
// identity for parent computed style. The latter check allows us to // identity for parent computed style. The latter check allows us to
// share style between cousins if the parents shared style. // share style between cousins if the parents shared style.
let parent = target.parent_element(); let parent = target.traversal_parent();
let candidate_parent = candidate.element.parent_element(); let candidate_parent = candidate.element.traversal_parent();
if parent != candidate_parent && if parent != candidate_parent &&
!checks::same_computed_values(parent, candidate_parent) { !checks::same_computed_values(parent, candidate_parent) {
miss!(Parent) miss!(Parent)

View file

@ -192,7 +192,7 @@ pub trait DomTraversal<E: TElement> : Sync {
if node.opaque() == root { if node.opaque() == root {
break; break;
} }
let parent = node.parent_element().unwrap(); let parent = node.traversal_parent().unwrap();
let remaining = parent.did_process_child(); let remaining = parent.did_process_child();
if remaining != 0 { if remaining != 0 {
// The parent has other unprocessed descendants. We only // The parent has other unprocessed descendants. We only
@ -308,7 +308,7 @@ pub trait DomTraversal<E: TElement> : Sync {
// But it may be that we no longer match, so detect that case // But it may be that we no longer match, so detect that case
// and act appropriately here. // and act appropriately here.
if el.is_native_anonymous() { if el.is_native_anonymous() {
if let Some(parent) = el.parent_element() { if let Some(parent) = el.traversal_parent() {
let parent_data = parent.borrow_data().unwrap(); let parent_data = parent.borrow_data().unwrap();
let going_to_reframe = parent_data.get_restyle().map_or(false, |r| { let going_to_reframe = parent_data.get_restyle().map_or(false, |r| {
(r.damage | r.damage_handled()) (r.damage | r.damage_handled())
@ -467,7 +467,7 @@ pub trait DomTraversal<E: TElement> : Sync {
return; return;
} }
for kid in parent.as_node().children() { for kid in parent.as_node().traversal_children() {
if Self::node_needs_traversal(kid, self.shared_context().traversal_flags) { if Self::node_needs_traversal(kid, self.shared_context().traversal_flags) {
// If we are in a restyle for reconstruction, there is no need to // If we are in a restyle for reconstruction, there is no need to
// perform a post-traversal, so we don't need to set the dirty // perform a post-traversal, so we don't need to set the dirty
@ -527,7 +527,7 @@ fn resolve_style_internal<E, F>(context: &mut StyleContext<E>,
// If the Element isn't styled, we need to compute its style. // If the Element isn't styled, we need to compute its style.
if data.get_styles().is_none() { if data.get_styles().is_none() {
// Compute the parent style if necessary. // Compute the parent style if necessary.
let parent = element.parent_element(); let parent = element.traversal_parent();
if let Some(p) = parent { if let Some(p) = parent {
display_none_root = resolve_style_internal(context, p, ensure_data); display_none_root = resolve_style_internal(context, p, ensure_data);
} }
@ -604,7 +604,7 @@ pub fn resolve_style<E, F, G, H>(context: &mut StyleContext<E>, element: E,
break; break;
} }
clear_data(curr); clear_data(curr);
curr = match curr.parent_element() { curr = match curr.traversal_parent() {
Some(parent) => parent, Some(parent) => parent,
None => break, None => break,
}; };
@ -632,7 +632,7 @@ pub fn resolve_default_style<E, F, G, H>(context: &mut StyleContext<E>,
let mut e = element; let mut e = element;
loop { loop {
old_data.push((e, set_data(e, None))); old_data.push((e, set_data(e, None)));
match e.parent_element() { match e.traversal_parent() {
Some(parent) => e = parent, Some(parent) => e = parent,
None => break, None => break,
} }
@ -862,8 +862,8 @@ fn preprocess_children<E, D>(context: &mut StyleContext<E>,
{ {
trace!("preprocess_children: {:?}", element); trace!("preprocess_children: {:?}", element);
// Loop over all the children. // Loop over all the traversal children.
for child in element.as_node().children() { for child in element.as_node().traversal_children() {
// FIXME(bholley): Add TElement::element_children instead of this. // FIXME(bholley): Add TElement::element_children instead of this.
let child = match child.as_element() { let child = match child.as_element() {
Some(el) => el, Some(el) => el,
@ -919,7 +919,7 @@ fn preprocess_children<E, D>(context: &mut StyleContext<E>,
/// Clear style data for all the subtree under `el`. /// Clear style data for all the subtree under `el`.
pub fn clear_descendant_data<E: TElement, F: Fn(E)>(el: E, clear_data: &F) { pub fn clear_descendant_data<E: TElement, F: Fn(E)>(el: E, clear_data: &F) {
for kid in el.as_node().children() { for kid in el.as_node().traversal_children() {
if let Some(kid) = kid.as_element() { if let Some(kid) = kid.as_element() {
// We maintain an invariant that, if an element has data, all its ancestors // We maintain an invariant that, if an element has data, all its ancestors
// have data as well. By consequence, any element without data has no // have data as well. By consequence, any element without data has no

View file

@ -198,7 +198,7 @@ fn traverse_subtree(element: GeckoElement,
snapshots: &ServoElementSnapshotTable) { snapshots: &ServoElementSnapshotTable) {
// When new content is inserted in a display:none subtree, we will call into // When new content is inserted in a display:none subtree, we will call into
// servo to try to style it. Detect that here and bail out. // servo to try to style it. Detect that here and bail out.
if let Some(parent) = element.parent_element() { if let Some(parent) = element.traversal_parent() {
if parent.borrow_data().map_or(true, |d| d.styles().is_display_none()) { if parent.borrow_data().map_or(true, |d| d.styles().is_display_none()) {
debug!("{:?} has unstyled parent {:?} - ignoring call to traverse_subtree", element, parent); debug!("{:?} has unstyled parent {:?} - ignoring call to traverse_subtree", element, parent);
return; return;
@ -2398,7 +2398,7 @@ unsafe fn maybe_restyle<'a>(data: &'a mut AtomicRefMut<ElementData>,
} }
// Propagate the bit up the chain. // Propagate the bit up the chain.
if let Some(p) = element.parent_element() { if let Some(p) = element.traversal_parent() {
if animation_only { if animation_only {
p.note_descendants::<AnimationOnlyDirtyDescendants>(); p.note_descendants::<AnimationOnlyDirtyDescendants>();
} else { } else {
@ -2735,7 +2735,7 @@ pub extern "C" fn Servo_AssertTreeIsClean(root: RawGeckoElementBorrowed) {
let root = GeckoElement(root); let root = GeckoElement(root);
fn assert_subtree_is_clean<'le>(el: GeckoElement<'le>) { fn assert_subtree_is_clean<'le>(el: GeckoElement<'le>) {
debug_assert!(!el.has_dirty_descendants() && !el.has_animation_only_dirty_descendants()); debug_assert!(!el.has_dirty_descendants() && !el.has_animation_only_dirty_descendants());
for child in el.as_node().children() { for child in el.as_node().traversal_children() {
if let Some(child) = child.as_element() { if let Some(child) = child.as_element() {
assert_subtree_is_clean(child); assert_subtree_is_clean(child);
} }