/* 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, 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) -> 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) -> bool,
{ for node in root.dom_descendants() { let element = match node.as_element() {
Some(e) => e,
None => continue,
};
if !filter(element) { continue;
}
Q::append_element(results, element); if Q::should_stop_after_first_match() { return;
}
}
}
/// 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| {
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 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| {
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.