mirror of
https://github.com/servo/servo.git
synced 2025-06-20 23:28:59 +01:00
style: Refactor querySelector to be generic over the query type, and implement a tree-walking variant of it.
Signed-off-by: Emilio Cobos Álvarez <emilio@crisal.io>
This commit is contained in:
parent
4f997bf333
commit
2274fd7ef3
2 changed files with 162 additions and 53 deletions
|
@ -61,37 +61,86 @@ where
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The result of a querySelector call.
|
/// A selector query abstraction, in order to be generic over QuerySelector and
|
||||||
pub type QuerySelectorResult<E> = SmallVec<[E; 128]>;
|
/// QuerySelectorAll.
|
||||||
|
pub trait SelectorQuery<E: TElement> {
|
||||||
|
/// The output of the query.
|
||||||
|
type Output;
|
||||||
|
|
||||||
/// The query kind we're doing (either only the first descendant that matches or
|
/// Whether the query should stop after the first element has been matched.
|
||||||
/// all of them).
|
fn should_stop_after_first_match() -> bool;
|
||||||
pub enum QuerySelectorKind {
|
|
||||||
/// <https://dom.spec.whatwg.org/#dom-parentnode-queryselectorall>
|
/// Append an element matching after the first query.
|
||||||
All,
|
fn append_element(output: &mut Self::Output, element: E);
|
||||||
/// <https://dom.spec.whatwg.org/#dom-parentnode-queryselector>
|
|
||||||
First,
|
/// Returns true if the output is empty.
|
||||||
|
fn is_empty(output: &Self::Output) -> bool;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct QuerySelectorProcessor<'a, E: TElement + 'a> {
|
/// The result of a querySelectorAll call.
|
||||||
kind: QuerySelectorKind,
|
pub type QuerySelectorAllResult<E> = SmallVec<[E; 128]>;
|
||||||
results: &'a mut QuerySelectorResult<E>,
|
|
||||||
|
/// A query for all the elements in a subtree.
|
||||||
|
pub struct QueryAll;
|
||||||
|
|
||||||
|
impl<E: TElement> SelectorQuery<E> for QueryAll {
|
||||||
|
type Output = QuerySelectorAllResult<E>;
|
||||||
|
|
||||||
|
fn should_stop_after_first_match() -> bool { false }
|
||||||
|
|
||||||
|
fn append_element(output: &mut Self::Output, element: E) {
|
||||||
|
output.push(element);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_empty(output: &Self::Output) -> bool {
|
||||||
|
output.is_empty()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A query for the first in-tree match of all the elements in a subtree.
|
||||||
|
pub struct QueryFirst;
|
||||||
|
|
||||||
|
impl<E: TElement> SelectorQuery<E> for QueryFirst {
|
||||||
|
type Output = Option<E>;
|
||||||
|
|
||||||
|
fn should_stop_after_first_match() -> bool { true }
|
||||||
|
|
||||||
|
fn append_element(output: &mut Self::Output, element: E) {
|
||||||
|
if output.is_none() {
|
||||||
|
*output = Some(element)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_empty(output: &Self::Output) -> bool {
|
||||||
|
output.is_none()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct QuerySelectorProcessor<'a, E, Q>
|
||||||
|
where
|
||||||
|
E: TElement + 'a,
|
||||||
|
Q: SelectorQuery<E>,
|
||||||
|
Q::Output: 'a,
|
||||||
|
{
|
||||||
|
results: &'a mut Q::Output,
|
||||||
matching_context: MatchingContext<'a, E::Impl>,
|
matching_context: MatchingContext<'a, E::Impl>,
|
||||||
selector_list: &'a SelectorList<E::Impl>,
|
selector_list: &'a SelectorList<E::Impl>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, E> InvalidationProcessor<'a, E> for QuerySelectorProcessor<'a, E>
|
impl<'a, E, Q> InvalidationProcessor<'a, E> for QuerySelectorProcessor<'a, E, Q>
|
||||||
where
|
where
|
||||||
E: TElement + 'a,
|
E: TElement + 'a,
|
||||||
|
Q: SelectorQuery<E>,
|
||||||
|
Q::Output: 'a,
|
||||||
{
|
{
|
||||||
fn collect_invalidations(
|
fn collect_invalidations(
|
||||||
&mut self,
|
&mut self,
|
||||||
_element: E,
|
element: E,
|
||||||
self_invalidations: &mut InvalidationVector<'a>,
|
self_invalidations: &mut InvalidationVector<'a>,
|
||||||
descendant_invalidations: &mut InvalidationVector<'a>,
|
descendant_invalidations: &mut InvalidationVector<'a>,
|
||||||
_sibling_invalidations: &mut InvalidationVector<'a>,
|
_sibling_invalidations: &mut InvalidationVector<'a>,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
// FIXME(emilio): If the element is not a root element, and
|
// TODO(emilio): If the element is not a root element, and
|
||||||
// selector_list has any descendant combinator, we need to do extra work
|
// selector_list has any descendant combinator, we need to do extra work
|
||||||
// in order to handle properly things like:
|
// in order to handle properly things like:
|
||||||
//
|
//
|
||||||
|
@ -103,6 +152,9 @@ where
|
||||||
//
|
//
|
||||||
// b.querySelector('#a div'); // Should return "c".
|
// b.querySelector('#a div'); // Should return "c".
|
||||||
//
|
//
|
||||||
|
// For now, assert it's a root element.
|
||||||
|
debug_assert!(element.parent_element().is_none());
|
||||||
|
|
||||||
let target_vector =
|
let target_vector =
|
||||||
if self.matching_context.scope_element.is_some() {
|
if self.matching_context.scope_element.is_some() {
|
||||||
descendant_invalidations
|
descendant_invalidations
|
||||||
|
@ -122,30 +174,92 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
fn should_process_descendants(&mut self, _: E) -> bool {
|
fn should_process_descendants(&mut self, _: E) -> bool {
|
||||||
match self.kind {
|
if Q::should_stop_after_first_match() {
|
||||||
QuerySelectorKind::All => true,
|
return Q::is_empty(&self.results)
|
||||||
QuerySelectorKind::First => self.results.is_empty(),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
fn invalidated_self(&mut self, e: E) {
|
fn invalidated_self(&mut self, e: E) {
|
||||||
self.results.push(e);
|
Q::append_element(self.results, e);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn recursion_limit_exceeded(&mut self, _e: E) {}
|
fn recursion_limit_exceeded(&mut self, _e: E) {}
|
||||||
fn invalidated_descendants(&mut self, _e: E, _child: E) {}
|
fn invalidated_descendants(&mut self, _e: E, _child: E) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <https://dom.spec.whatwg.org/#dom-parentnode-queryselector>
|
/// Fast paths for a given selector query.
|
||||||
pub fn query_selector<E: TElement>(
|
fn query_selector_fast<E, Q>(
|
||||||
|
_root: E::ConcreteNode,
|
||||||
|
_selector_list: &SelectorList<E::Impl>,
|
||||||
|
_results: &mut Q::Output,
|
||||||
|
_quirks_mode: QuirksMode,
|
||||||
|
) -> Result<(), ()>
|
||||||
|
where
|
||||||
|
E: TElement,
|
||||||
|
Q: SelectorQuery<E>,
|
||||||
|
{
|
||||||
|
// FIXME(emilio): Implement :-)
|
||||||
|
Err(())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Slow path for a given selector query.
|
||||||
|
fn query_selector_slow<E, Q>(
|
||||||
root: E::ConcreteNode,
|
root: E::ConcreteNode,
|
||||||
selector_list: &SelectorList<E::Impl>,
|
selector_list: &SelectorList<E::Impl>,
|
||||||
results: &mut QuerySelectorResult<E>,
|
results: &mut Q::Output,
|
||||||
kind: QuerySelectorKind,
|
matching_context: &mut MatchingContext<E::Impl>,
|
||||||
|
)
|
||||||
|
where
|
||||||
|
E: TElement,
|
||||||
|
Q: SelectorQuery<E>,
|
||||||
|
{
|
||||||
|
for node in root.dom_descendants() {
|
||||||
|
let element = match node.as_element() {
|
||||||
|
Some(e) => e,
|
||||||
|
None => continue,
|
||||||
|
};
|
||||||
|
|
||||||
|
if !matching::matches_selector_list(selector_list, &element, matching_context) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
Q::append_element(results, element);
|
||||||
|
if Q::should_stop_after_first_match() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <https://dom.spec.whatwg.org/#dom-parentnode-queryselector>
|
||||||
|
pub fn query_selector<E, Q>(
|
||||||
|
root: E::ConcreteNode,
|
||||||
|
selector_list: &SelectorList<E::Impl>,
|
||||||
|
results: &mut Q::Output,
|
||||||
quirks_mode: QuirksMode,
|
quirks_mode: QuirksMode,
|
||||||
) {
|
)
|
||||||
|
where
|
||||||
|
E: TElement,
|
||||||
|
Q: SelectorQuery<E>,
|
||||||
|
{
|
||||||
use invalidation::element::invalidator::TreeStyleInvalidator;
|
use invalidation::element::invalidator::TreeStyleInvalidator;
|
||||||
|
|
||||||
|
let fast_result = query_selector_fast::<E, Q>(
|
||||||
|
root,
|
||||||
|
selector_list,
|
||||||
|
results,
|
||||||
|
quirks_mode,
|
||||||
|
);
|
||||||
|
|
||||||
|
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.
|
||||||
let mut nth_index_cache = NthIndexCache::default();
|
let mut nth_index_cache = NthIndexCache::default();
|
||||||
let mut matching_context = MatchingContext::new(
|
let mut matching_context = MatchingContext::new(
|
||||||
MatchingMode::Normal,
|
MatchingMode::Normal,
|
||||||
|
@ -157,22 +271,20 @@ pub fn query_selector<E: TElement>(
|
||||||
let root_element = root.as_element();
|
let root_element = root.as_element();
|
||||||
matching_context.scope_element = root_element.map(|e| e.opaque());
|
matching_context.scope_element = root_element.map(|e| e.opaque());
|
||||||
|
|
||||||
let mut processor = QuerySelectorProcessor {
|
if root_element.is_some() {
|
||||||
kind,
|
query_selector_slow::<E, Q>(
|
||||||
|
root,
|
||||||
|
selector_list,
|
||||||
|
results,
|
||||||
|
&mut matching_context,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
let mut processor = QuerySelectorProcessor::<E, Q> {
|
||||||
results,
|
results,
|
||||||
matching_context,
|
matching_context,
|
||||||
selector_list,
|
selector_list,
|
||||||
};
|
};
|
||||||
|
|
||||||
match root_element {
|
|
||||||
Some(e) => {
|
|
||||||
TreeStyleInvalidator::new(
|
|
||||||
e,
|
|
||||||
/* stack_limit_checker = */ None,
|
|
||||||
&mut processor,
|
|
||||||
).invalidate();
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
for node in root.dom_children() {
|
for node in root.dom_children() {
|
||||||
if let Some(e) = node.as_element() {
|
if let Some(e) = node.as_element() {
|
||||||
TreeStyleInvalidator::new(
|
TreeStyleInvalidator::new(
|
||||||
|
@ -183,5 +295,4 @@ pub fn query_selector<E: TElement>(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1620,21 +1620,19 @@ pub unsafe extern "C" fn Servo_SelectorList_QueryFirst(
|
||||||
selectors: RawServoSelectorListBorrowed,
|
selectors: RawServoSelectorListBorrowed,
|
||||||
) -> *const structs::RawGeckoElement {
|
) -> *const structs::RawGeckoElement {
|
||||||
use std::borrow::Borrow;
|
use std::borrow::Borrow;
|
||||||
use style::dom_apis::{self, QuerySelectorResult, QuerySelectorKind};
|
use style::dom_apis::{self, QueryFirst};
|
||||||
|
|
||||||
let node = GeckoNode(node);
|
let node = GeckoNode(node);
|
||||||
let selectors = ::selectors::SelectorList::from_ffi(selectors).borrow();
|
let selectors = ::selectors::SelectorList::from_ffi(selectors).borrow();
|
||||||
let mut result = QuerySelectorResult::new();
|
let mut result = None;
|
||||||
dom_apis::query_selector::<GeckoElement>(
|
dom_apis::query_selector::<GeckoElement, QueryFirst>(
|
||||||
node,
|
node,
|
||||||
&selectors,
|
&selectors,
|
||||||
&mut result,
|
&mut result,
|
||||||
QuerySelectorKind::First,
|
|
||||||
node.owner_document_quirks_mode(),
|
node.owner_document_quirks_mode(),
|
||||||
);
|
);
|
||||||
|
|
||||||
result.first()
|
result.map_or(ptr::null(), |e| e.0)
|
||||||
.map_or(ptr::null(), |e| e.0)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue