mirror of
https://github.com/servo/servo.git
synced 2025-06-21 15:49:04 +01:00
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:
parent
a9af61e3be
commit
a3fcfb6435
1 changed files with 84 additions and 13 deletions
|
@ -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.
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue