style: Optimize all ids in the rightmost compound selector. draft
authorEmilio Cobos Álvarez <emilio@crisal.io>
Fri, 27 Oct 2017 12:59:02 +0200
changeset 690481 84dc6b1413c6ec8c75e2e22a41011026eeb05c4f
parent 690480 031f26da5cdd56f88bfd8b5cf28ded38a2dad241
child 690482 ba91c33d481a1d03cbb030a9e5a4c4291159e865
push id87308
push userbmo:emilio@crisal.io
push dateThu, 02 Nov 2017 01:16:37 +0000
milestone58.0a1
style: Optimize all ids in the rightmost compound selector. This is effectively the same optimization Gecko has now. MozReview-Commit-ID: 21HrjpHdpOz
servo/components/style/dom_apis.rs
--- a/servo/components/style/dom_apis.rs
+++ b/servo/components/style/dom_apis.rs
@@ -1,22 +1,23 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 //! Generic implementations of some DOM APIs so they can be shared between Servo
 //! and Gecko.
 
+use Atom;
 use context::QuirksMode;
 use dom::{TDocument, TElement, TNode};
 use invalidation::element::invalidator::{Invalidation, InvalidationProcessor, InvalidationVector};
 use selectors::{Element, NthIndexCache, SelectorList};
 use selectors::attr::CaseSensitivity;
 use selectors::matching::{self, MatchingContext, MatchingMode};
-use selectors::parser::{Component, LocalName};
+use selectors::parser::{Combinator, Component, LocalName};
 use smallvec::SmallVec;
 use std::borrow::Borrow;
 
 /// <https://dom.spec.whatwg.org/#dom-element-matches>
 pub fn element_matches<E>(
     element: &E,
     selector_list: &SelectorList<E::Impl>,
     quirks_mode: QuirksMode,
@@ -234,65 +235,94 @@ where
             return true;
         }
 
         current = n.parent_node();
     }
     false
 }
 
+fn find_elements_with_id<E, Q, F>(
+    root: E::ConcreteNode,
+    id: &Atom,
+    results: &mut Q::Output,
+    quirks_mode: QuirksMode,
+    mut filter: F,
+)
+where
+    E: TElement,
+    Q: SelectorQuery<E>,
+    F: FnMut(E) -> bool,
+{
+    let case_sensitivity = quirks_mode.classes_and_ids_case_sensitivity();
+    if case_sensitivity == CaseSensitivity::CaseSensitive &&
+       root.is_in_document()
+    {
+        let doc = root.owner_doc();
+        if let Ok(elements) = doc.elements_with_id(id) {
+            if root == doc.as_node() {
+                for element in elements {
+                    if !filter(*element) {
+                        continue;
+                    }
+
+                    Q::append_element(results, *element);
+                    if Q::should_stop_after_first_match() {
+                        break;
+                    }
+                }
+            } else {
+                for element in elements {
+                    if !element_is_descendant_of(*element, root) {
+                        continue;
+                    }
+
+                    if !filter(*element) {
+                        continue;
+                    }
+
+                    Q::append_element(results, *element);
+                    if Q::should_stop_after_first_match() {
+                        break;
+                    }
+                }
+            }
+        }
+        return;
+    }
+
+    collect_all_elements::<E, Q, _>(root, results, |element| {
+        element.has_id(id, case_sensitivity) && filter(element)
+    });
+}
+
+
 /// Fast paths for querySelector with a single simple selector.
 fn query_selector_single_query<E, Q>(
     root: E::ConcreteNode,
     component: &Component<E::Impl>,
     results: &mut Q::Output,
     quirks_mode: QuirksMode,
 ) -> Result<(), ()>
 where
     E: TElement,
     Q: SelectorQuery<E>,
 {
     match *component {
         Component::ExplicitUniversalType => {
             collect_all_elements::<E, Q, _>(root, results, |_| true)
         }
         Component::ID(ref id) => {
-            let case_sensitivity = quirks_mode.classes_and_ids_case_sensitivity();
-
-            if case_sensitivity == CaseSensitivity::CaseSensitive &&
-               root.is_in_document()
-            {
-                let doc = root.owner_doc();
-                if let Ok(elements) = doc.elements_with_id(id) {
-                    if root == doc.as_node() {
-                        for element in elements {
-                            Q::append_element(results, *element);
-                            if Q::should_stop_after_first_match() {
-                                break;
-                            }
-                        }
-                    } else {
-                        for element in elements {
-                            if !element_is_descendant_of(*element, root) {
-                                continue;
-                            }
-
-                            Q::append_element(results, *element);
-                            if Q::should_stop_after_first_match() {
-                                break;
-                            }
-                        }
-                    }
-                    return Ok(())
-                }
-            }
-
-            collect_all_elements::<E, Q, _>(root, results, |element| {
-                element.has_id(id, case_sensitivity)
-            })
+            find_elements_with_id::<E, Q, _>(
+                root,
+                id,
+                results,
+                quirks_mode,
+                |_| true,
+            )
         }
         Component::Class(ref class) => {
             let case_sensitivity = quirks_mode.classes_and_ids_case_sensitivity();
             collect_all_elements::<E, Q, _>(root, results, |element| {
                 element.has_class(class, case_sensitivity)
             })
         }
         Component::LocalName(LocalName { ref name, ref lower_name }) => {
@@ -316,43 +346,95 @@ where
 /// Fast paths for a given selector query.
 ///
 /// FIXME(emilio, nbp): This may very well be a good candidate for code to be
 /// replaced by HolyJit :)
 fn query_selector_fast<E, Q>(
     root: E::ConcreteNode,
     selector_list: &SelectorList<E::Impl>,
     results: &mut Q::Output,
-    quirks_mode: QuirksMode,
+    matching_context: &mut MatchingContext<E::Impl>,
 ) -> Result<(), ()>
 where
     E: TElement,
     Q: SelectorQuery<E>,
 {
     // We need to return elements in document order, and reordering them
     // afterwards is kinda silly.
     if selector_list.0.len() > 1 {
         return Err(());
     }
 
     let selector = &selector_list.0[0];
+    let quirks_mode = matching_context.quirks_mode();
 
     // Let's just care about the easy cases for now.
     if selector.len() == 1 {
         return query_selector_single_query::<E, Q>(
             root,
             selector.iter().next().unwrap(),
             results,
             quirks_mode,
         );
     }
 
-    // FIXME(emilio): Implement better optimizations for compound selectors and
-    // such.
-    Err(())
+    let mut iter = selector.iter();
+    let mut combinator: Option<Combinator> = None;
+
+    loop {
+        debug_assert!(combinator.map_or(true, |c| !c.is_sibling()));
+
+        for component in &mut iter {
+            match *component {
+                Component::ID(ref id) => {
+                    if combinator.is_none() {
+                        // In the rightmost compound, just find descednants of
+                        // root that match the selector list with that id.
+                        find_elements_with_id::<E, Q, _>(
+                            root,
+                            id,
+                            results,
+                            quirks_mode,
+                            |e| {
+                                matching::matches_selector_list(
+                                    selector_list,
+                                    &e,
+                                    matching_context,
+                                )
+                            }
+                        );
+
+                        return Ok(());
+                    }
+
+                    // TODO(emilio): Find descendants of `root` that are
+                    // descendants of an element with a given `id`.
+                }
+                _ => {},
+            }
+        }
+
+        loop {
+            let next_combinator = match iter.next_sequence() {
+                None => return Err(()),
+                Some(c) => c,
+            };
+
+            // We don't want to scan stuff affected by sibling combinators,
+            // given we scan the subtree of elements with a given id (and we
+            // don't want to care about scanning the siblings' subtrees).
+            if next_combinator.is_sibling() {
+                for _ in &mut iter {}
+                continue;
+            }
+
+            combinator = Some(next_combinator);
+            break;
+        }
+    }
 }
 
 // Slow path for a given selector query.
 fn query_selector_slow<E, Q>(
     root: E::ConcreteNode,
     selector_list: &SelectorList<E::Impl>,
     results: &mut Q::Output,
     matching_context: &mut MatchingContext<E::Impl>,
@@ -374,42 +456,44 @@ pub fn query_selector<E, Q>(
 )
 where
     E: TElement,
     Q: SelectorQuery<E>,
 {
     use invalidation::element::invalidator::TreeStyleInvalidator;
 
     let quirks_mode = root.owner_doc().quirks_mode();
-    let fast_result = query_selector_fast::<E, Q>(
-        root,
-        selector_list,
-        results,
-        quirks_mode,
-    );
 
-    if fast_result.is_ok() {
-        return;
-    }
-
-    // Slow path: Use the invalidation machinery if we're a root, and tree
-    // traversal otherwise.
-    //
-    // See the comment in collect_invalidations to see why only if we're a root.
     let mut nth_index_cache = NthIndexCache::default();
     let mut matching_context = MatchingContext::new(
         MatchingMode::Normal,
         None,
         Some(&mut nth_index_cache),
         quirks_mode,
     );
 
     let root_element = root.as_element();
     matching_context.scope_element = root_element.map(|e| e.opaque());
 
+    let fast_result = query_selector_fast::<E, Q>(
+        root,
+        selector_list,
+        results,
+        &mut matching_context,
+    );
+
+    if fast_result.is_ok() {
+        return;
+    }
+
+    // Slow path: Use the invalidation machinery if we're a root, and tree
+    // traversal otherwise.
+    //
+    // See the comment in collect_invalidations to see why only if we're a root.
+    //
     // The invalidation mechanism is only useful in presence of combinators.
     //
     // We could do that check properly here, though checking the length of the
     // selectors is a good heuristic.
     let invalidation_may_be_useful =
         selector_list.0.iter().any(|s| s.len() > 1);
 
     if root_element.is_some() || !invalidation_may_be_useful {