Bug 1823642 - Optimize [id=foo] as #foo in querySelector/All. r=zrhoffman

This page seems to rely on this optimization being present. Optimizing
it is straight-forward. Why do they do that instead of using the #foo
syntax? Who knows.

Differential Revision: https://phabricator.services.mozilla.com/D173148
This commit is contained in:
Emilio Cobos Álvarez 2023-03-21 21:40:12 +00:00
parent 8af8caa1a5
commit c062dcce43
2 changed files with 93 additions and 63 deletions

View File

@ -7,13 +7,14 @@
use crate::context::QuirksMode; use crate::context::QuirksMode;
use crate::dom::{TDocument, TElement, TNode, TShadowRoot}; use crate::dom::{TDocument, TElement, TNode, TShadowRoot};
use crate::selector_parser::SelectorImpl;
use crate::invalidation::element::invalidation_map::Dependency; use crate::invalidation::element::invalidation_map::Dependency;
use crate::invalidation::element::invalidator::{DescendantInvalidationLists, Invalidation}; use crate::invalidation::element::invalidator::{DescendantInvalidationLists, Invalidation};
use crate::invalidation::element::invalidator::{InvalidationProcessor, InvalidationVector}; use crate::invalidation::element::invalidator::{InvalidationProcessor, InvalidationVector};
use crate::values::AtomIdent; use crate::values::AtomIdent;
use selectors::attr::CaseSensitivity; use selectors::attr::CaseSensitivity;
use selectors::matching::{self, MatchingContext, MatchingMode, NeedsSelectorFlags}; use selectors::matching::{self, MatchingContext, MatchingMode, NeedsSelectorFlags};
use selectors::parser::{Combinator, Component, LocalName, SelectorImpl}; use selectors::parser::{Combinator, Component, LocalName};
use selectors::{Element, SelectorList}; use selectors::{Element, SelectorList};
use smallvec::SmallVec; use smallvec::SmallVec;
@ -376,6 +377,23 @@ where
element.local_name() == &**chosen_name element.local_name() == &**chosen_name
} }
fn get_id(component: &Component<SelectorImpl>) -> Option<&AtomIdent> {
use selectors::attr::AttrSelectorOperator;
Some(match component {
Component::ID(ref id) => id,
Component::AttributeInNoNamespace { ref operator, ref local_name, ref value, .. } => {
if *local_name != local_name!("id") {
return None;
}
if *operator != AttrSelectorOperator::Equal {
return None;
}
AtomIdent::cast(&value.0)
},
_ => return None,
})
}
/// Fast paths for querySelector with a single simple selector. /// Fast paths for querySelector with a single simple selector.
fn query_selector_single_query<E, Q>( fn query_selector_single_query<E, Q>(
root: E::ConcreteNode, root: E::ConcreteNode,
@ -387,13 +405,11 @@ where
E: TElement, E: TElement,
Q: SelectorQuery<E>, Q: SelectorQuery<E>,
{ {
// TODO: Maybe we could implement a fast path for [name=""]?
match *component { match *component {
Component::ExplicitUniversalType => { Component::ExplicitUniversalType => {
collect_all_elements::<E, Q, _>(root, results, |_| true) collect_all_elements::<E, Q, _>(root, results, |_| true)
}, },
Component::ID(ref id) => {
collect_elements_with_id::<E, Q, _>(root, id, results, quirks_mode, |_| true);
},
Component::Class(ref class) => { Component::Class(ref class) => {
let case_sensitivity = quirks_mode.classes_and_ids_case_sensitivity(); let case_sensitivity = quirks_mode.classes_and_ids_case_sensitivity();
collect_all_elements::<E, Q, _>(root, results, |element| { collect_all_elements::<E, Q, _>(root, results, |element| {
@ -405,16 +421,22 @@ where
local_name_matches(element, local_name) local_name_matches(element, local_name)
}) })
}, },
ref other => {
let id = match get_id(other) {
Some(id) => id,
// TODO(emilio): More fast paths? // TODO(emilio): More fast paths?
_ => return Err(()), None => return Err(()),
};
collect_elements_with_id::<E, Q, _>(root, id, results, quirks_mode, |_| true);
},
} }
Ok(()) Ok(())
} }
enum SimpleFilter<'a, Impl: SelectorImpl> { enum SimpleFilter<'a> {
Class(&'a AtomIdent), Class(&'a AtomIdent),
LocalName(&'a LocalName<Impl>), LocalName(&'a LocalName<SelectorImpl>),
} }
/// Fast paths for a given selector query. /// Fast paths for a given selector query.
@ -468,14 +490,29 @@ where
'component_loop: for component in &mut iter { 'component_loop: for component in &mut iter {
match *component { match *component {
Component::ID(ref id) => { Component::Class(ref class) => {
if combinator.is_none() { if combinator.is_none() {
// In the rightmost compound, just find descendants of simple_filter = Some(SimpleFilter::Class(class));
// root that match the selector list with that id. }
},
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));
}
},
ref other => {
if let Some(id) = get_id(other) {
if combinator.is_none() {
// In the rightmost compound, just find descendants of root that match
// the selector list with that id.
collect_elements_with_id::<E, Q, _>(root, id, results, quirks_mode, |e| { collect_elements_with_id::<E, Q, _>(root, id, results, quirks_mode, |e| {
matching::matches_selector_list(selector_list, &e, matching_context) matching::matches_selector_list(selector_list, &e, matching_context)
}); });
return Ok(()); return Ok(());
} }
@ -521,23 +558,8 @@ where
} }
return Ok(()); 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));
}
},
_ => {},
} }
} }

View File

@ -367,6 +367,14 @@ impl AtomIdent {
callback(&*atom) callback(&*atom)
}) })
} }
/// Cast an atom ref to an AtomIdent ref.
#[inline]
pub fn cast<'a>(atom: &'a Atom) -> &'a Self {
let ptr = atom as *const _ as *const Self;
// safety: repr(transparent)
unsafe { &*ptr }
}
} }
#[cfg(feature = "gecko")] #[cfg(feature = "gecko")]