style: Add a fast path for querySelector{,All} when we have classes or tags in the rightmost compound.

Before this patch we were only optimizing the case of a single selector, which
is fine, but not enough to catch ones like .foo .bar or so.

This patch allows us to optimize classes and tags in the rightmost compound,
while keeping the current optimization for #id selectors.

Need to profile this, but code-wise should be ready for review.

Differential Revision: https://phabricator.services.mozilla.com/D9351
This commit is contained in:
Emilio Cobos Álvarez 2018-10-29 12:04:25 +00:00
parent a9af61e3be
commit a3fcfb6435
No known key found for this signature in database
GPG key ID: 056B727BB9C1027C

View file

@ -13,7 +13,7 @@ use invalidation::element::invalidator::{InvalidationProcessor, InvalidationVect
use selectors::{Element, NthIndexCache, SelectorList};
use selectors::attr::CaseSensitivity;
use selectors::matching::{self, MatchingContext, MatchingMode};
use selectors::parser::{Combinator, Component, LocalName};
use selectors::parser::{Combinator, Component, LocalName, SelectorImpl};
use smallvec::SmallVec;
use std::borrow::Borrow;
@ -333,6 +333,19 @@ fn collect_elements_with_id<E, Q, F>(
}
}
#[inline(always)]
fn local_name_matches<E>(element: E, local_name: &LocalName<E::Impl>) -> bool
where
E: TElement,
{
let LocalName { ref name, ref lower_name } = *local_name;
if element.is_html_element_in_html_document() {
element.local_name() == lower_name.borrow()
} else {
element.local_name() == name.borrow()
}
}
/// Fast paths for querySelector with a single simple selector.
fn query_selector_single_query<E, Q>(
root: E::ConcreteNode,
@ -357,16 +370,11 @@ where
element.has_class(class, case_sensitivity)
})
},
Component::LocalName(LocalName {
ref name,
ref lower_name,
}) => collect_all_elements::<E, Q, _>(root, results, |element| {
if element.is_html_element_in_html_document() {
element.local_name() == lower_name.borrow()
} else {
element.local_name() == name.borrow()
}
}),
Component::LocalName(ref local_name) => {
collect_all_elements::<E, Q, _>(root, results, |element| {
local_name_matches(element, local_name)
})
},
// TODO(emilio): More fast paths?
_ => return Err(()),
}
@ -374,8 +382,18 @@ where
Ok(())
}
enum SimpleFilter<'a, Impl: SelectorImpl> {
Class(&'a Atom),
LocalName(&'a LocalName<Impl>),
}
/// Fast paths for a given selector query.
///
/// When there's only one component, we go directly to
/// `query_selector_single_query`, otherwise, we try to optimize by looking just
/// at the subtrees rooted at ids in the selector, and otherwise we try to look
/// up by class name or local name in the rightmost compound.
///
/// FIXME(emilio, nbp): This may very well be a good candidate for code to be
/// replaced by HolyJit :)
fn query_selector_fast<E, Q>(
@ -410,7 +428,12 @@ where
let mut iter = selector.iter();
let mut combinator: Option<Combinator> = None;
loop {
// We want to optimize some cases where there's no id involved whatsoever,
// like `.foo .bar`, but we don't want to make `#foo .bar` slower because of
// that.
let mut simple_filter = None;
'selector_loop: loop {
debug_assert!(combinator.map_or(true, |c| !c.is_sibling()));
'component_loop: for component in &mut iter {
@ -469,13 +492,28 @@ where
return Ok(());
},
Component::Class(ref class) => {
if combinator.is_none() {
simple_filter = Some(SimpleFilter::Class(class));
}
},
Component::LocalName(ref local_name) => {
if combinator.is_none() {
// Prefer to look at class rather than local-name if
// both are present.
if let Some(SimpleFilter::Class(..)) = simple_filter {
continue;
}
simple_filter = Some(SimpleFilter::LocalName(local_name));
}
},
_ => {},
}
}
loop {
let next_combinator = match iter.next_sequence() {
None => return Err(()),
None => break 'selector_loop,
Some(c) => c,
};
@ -492,6 +530,39 @@ where
break;
}
}
// We got here without finding any ID or such that we could handle. Try to
// use one of the simple filters.
let simple_filter = match simple_filter {
Some(f) => f,
None => return Err(()),
};
match simple_filter {
SimpleFilter::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) &&
matching::matches_selector_list(
selector_list,
&element,
matching_context,
)
});
}
SimpleFilter::LocalName(ref local_name) => {
collect_all_elements::<E, Q, _>(root, results, |element| {
local_name_matches(element, local_name) &&
matching::matches_selector_list(
selector_list,
&element,
matching_context,
)
});
}
}
Ok(())
}
// Slow path for a given selector query.