style: Distinguish between the tree structures used for traversal and selector matching. draft
authorCameron McCormack <cam@mcc.id.au>
Fri, 09 Jun 2017 12:05:39 +0800
changeset 591536 0e047c5148bb30c67b2b3cf78a5fd087670697c8
parent 591535 044380ba86db494ecaa11404134609f37b288a23
child 591537 eae0475ea3a26a13112ac0e30dae5be9c62dc53d
push id63078
push userbmo:cam@mcc.id.au
push dateFri, 09 Jun 2017 06:22:34 +0000
milestone55.0a1
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. MozReview-Commit-ID: 6ZpDLl2Snwn
servo/components/style/bloom.rs
servo/components/style/dom.rs
servo/components/style/gecko/wrapper.rs
servo/components/style/invalidation/stylesheets.rs
servo/components/style/matching.rs
servo/components/style/parallel.rs
servo/components/style/sequential.rs
servo/components/style/sharing/mod.rs
servo/components/style/traversal.rs
servo/ports/geckolib/glue.rs
--- a/servo/components/style/bloom.rs
+++ b/servo/components/style/bloom.rs
@@ -84,17 +84,17 @@ impl<E: TElement> StyleBloom<E> {
         &*self.filter
     }
 
     /// Push an element to the bloom filter, knowing that it's a child of the
     /// last element parent.
     pub fn push(&mut self, element: E) {
         if cfg!(debug_assertions) {
             if self.elements.is_empty() {
-                assert!(element.parent_element().is_none());
+                assert!(element.traversal_parent().is_none());
             }
         }
         self.push_internal(element);
     }
 
     /// Same as `push`, but without asserting, in order to use it from
     /// `rebuild`.
     fn push_internal(&mut self, element: E) {
@@ -134,33 +134,33 @@ impl<E: TElement> StyleBloom<E> {
         self.filter.clear();
         self.elements.clear();
     }
 
     /// Rebuilds the bloom filter up to the parent of the given element.
     pub fn rebuild(&mut self, mut element: E) {
         self.clear();
 
-        while let Some(parent) = element.parent_element() {
+        while let Some(parent) = element.traversal_parent() {
             self.push_internal(parent);
             element = parent;
         }
 
         // Put them in the order we expect, from root to `element`'s parent.
         self.elements.reverse();
     }
 
     /// In debug builds, asserts that all the parents of `element` are in the
     /// bloom filter.
     ///
     /// Goes away in release builds.
     pub fn assert_complete(&self, mut element: E) {
         if cfg!(debug_assertions) {
             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]);
                 element = parent;
                 checked += 1;
             }
             assert_eq!(checked, self.elements.len());
         }
     }
 
@@ -185,26 +185,26 @@ impl<E: TElement> StyleBloom<E> {
                                      element_depth: usize)
     {
         // Easy case, we're in a different restyle, or we're empty.
         if self.elements.is_empty() {
             self.rebuild(element);
             return;
         }
 
-        let parent_element = match element.parent_element() {
+        let traversal_parent = match element.traversal_parent() {
             Some(parent) => parent,
             None => {
                 // Yay, another easy case.
                 self.clear();
                 return;
             }
         };
 
-        if self.current_parent() == Some(parent_element) {
+        if self.current_parent() == Some(traversal_parent) {
             // Ta da, cache hit, we're all done.
             return;
         }
 
         if element_depth == 0 {
             self.clear();
             return;
         }
@@ -227,32 +227,32 @@ impl<E: TElement> StyleBloom<E> {
         // If the filter represents an element too deep in the dom, we need to
         // pop ancestors.
         while current_depth > element_depth - 1 {
             self.pop().expect("Emilio is bad at math");
             current_depth -= 1;
         }
 
         // Now let's try to find a common parent in the bloom filter chain,
-        // starting with parent_element.
-        let mut common_parent = parent_element;
+        // starting with traversal_parent.
+        let mut common_parent = traversal_parent;
         let mut common_parent_depth = element_depth - 1;
 
         // Let's collect the parents we are going to need to insert once we've
         // found the common one.
         let mut parents_to_insert = SmallVec::<[E; 8]>::new();
 
         // If the bloom filter still doesn't have enough elements, the common
         // parent is up in the dom.
         while common_parent_depth > current_depth {
             // TODO(emilio): Seems like we could insert parents here, then
             // reverse the slice.
             parents_to_insert.push(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;
         }
 
         // Now the two depths are the same.
         debug_assert_eq!(common_parent_depth, current_depth);
 
         // Happy case: The parents match, we only need to push the ancestors
         // we've collected and we'll never enter in this loop.
@@ -264,17 +264,17 @@ impl<E: TElement> StyleBloom<E> {
         // hangs off the document (such as scrollbars) as a separate subtree
         // from the document root.
         //
         // Thus it's possible with Gecko that we do not find any common
         // ancestor.
         while **self.elements.last().unwrap() != common_parent {
             parents_to_insert.push(common_parent);
             self.pop().unwrap();
-            common_parent = match common_parent.parent_element() {
+            common_parent = match common_parent.traversal_parent() {
                 Some(parent) => parent,
                 None => {
                     debug_assert!(self.elements.is_empty());
                     if cfg!(feature = "gecko") {
                         break;
                     } else {
                         panic!("should have found a common ancestor");
                     }
--- a/servo/components/style/dom.rs
+++ b/servo/components/style/dom.rs
@@ -103,27 +103,37 @@ pub trait TNode : Sized + Copy + Clone +
     type ConcreteChildrenIterator: Iterator<Item = Self>;
 
     /// Convert this node in an `UnsafeNode`.
     fn to_unsafe(&self) -> UnsafeNode;
 
     /// Get a node back from an `UnsafeNode`.
     unsafe fn from_unsafe(n: &UnsafeNode) -> Self;
 
-    /// Returns an iterator over this node's children.
-    fn children(self) -> LayoutIterator<Self::ConcreteChildrenIterator>;
-
-    /// Converts self into an `OpaqueNode`.
-    fn opaque(&self) -> OpaqueNode;
+    /// Get this node's parent node.
+    fn parent_node(&self) -> Option<Self>;
 
     /// Get this node's parent element if present.
     fn parent_element(&self) -> Option<Self::ConcreteElement> {
         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.
     fn debug_id(self) -> usize;
 
     /// Get this node as an element, if it's one.
     fn as_element(&self) -> Option<Self::ConcreteElement>;
 
     /// Whether this node needs to be laid out on viewport size change.
     fn needs_dirty_on_viewport_size_changed(&self) -> bool;
@@ -133,19 +143,16 @@ pub trait TNode : Sized + Copy + Clone +
 
     /// Whether this node can be fragmented. This is used for multicol, and only
     /// for Servo.
     fn can_be_fragmented(&self) -> bool;
 
     /// Set whether this node can be fragmented.
     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
     /// restyle data appropriately on some forced restyles.
     fn is_in_doc(&self) -> bool;
 }
 
 /// Wrapper to output the ElementData along with the node when formatting for
 /// Debug.
 pub struct ShowData<N: TNode>(pub N);
@@ -217,17 +224,17 @@ fn fmt_with_data_and_primary_values<N: T
 fn fmt_subtree<F, N: TNode>(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() {
+    for kid in n.traversal_children() {
         try!(writeln!(f, ""));
         try!(fmt_subtree(f, stringify, kid, indent + 1));
     }
 
     Ok(())
 }
 
 /// Flag that this element has a descendant for style processing, propagating
@@ -251,17 +258,17 @@ pub unsafe fn raw_note_descendants<E, B>
                   "You should ensure you only flag styled elements");
 
     let mut curr = Some(element);
     while let Some(el) = curr {
         if B::has(el) {
             break;
         }
         B::set(el);
-        curr = el.parent_element();
+        curr = el.traversal_parent();
     }
 
     // Note: We disable this assertion on servo because of bugs. See the
     // comment around note_dirty_descendant in layout/wrapper.rs.
     if cfg!(feature = "gecko") {
         debug_assert!(element.descendants_bit_is_propagated::<B>());
     }
 
@@ -292,17 +299,17 @@ pub trait TElement : Eq + PartialEq + De
 
     /// Get this element as a node.
     fn as_node(&self) -> Self::ConcreteNode;
 
     /// Returns the depth of this element in the DOM.
     fn depth(&self) -> usize {
         let mut depth = 0;
         let mut curr = *self;
-        while let Some(parent) = curr.parent_element() {
+        while let Some(parent) = curr.traversal_parent() {
             depth += 1;
             curr = parent;
         }
 
         depth
     }
 
     /// While doing a reflow, the element at the root has no parent, as far as we're
@@ -310,21 +317,28 @@ pub trait TElement : Eq + PartialEq + De
     fn layout_parent_element(self, reflow_root: OpaqueNode) -> Option<Self> {
         if self.as_node().opaque() == reflow_root {
             None
         } else {
             self.parent_element()
         }
     }
 
+    /// 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.
     ///
     /// 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
-    /// non-NAC ancestor.
+    /// of Gecko's Native Anonymous Content, which uses the traversal parent
+    /// (i.e. the flattened tree parent) and which also may need to find the
+    /// closest non-NAC ancestor.
     fn inheritance_parent(&self) -> Option<Self> {
         self.parent_element()
     }
 
     /// For a given NAC element, return the closest non-NAC ancestor, which is
     /// guaranteed to exist.
     fn closest_non_native_anonymous_ancestor(&self) -> Option<Self> {
         unreachable!("Servo doesn't know about NAC");
@@ -440,17 +454,17 @@ pub trait TElement : Eq + PartialEq + De
     /// Only safe to call with exclusive access to the element.
     unsafe fn set_dirty_descendants(&self);
 
     /// Debug helper to be sure the bit is propagated.
     fn descendants_bit_is_propagated<B: DescendantsBit<Self>>(&self) -> bool {
         let mut current = Some(*self);
         while let Some(el) = current {
             if !B::has(el) { return false; }
-            current = el.parent_element();
+            current = el.traversal_parent();
         }
 
         true
     }
 
     /// Flag that this element has no descendant for style processing.
     ///
     /// Only safe to call with exclusive access to the element.
--- a/servo/components/style/gecko/wrapper.rs
+++ b/servo/components/style/gecko/wrapper.rs
@@ -199,16 +199,26 @@ impl<'ln> GeckoNode<'ln> {
 
         if parent_el.map_or(false, |el| el.has_shadow_root()) {
             return false;
         }
 
         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.
     fn is_root_of_native_anonymous_subtree(&self) -> bool {
         use gecko_bindings::structs::NODE_IS_NATIVE_ANONYMOUS_ROOT;
         return self.flags() & (NODE_IS_NATIVE_ANONYMOUS_ROOT as u32) != 0
     }
 
     fn contains_non_whitespace_content(&self) -> bool {
         unsafe { Gecko_IsSignificantChild(self.0, true, false) }
@@ -235,22 +245,34 @@ impl<'ln> TNode for GeckoNode<'ln> {
     fn to_unsafe(&self) -> UnsafeNode {
         (self.0 as *const _ as usize, 0)
     }
 
     unsafe fn from_unsafe(n: &UnsafeNode) -> Self {
         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) };
         if let Some(iter) = maybe_iter.into_owned_opt() {
             LayoutIterator(GeckoChildrenIterator::GeckoIterator(iter))
         } else {
-            LayoutIterator(GeckoChildrenIterator::Current(self.first_child()))
+            LayoutIterator(self.dom_children())
         }
     }
 
     fn opaque(&self) -> OpaqueNode {
         let ptr: usize = self.0 as *const _ as usize;
         OpaqueNode(ptr)
     }
 
@@ -272,26 +294,16 @@ impl<'ln> TNode for GeckoNode<'ln> {
         false
     }
 
     unsafe fn set_can_be_fragmented(&self, _value: bool) {
         // FIXME(SimonSapin): Servo uses this to implement CSS multicol / fragmentation
         // Maybe this isn’t 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 {
         unsafe { bindings::Gecko_IsInDocument(self.0) }
     }
 
     fn needs_dirty_on_viewport_size_changed(&self) -> bool {
         // Gecko's node doesn't have the DIRTY_ON_VIEWPORT_SIZE_CHANGE flag,
         // so we force them to be dirtied on viewport size change, regardless if
         // they use viewport percentage size or not.
@@ -639,19 +651,20 @@ impl structs::FontSizePrefs {
 }
 
 impl<'le> TElement for GeckoElement<'le> {
     type ConcreteNode = GeckoNode<'le>;
     type FontMetricsProvider = GeckoFontMetricsProvider;
 
     fn inheritance_parent(&self) -> Option<Self> {
         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> {
         debug_assert!(self.is_native_anonymous());
         let mut parent = match self.parent_element() {
             Some(e) => e,
             None => return None,
         };
@@ -869,17 +882,17 @@ impl<'le> TElement for GeckoElement<'le>
         // We have to update animations even if the element has no computed
         // style since it means the element is in a display:none subtree, we
         // should destroy all CSS animations in display:none subtree.
         let computed_data = self.borrow_data();
         let computed_values =
             computed_data.as_ref().map(|d| d.styles().primary.values());
         let computed_values_opt =
             computed_values.map(|v| *HasArcFFI::arc_as_borrowed(v));
-        let parent_element = self.parent_element();
+        let parent_element = self.inheritance_parent();
         let parent_data =
             parent_element.as_ref().and_then(|e| e.borrow_data());
         let parent_values =
             parent_data.as_ref().map(|d| d.styles().primary.values());
         let parent_values_opt =
             parent_values.map(|v| *HasArcFFI::arc_as_borrowed(v));
         let before_change_values =
             before_change_style.as_ref().map(|v| *HasArcFFI::arc_as_borrowed(v));
--- a/servo/components/style/invalidation/stylesheets.rs
+++ b/servo/components/style/invalidation/stylesheets.rs
@@ -161,17 +161,17 @@ impl StylesheetInvalidationSet {
                 data.ensure_restyle().hint.insert(StoredRestyleHint::subtree());
                 return true;
             }
         }
 
 
         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() {
                 Some(e) => e,
                 None => continue,
             };
 
             any_children_invalid |= self.process_invalidations_in_subtree(child);
         }
 
--- a/servo/components/style/matching.rs
+++ b/servo/components/style/matching.rs
@@ -225,17 +225,17 @@ trait PrivateMatchMethods: TElement {
     ///
     /// Returns itself if the element has no parent. In practice this doesn't
     /// happen because the root element is blockified per spec, but it could
     /// happen if we decide to not blockify for roots of disconnected subtrees,
     /// which is a kind of dubious beahavior.
     fn layout_parent(&self) -> Self {
         let mut current = self.clone();
         loop {
-            current = match current.parent_element() {
+            current = match current.traversal_parent() {
                 Some(el) => el,
                 None => return current,
             };
 
             let is_display_contents =
                 current.borrow_data().unwrap().styles().primary.values().is_display_contents();
 
             if !is_display_contents {
--- a/servo/components/style/parallel.rs
+++ b/servo/components/style/parallel.rs
@@ -71,17 +71,17 @@ pub fn traverse_dom<E, D>(traversal: &D,
     let mut nodes = NodeList::<E::ConcreteNode>::new();
 
     debug_assert!(traversal.is_parallel());
     // 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 depth = if token.traverse_unstyled_children_only() {
         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()) {
                 nodes.push(unsafe { SendNode::new(kid) });
             }
         }
         root.depth() + 1
     } else {
         nodes.push(unsafe { SendNode::new(root.as_node()) });
         root.depth()
--- a/servo/components/style/sequential.rs
+++ b/servo/components/style/sequential.rs
@@ -27,17 +27,17 @@ pub fn traverse_dom<E, D>(traversal: &D,
     debug_assert!(!traversal.is_parallel());
     debug_assert!(token.should_traverse());
 
     let mut discovered = VecDeque::<WorkItem<E::ConcreteNode>>::with_capacity(16);
     let mut tlc = traversal.create_thread_local_context();
     let root_depth = root.depth();
 
     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()) {
                 discovered.push_back(WorkItem(kid, root_depth + 1));
             }
         }
     } else {
         discovered.push_back(WorkItem(root.as_node(), root_depth));
     }
 
--- a/servo/components/style/sharing/mod.rs
+++ b/servo/components/style/sharing/mod.rs
@@ -173,20 +173,20 @@ impl ValidationData {
             // The bloom filter may already be set up for our element.
             // If it is, use it.  If not, we must be in a candidate
             // (i.e. something in the cache), and the element is one
             // of our cousins, not a sibling.  In that case, we'll
             // just do revalidation selector matching without a bloom
             // filter, to avoid thrashing the filter.
             let bloom_to_use = if bloom_known_valid {
                 debug_assert_eq!(bloom.current_parent(),
-                                 element.parent_element());
+                                 element.traversal_parent());
                 Some(bloom.filter())
             } else {
-                if bloom.current_parent() == element.parent_element() {
+                if bloom.current_parent() == element.traversal_parent() {
                     Some(bloom.filter())
                 } else {
                     None
                 }
             };
             self.revalidation_match_results =
                 Some(stylist.match_revalidation_selectors(&element,
                                                           bloom_to_use,
@@ -332,17 +332,17 @@ impl<E: TElement> StyleSharingTarget<E> 
         let bloom_filter = &context.thread_local.bloom_filter;
 
         if cache.dom_depth != bloom_filter.matching_depth() {
             debug!("Can't share style, because DOM depth changed from {:?} to {:?}, element: {:?}",
                    cache.dom_depth, bloom_filter.matching_depth(), self.element);
             return StyleSharingResult::CannotShare;
         }
         debug_assert_eq!(bloom_filter.current_parent(),
-                         self.element.parent_element());
+                         self.element.traversal_parent());
 
         let result = cache
             .share_style_if_possible(shared_context,
                                      selector_flags_map,
                                      bloom_filter,
                                      &mut self,
                                      data);
 
@@ -433,17 +433,17 @@ impl<E: TElement> StyleSharingCandidateC
     pub fn insert_if_possible(&mut self,
                               element: &E,
                               style: &ComputedValues,
                               relations: StyleRelations,
                               mut validation_data: ValidationData,
                               dom_depth: usize) {
         use selectors::matching::AFFECTED_BY_PRESENTATIONAL_HINTS;
 
-        let parent = match element.parent_element() {
+        let parent = match element.traversal_parent() {
             Some(element) => element,
             None => {
                 debug!("Failing to insert to the cache: no parent element");
                 return;
             }
         };
 
         if element.is_native_anonymous() {
@@ -510,17 +510,17 @@ impl<E: TElement> StyleSharingCandidateC
         data: &mut ElementData
     ) -> StyleSharingResult {
         if shared_context.options.disable_style_sharing_cache {
             debug!("{:?} Cannot share style: style sharing cache disabled",
                    target.element);
             return StyleSharingResult::CannotShare
         }
 
-        if target.parent_element().is_none() {
+        if target.traversal_parent().is_none() {
             debug!("{:?} Cannot share style: element has no parent",
                    target.element);
             return StyleSharingResult::CannotShare
         }
 
         if target.is_native_anonymous() {
             debug!("{:?} Cannot share style: NAC", target.element);
             return StyleSharingResult::CannotShare;
@@ -595,18 +595,18 @@ impl<E: TElement> StyleSharingCandidateC
             ($miss: ident) => {
                 return Err(CacheMiss::$miss);
             }
         }
 
         // Check that we have the same parent, or at least the same pointer
         // identity for parent computed style. The latter check allows us to
         // share style between cousins if the parents shared style.
-        let parent = target.parent_element();
-        let candidate_parent = candidate.element.parent_element();
+        let parent = target.traversal_parent();
+        let candidate_parent = candidate.element.traversal_parent();
         if parent != candidate_parent &&
            !checks::same_computed_values(parent, candidate_parent) {
             miss!(Parent)
         }
 
         if target.is_native_anonymous() {
             debug_assert!(!candidate.element.is_native_anonymous(),
                           "Why inserting NAC into the cache?");
--- a/servo/components/style/traversal.rs
+++ b/servo/components/style/traversal.rs
@@ -187,17 +187,17 @@ pub trait DomTraversal<E: TElement> : Sy
 
         if children_to_process == 0 {
             // We are a leaf. Walk up the chain.
             loop {
                 self.process_postorder(thread_local, node);
                 if node.opaque() == root {
                     break;
                 }
-                let parent = node.parent_element().unwrap();
+                let parent = node.traversal_parent().unwrap();
                 let remaining = parent.did_process_child();
                 if remaining != 0 {
                     // The parent has other unprocessed descendants. We only
                     // perform postorder processing after the last descendant
                     // has been processed.
                     break
                 }
 
@@ -303,17 +303,17 @@ pub trait DomTraversal<E: TElement> : Sy
         //
         // We need to conservatively continue the traversal to style the
         // pseudo-element in order to properly process potentially-new
         // transitions that we won't see otherwise.
         //
         // But it may be that we no longer match, so detect that case
         // and act appropriately here.
         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 going_to_reframe = parent_data.get_restyle().map_or(false, |r| {
                     (r.damage | r.damage_handled())
                         .contains(RestyleDamage::reconstruct())
                 });
 
                 let mut is_before_or_after_pseudo = false;
                 if let Some(pseudo) = el.implemented_pseudo_element() {
@@ -462,17 +462,17 @@ pub trait DomTraversal<E: TElement> : Sy
         let should_traverse =
             self.should_traverse_children(thread_local.borrow_mut(), parent,
                                           &parent.borrow_data().unwrap(), MayLog);
         thread_local.borrow_mut().end_element(parent);
         if !should_traverse {
             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 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
                 // descendants bit on the parent.
                 if !self.shared_context().traversal_flags.for_reconstruct() {
                     let el = kid.as_element();
                     if el.as_ref().and_then(|el| el.borrow_data())
                                   .map_or(false, |d| d.has_styles()) {
@@ -522,17 +522,17 @@ fn resolve_style_internal<E, F>(context:
 {
     ensure_data(element);
     let mut data = element.mutate_data().unwrap();
     let mut display_none_root = None;
 
     // If the Element isn't styled, we need to compute its style.
     if data.get_styles().is_none() {
         // Compute the parent style if necessary.
-        let parent = element.parent_element();
+        let parent = element.traversal_parent();
         if let Some(p) = parent {
             display_none_root = resolve_style_internal(context, p, ensure_data);
         }
 
         // Maintain the bloom filter. If it doesn't exist, we need to build it
         // from scratch. Otherwise we just need to push the parent.
         if context.thread_local.bloom_filter.is_empty() {
             context.thread_local.bloom_filter.rebuild(element);
@@ -599,17 +599,17 @@ pub fn resolve_style<E, F, G, H>(context
             unsafe {
                 curr.unset_dirty_descendants();
                 curr.unset_animation_only_dirty_descendants();
             }
             if in_doc && curr == display_none_root.unwrap() {
                 break;
             }
             clear_data(curr);
-            curr = match curr.parent_element() {
+            curr = match curr.traversal_parent() {
                 Some(parent) => parent,
                 None => break,
             };
         }
     }
 }
 
 /// Manually resolve default styles for the given Element, which are the styles
@@ -627,17 +627,17 @@ pub fn resolve_default_style<E, F, G, H>
           H: FnOnce(&ElementStyles)
 {
     // Save and clear out element data from the element and its ancestors.
     let mut old_data: SmallVec<[(E, Option<ElementData>); 8]> = SmallVec::new();
     {
         let mut e = element;
         loop {
             old_data.push((e, set_data(e, None)));
-            match e.parent_element() {
+            match e.traversal_parent() {
                 Some(parent) => e = parent,
                 None => break,
             }
         }
     }
 
     // Resolve styles up the tree.
     resolve_style_internal(context, element, ensure_data);
@@ -857,18 +857,18 @@ fn preprocess_children<E, D>(context: &m
                              element: E,
                              mut propagated_hint: StoredRestyleHint,
                              damage_handled: RestyleDamage)
     where E: TElement,
           D: DomTraversal<E>,
 {
     trace!("preprocess_children: {:?}", element);
 
-    // Loop over all the children.
-    for child in element.as_node().children() {
+    // Loop over all the traversal children.
+    for child in element.as_node().traversal_children() {
         // FIXME(bholley): Add TElement::element_children instead of this.
         let child = match child.as_element() {
             Some(el) => el,
             None => continue,
         };
 
         let mut child_data =
             unsafe { D::ensure_element_data(&child).borrow_mut() };
@@ -914,17 +914,17 @@ fn preprocess_children<E, D>(context: &m
 
         // Store the damage already handled by ancestors.
         restyle_data.set_damage_handled(damage_handled);
     }
 }
 
 /// Clear style data for all the subtree under `el`.
 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() {
             // 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
             // descendants with data.
             if kid.get_data().is_some() {
                 clear_data(kid);
                 clear_descendant_data(kid, clear_data);
             }
--- a/servo/ports/geckolib/glue.rs
+++ b/servo/ports/geckolib/glue.rs
@@ -191,17 +191,17 @@ fn create_shared_context<'a>(global_styl
 }
 
 fn traverse_subtree(element: GeckoElement,
                     raw_data: RawServoStyleSetBorrowed,
                     traversal_flags: TraversalFlags,
                     snapshots: &ServoElementSnapshotTable) {
     // 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.
-    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()) {
             debug!("{:?} has unstyled parent {:?} - ignoring call to traverse_subtree", element, parent);
             return;
         }
     }
 
     let per_doc_data = PerDocumentStyleData::from_ffi(raw_data).borrow();
     debug_assert!(!per_doc_data.stylesheets.has_changed());
@@ -2362,17 +2362,17 @@ unsafe fn maybe_restyle<'a>(data: &'a mu
     -> Option<&'a mut RestyleData>
 {
     // Don't generate a useless RestyleData if the element hasn't been styled.
     if !data.has_styles() {
         return None;
     }
 
     // Propagate the bit up the chain.
-    if let Some(p) = element.parent_element() {
+    if let Some(p) = element.traversal_parent() {
         if animation_only {
             p.note_descendants::<AnimationOnlyDirtyDescendants>();
         } else {
             p.note_descendants::<DirtyDescendants>();
         }
     };
 
     bindings::Gecko_SetOwnerDocumentNeedsStyleFlush(element.0);
@@ -2692,17 +2692,17 @@ pub extern "C" fn Servo_AnimationValue_C
 pub extern "C" fn Servo_AssertTreeIsClean(root: RawGeckoElementBorrowed) {
     if !cfg!(feature = "gecko_debug") {
         panic!("Calling Servo_AssertTreeIsClean in release build");
     }
 
     let root = GeckoElement(root);
     fn assert_subtree_is_clean<'le>(el: GeckoElement<'le>) {
         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() {
                 assert_subtree_is_clean(child);
             }
         }
     }
 
     assert_subtree_is_clean(root);
 }