/* This Source Code Form is subject to the terms of the Mozilla Public *License,v.2.0.IfacopyoftheMPLwasnotdistributedwiththis
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
//! Generic implementations of some DOM APIs so they can be shared between Servo //! and Gecko.
usecrate::context::QuirksMode; usecrate::dom::{TDocument, TElement, TNode, TShadowRoot}; usecrate::invalidation::element::invalidation_map::Dependency; usecrate::invalidation::element::invalidator::{
DescendantInvalidationLists, Invalidation, SiblingTraversalMap,
}; usecrate::invalidation::element::invalidator::{InvalidationProcessor, InvalidationVector}; usecrate::selector_parser::SelectorImpl; usecrate::values::AtomIdent; use selectors::attr::CaseSensitivity; use selectors::attr::{AttrSelectorOperation, NamespaceConstraint}; use selectors::matching::{ self, MatchingContext, MatchingForInvalidation, MatchingMode, NeedsSelectorFlags,
SelectorCaches,
}; use selectors::parser::{Combinator, Component, LocalName}; use selectors::{Element, OpaqueElement, SelectorList}; use smallvec::SmallVec;
letmut current = Some(element); whilelet Some(element) = current.take() { if matching::matches_selector_list(selector_list, &element, &mut context) { return Some(element);
}
current = element.parent_element();
}
return None;
}
/// A selector query abstraction, in order to be generic over QuerySelector and /// QuerySelectorAll. pubtrait SelectorQuery<E: TElement> { /// The output of the query. type Output;
/// Whether the query should stop after the first element has been matched. fn should_stop_after_first_match() -> bool;
/// Append an element matching after the first query. fn append_element(output: &mutSelf::Output, element: E);
/// Returns true if the output is empty. fn is_empty(output: &Self::Output) -> bool;
}
/// The result of a querySelectorAll call. pubtype QuerySelectorAllResult<E> = SmallVec<[E; 128]>;
/// A query for all the elements in a subtree. pubstruct QueryAll;
impl<E: TElement> SelectorQuery<E> for QueryAll { type Output = QuerySelectorAllResult<E>;
impl<'a, 'b, E, Q> InvalidationProcessor<'a, 'b, E> for QuerySelectorProcessor<'a, 'b, E, Q> where
E: TElement + 'a,
Q: SelectorQuery<E>,
Q::Output: 'a,
{ fn light_tree_only(&self) -> bool { true
}
fn check_outer_dependency(&mutself, _: &Dependency, _: E, _: Option<OpaqueElement>) -> bool {
debug_assert!( false, "How? We should only have parent-less dependencies here!"
); true
}
fn collect_invalidations(
&mutself,
element: E,
self_invalidations: &mut InvalidationVector<'a>,
descendant_invalidations: &mut DescendantInvalidationLists<'a>,
_sibling_invalidations: &mut InvalidationVector<'a>,
) -> bool { // TODO(emilio): If the element is not a root element, and // selector_list has any descendant combinator, we need to do extra work // in order to handle properly things like: // // <div id="a"> // <div id="b"> // <div id="c"></div> // </div> // </div> // // b.querySelector('#a div'); // Should return "c". // // For now, assert it's a root element.
debug_assert!(element.parent_element().is_none());
fn collect_all_elements<E, Q, F>(root: E::ConcreteNode, results: &mut Q::Output, mut filter: F) where
E: TElement,
Q: SelectorQuery<E>,
F: FnMut(E) -> Operation,
{ letmut iter = root.dom_descendants(); letmut cur = iter.next(); whilelet Some(node) = cur { let element = match node.as_element() {
Some(e) => e,
None => {
cur = iter.next(); continue;
},
}; match filter(element) { // Element matches - add to results and continue traversing its children.
Operation::Accept => {
Q::append_element(results, element); if Q::should_stop_after_first_match() { return;
}
}, // Element doesn't match - skip it but continue traversing its children.
Operation::Reject => {}, // Element doesn't match and skip entire subtree.
Operation::RejectSkippingChildren => {
cur = iter.next_skipping_children(); continue;
},
}
cur = iter.next();
}
}
/// Returns whether a given element connected to `root` is descendant of `root`. /// /// NOTE(emilio): if root == element, this returns false. fn connected_element_is_descendant_of<E>(element: E, root: E::ConcreteNode) -> bool where
E: TElement,
{ // Optimize for when the root is a document or a shadow root and the element // is connected to that root. if root.as_document().is_some() {
debug_assert!(element.as_node().is_in_document(), "Not connected?");
debug_assert_eq!(
root,
root.owner_doc().as_node(), "Where did this element come from?",
); returntrue;
}
letmut current = element.as_node().parent_node(); whilelet Some(n) = current.take() { if n == root { returntrue;
}
current = n.parent_node();
} false
}
/// Fast path for iterating over every element with a given id in the document /// or shadow root that `root` is connected to. fn fast_connected_elements_with_id<'a, N>(
root: N,
id: &AtomIdent,
case_sensitivity: CaseSensitivity,
) -> Result<&'a [N::ConcreteElement], ()> where
N: TNode + 'a,
{ if case_sensitivity != CaseSensitivity::CaseSensitive { return Err(());
}
if root.is_in_document() { return root.owner_doc().elements_with_id(id);
}
/// Collects elements with a given id under `root`, that pass `filter`. fn collect_elements_with_id<E, Q, F>(
root: E::ConcreteNode,
id: &AtomIdent,
results: &mut Q::Output,
class_and_id_case_sensitivity: CaseSensitivity, mut filter: F,
) where
E: TElement,
Q: SelectorQuery<E>,
F: FnMut(E) -> bool,
{ let elements = match fast_connected_elements_with_id(root, id, class_and_id_case_sensitivity) {
Ok(elements) => elements,
Err(()) => {
collect_all_elements::<E, Q, _>(root, results, |e| {
Operation::from(e.has_id(id, class_and_id_case_sensitivity) && filter(e))
});
return;
},
};
for element in elements { // If the element is not an actual descendant of the root, even though // it's connected, we don't really care about it. if !connected_element_is_descendant_of(*element, root) { continue;
}
if !filter(*element) { continue;
}
Q::append_element(results, *element); if Q::should_stop_after_first_match() { break;
}
}
}
fn has_attr<E>(element: E, local_name: &crate::LocalName) -> bool where
E: TElement,
{ letmut found = false;
element.each_attr_name(|name| found |= name == local_name);
found
}
#[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;
let chosen_name = if name == lower_name || element.is_html_element_in_html_document() {
lower_name
} else {
name
};
/// 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,
class_and_id_case_sensitivity: CaseSensitivity,
) -> Result<(), ()> where
E: TElement,
Q: SelectorQuery<E>,
{ match *component {
Component::ExplicitUniversalType => {
collect_all_elements::<E, Q, _>(root, results, |_| Operation::Accept)
},
Component::Class(ref class) => { // Bloom filter can only be used when case sensitive. let bloom_hash = if class_and_id_case_sensitivity == CaseSensitivity::CaseSensitive {
Some(E::hash_for_bloom_filter(class.0.get_hash()))
} else {
None
};
collect_all_elements::<E, Q, _>(root, results, |element| { if bloom_hash.is_some_and(|hash| !element.bloom_may_have_hash(hash)) { return Operation::RejectSkippingChildren;
}
Operation::from(element.has_class(class, class_and_id_case_sensitivity))
});
},
Component::LocalName(ref local_name) => {
collect_all_elements::<E, Q, _>(root, results, |element| {
Operation::from(local_name_matches(element, local_name))
})
},
Component::AttributeInNoNamespaceExists { ref local_name, ref local_name_lower,
} => { // For HTML elements: C++ hashes lowercase // For XUL/SVG/MathML elements: C++ hashes original case let hash_original = E::hash_for_bloom_filter(local_name.0.get_hash()); let hash_lower = if local_name.0 == local_name_lower.0 {
hash_original
} else {
E::hash_for_bloom_filter(local_name_lower.0.get_hash())
};
collect_all_elements::<E, Q, _>(root, results, |element| { // Check bloom filter first let bloom_found_hash = if hash_original == hash_lower
|| !element.as_node().owner_doc().is_html_document()
{
element.bloom_may_have_hash(hash_original)
} elseif element.is_html_element_in_html_document() { // HTML elements store lowercase hashes
element.bloom_may_have_hash(hash_lower)
} else { // Non-HTML elements in HTML documents might have HTML descendants // with lowercase-only hashes, so check both
element.bloom_may_have_hash(hash_original)
|| element.bloom_may_have_hash(hash_lower)
};
if !bloom_found_hash { return Operation::RejectSkippingChildren;
}
/// 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>(
root: E::ConcreteNode,
selector_list: &SelectorList<E::Impl>,
results: &mut Q::Output,
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.len() > 1 { return Err(());
}
let selector = &selector_list.slice()[0]; let class_and_id_case_sensitivity = matching_context.classes_and_ids_case_sensitivity(); // Let's just care about the easy cases for now. if selector.len() == 1 { if query_selector_single_query::<E, Q>(
root,
selector.iter().next().unwrap(),
results,
class_and_id_case_sensitivity,
)
.is_ok()
{ return Ok(());
}
}
letmut iter = selector.iter(); letmut combinator: Option<Combinator> = None;
// 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. letmut simple_filter = None;
'component_loop: for component in &mut iter { match *component {
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. iflet Some(SimpleFilter::Class(..)) = simple_filter { continue;
}
simple_filter = Some(SimpleFilter::LocalName(local_name));
}
}, ref other => { iflet 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,
class_and_id_case_sensitivity,
|e| {
matching::matches_selector_list(
selector_list,
&e,
matching_context,
)
},
); return Ok(());
}
let elements = fast_connected_elements_with_id(
root,
id,
class_and_id_case_sensitivity,
)?; if elements.is_empty() { return Ok(());
}
// Results need to be in document order. Let's not bother // reordering or deduplicating nodes, which we would need to // do if one element with the given id were a descendant of // another element with that given id. if !Q::should_stop_after_first_match() && elements.len() > 1 { continue;
}
for element in elements { // If the element is not a descendant of the root, then // it may have descendants that match our selector that // _are_ descendants of the root, and other descendants // that match our selector that are _not_. // // So we can't just walk over the element's descendants // and match the selector against all of them, nor can // we skip looking at this element's descendants. // // Give up on trying to optimize based on this id and // keep walking our selector. if !connected_element_is_descendant_of(*element, root) { continue'component_loop;
}
loop { let next_combinator = match iter.next_sequence() {
None => break'selector_loop,
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() { // Advance to the next combinator. for _ in &mut iter {} continue;
}
combinator = Some(next_combinator); 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(()),
};
// 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>,
) where
E: TElement,
Q: SelectorQuery<E>,
{
collect_all_elements::<E, Q, _>(root, results, |element| {
Operation::from(matching::matches_selector_list(
selector_list,
&element,
matching_context,
))
});
}
/// Whether the invalidation machinery should be used for this query. #[derive(PartialEq)] pubenum MayUseInvalidation { /// We may use it if we deem it useful.
Yes, /// Don't use it.
No,
}
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. // // A selector with a combinator needs to have a length of at least 3: A // simple selector, a combinator, and another simple selector. let invalidation_may_be_useful = may_use_invalidation == MayUseInvalidation::Yes
&& selector_list.slice().iter().any(|s| s.len() > 2);
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.