Skip to main content

blitz_dom/
query_selector.rs

1use blitz_traits::node_id::NodeId;
2use selectors::SelectorList;
3use smallvec::SmallVec;
4use style::dom::{TDocument, TNode};
5use style::dom_apis::{
6    MayUseInvalidation, QueryAll, QueryFirst, element_closest, element_matches, query_selector,
7};
8use style::selector_parser::{SelectorImpl, SelectorParser};
9use style_traits::ParseError;
10
11use crate::{BaseDocument, Node};
12
13impl BaseDocument {
14    /// Find the node with the specified id attribute (if one exists).
15    /// If multiple nodes have the same id, the first in tree order is returned.
16    pub fn get_element_by_id(&self, id: &str) -> Option<NodeId> {
17        match self.nodes_to_id.get(id)?.as_slice() {
18            [] => None,
19            [node_id] => Some(*node_id),
20            candidates => self.first_in_tree_order(candidates),
21        }
22    }
23
24    /// Find the first of `candidates` in tree order
25    fn first_in_tree_order(&self, candidates: &[NodeId]) -> Option<NodeId> {
26        let mut stack = vec![self.root_node_id];
27        while let Some(node_id) = stack.pop() {
28            if candidates.contains(&node_id) {
29                return Some(node_id);
30            }
31            stack.extend(self.nodes[node_id].children.iter().rev().copied());
32        }
33        None
34    }
35
36    /// Add a node to the id-to-node map
37    pub(crate) fn add_to_id_map(&mut self, id: &str, node_id: NodeId) {
38        if id.is_empty() {
39            return;
40        }
41        #[cfg(feature = "shadow-dom")]
42        if self.containing_shadow_root(node_id).is_some() {
43            return;
44        }
45        let node_ids = self.nodes_to_id.entry(id.to_string()).or_default();
46        if !node_ids.contains(&node_id) {
47            node_ids.push(node_id);
48        }
49    }
50
51    /// Remove a node from the id-to-node map
52    pub(crate) fn remove_from_id_map(&mut self, id: &str, node_id: NodeId) {
53        if let Some(node_ids) = self.nodes_to_id.get_mut(id) {
54            node_ids.retain(|nid| *nid != node_id);
55            if node_ids.is_empty() {
56                self.nodes_to_id.remove(id);
57            }
58        }
59    }
60
61    /// Find the first node that matches the selector specified as a string
62    /// Returns:
63    ///   - Err(_) if parsing the selector fails
64    ///   - Ok(None) if nothing matches
65    ///   - Ok(Some(node_id)) with the first node ID that matches if one is found
66    pub fn query_selector<'input>(
67        &self,
68        selector: &'input str,
69    ) -> Result<Option<NodeId>, ParseError<'input>> {
70        self.query_selector_in(self.root_node_id, selector)
71    }
72
73    /// Find the first descendant of `scope` that matches the selector specified
74    /// as a string.
75    ///
76    /// The scope node itself is never matched. Selector parts may match nodes
77    /// outside the scope while evaluating relationships between descendants.
78    ///
79    /// Returns:
80    ///   - `Err(_)` if parsing the selector fails
81    ///   - `Ok(None)` if nothing matches
82    ///   - `Ok(Some(node_id))` with the first matching descendant ID otherwise
83    pub fn query_selector_in<'input>(
84        &self,
85        scope: NodeId,
86        selector: &'input str,
87    ) -> Result<Option<NodeId>, ParseError<'input>> {
88        let selector_list = self.try_parse_selector_list(selector)?;
89        Ok(self.query_selector_in_raw(scope, &selector_list))
90    }
91
92    /// Find the first descendant of the document root that matches the
93    /// selector(s) specified in `selector_list`.
94    ///
95    /// The document root itself is never matched. Selector parts may match
96    /// nodes outside the scope while evaluating relationships between
97    /// descendants.
98    pub fn query_selector_raw(&self, selector_list: &SelectorList<SelectorImpl>) -> Option<NodeId> {
99        self.query_selector_in_raw(self.root_node_id, selector_list)
100    }
101
102    /// Find the first descendant of `scope` that matches the selector(s)
103    /// specified in `selector_list`.
104    ///
105    /// The scope node itself is never matched. Selector parts may match nodes
106    /// outside the scope while evaluating relationships between descendants.
107    pub fn query_selector_in_raw(
108        &self,
109        scope: NodeId,
110        selector_list: &SelectorList<SelectorImpl>,
111    ) -> Option<NodeId> {
112        #[cfg(feature = "shadow-dom")]
113        if self.has_shadow_roots() {
114            return query_dom_descendants(&self.nodes[scope], selector_list, true)
115                .first()
116                .copied();
117        }
118
119        let root_node = &self.nodes[scope];
120        let mut result = None;
121        query_selector::<&Node, QueryFirst>(
122            root_node,
123            selector_list,
124            &mut result,
125            self.may_use_invalidation_for(scope),
126        );
127
128        result.map(|node| node.id)
129    }
130
131    /// Find all nodes that match the selector specified as a string
132    /// Returns:
133    ///   - Err(_) if parsing the selector fails
134    ///   - Ok(SmallVec<usize>) with all matching nodes otherwise
135    pub fn query_selector_all<'input>(
136        &self,
137        selector: &'input str,
138    ) -> Result<SmallVec<[NodeId; 32]>, ParseError<'input>> {
139        self.query_selector_all_in(self.root_node_id, selector)
140    }
141
142    /// Find all descendants of `scope` that match the selector specified as a
143    /// string, in tree order.
144    ///
145    /// The scope node itself is never matched. Selector parts may match nodes
146    /// outside the scope while evaluating relationships between descendants.
147    ///
148    /// Returns:
149    ///   - `Err(_)` if parsing the selector fails
150    ///   - `Ok(_)` with all matching descendant IDs otherwise
151    pub fn query_selector_all_in<'input>(
152        &self,
153        scope: NodeId,
154        selector: &'input str,
155    ) -> Result<SmallVec<[NodeId; 32]>, ParseError<'input>> {
156        let selector_list = self.try_parse_selector_list(selector)?;
157        Ok(self.query_selector_all_in_raw(scope, &selector_list))
158    }
159
160    /// Find all descendants of the document root that match the selector(s)
161    /// specified in `selector_list`, in tree order.
162    ///
163    /// The document root itself is never matched. Selector parts may match
164    /// nodes outside the scope while evaluating relationships between
165    /// descendants.
166    pub fn query_selector_all_raw(
167        &self,
168        selector_list: &SelectorList<SelectorImpl>,
169    ) -> SmallVec<[NodeId; 32]> {
170        self.query_selector_all_in_raw(self.root_node_id, selector_list)
171    }
172
173    /// Find all descendants of `scope` that match the selector(s) specified in
174    /// `selector_list`, in tree order.
175    ///
176    /// The scope node itself is never matched. Selector parts may match nodes
177    /// outside the scope while evaluating relationships between descendants.
178    pub fn query_selector_all_in_raw(
179        &self,
180        scope: NodeId,
181        selector_list: &SelectorList<SelectorImpl>,
182    ) -> SmallVec<[NodeId; 32]> {
183        #[cfg(feature = "shadow-dom")]
184        if self.has_shadow_roots() {
185            return query_dom_descendants(&self.nodes[scope], selector_list, false);
186        }
187
188        let root_node = &self.nodes[scope];
189        let mut results = SmallVec::new();
190        query_selector::<&Node, QueryAll>(
191            root_node,
192            selector_list,
193            &mut results,
194            self.may_use_invalidation_for(scope),
195        );
196
197        results.iter().map(|node| node.id).collect()
198    }
199
200    fn may_use_invalidation_for(&self, scope: NodeId) -> MayUseInvalidation {
201        if scope == self.root_node_id {
202            MayUseInvalidation::Yes
203        } else {
204            MayUseInvalidation::No
205        }
206    }
207
208    /// Test whether the node identified by `node_id` matches the selector
209    /// specified as a string.
210    ///
211    /// Non-element nodes never match.
212    pub fn matches_selector<'input>(
213        &self,
214        node_id: NodeId,
215        selector: &'input str,
216    ) -> Result<bool, ParseError<'input>> {
217        let selector_list = self.try_parse_selector_list(selector)?;
218        Ok(self.nodes[node_id].matches_selector_raw(&selector_list))
219    }
220
221    /// Find the closest matching element at or above the node identified by
222    /// `node_id`.
223    ///
224    /// Non-element nodes never match and return `None`.
225    pub fn closest<'input>(
226        &self,
227        node_id: NodeId,
228        selector: &'input str,
229    ) -> Result<Option<NodeId>, ParseError<'input>> {
230        let selector_list = self.try_parse_selector_list(selector)?;
231        Ok(self.nodes[node_id].closest_raw(&selector_list))
232    }
233
234    pub fn try_parse_selector_list<'input>(
235        &self,
236        input: &'input str,
237    ) -> Result<SelectorList<SelectorImpl>, ParseError<'input>> {
238        let url_extra_data = self.url.url_extra_data();
239        SelectorParser::parse_author_origin_no_namespace(input, &url_extra_data)
240    }
241}
242
243/// Stylo's TNode links describe style traversal. DOM queries instead walk
244/// DOM children, including slot fallback and excluding attached shadow roots.
245/// Match with the receiver as :scope and keep Stylo's selector semantics.
246#[cfg(feature = "shadow-dom")]
247fn query_dom_descendants(
248    scope: &Node,
249    selector_list: &SelectorList<SelectorImpl>,
250    first: bool,
251) -> SmallVec<[NodeId; 32]> {
252    use selectors::context::{
253        MatchingContext, MatchingForInvalidation, MatchingMode, NeedsSelectorFlags, SelectorCaches,
254    };
255    use selectors::matching::matches_selector_list;
256
257    let mut caches = SelectorCaches::default();
258    let mut context = MatchingContext::new(
259        MatchingMode::Normal,
260        None,
261        &mut caches,
262        scope.owner_doc().quirks_mode(),
263        NeedsSelectorFlags::No,
264        MatchingForInvalidation::No,
265    );
266    context.scope_element = scope
267        .as_element()
268        .map(|element| selectors::Element::opaque(&element));
269    let mut results = SmallVec::new();
270    let mut stack: Vec<_> = scope.children.iter().rev().copied().collect();
271    while let Some(id) = stack.pop() {
272        let node = scope.with(id);
273        if node.is_element() && matches_selector_list(selector_list, &node, &mut context) {
274            results.push(id);
275            if first {
276                break;
277            }
278        }
279        stack.extend(node.children.iter().rev().copied());
280    }
281    results
282}
283
284impl Node {
285    /// Find the first descendant of this node that matches the selector(s)
286    /// specified in `selector_list`.
287    ///
288    /// The scope node itself is never matched. Selector parts may match nodes
289    /// outside the scope while evaluating relationships between descendants.
290    ///
291    /// Text and comment scope nodes return no matches.
292    pub fn query_selector_raw(&self, selector_list: &SelectorList<SelectorImpl>) -> Option<NodeId> {
293        #[cfg(feature = "shadow-dom")]
294        {
295            query_dom_descendants(self, selector_list, true)
296                .first()
297                .copied()
298        }
299        #[cfg(not(feature = "shadow-dom"))]
300        {
301            let mut result = None;
302            query_selector::<&Node, QueryFirst>(
303                self,
304                selector_list,
305                &mut result,
306                MayUseInvalidation::No,
307            );
308            result.map(|node| node.id)
309        }
310    }
311
312    /// Find all descendants of this node that match the selector(s) specified
313    /// in `selector_list`, in tree order.
314    ///
315    /// The scope node itself is never matched. Selector parts may match nodes
316    /// outside the scope while evaluating relationships between descendants.
317    ///
318    /// Text and comment scope nodes return no matches.
319    pub fn query_selector_all_raw(
320        &self,
321        selector_list: &SelectorList<SelectorImpl>,
322    ) -> SmallVec<[NodeId; 32]> {
323        #[cfg(feature = "shadow-dom")]
324        {
325            query_dom_descendants(self, selector_list, false)
326        }
327        #[cfg(not(feature = "shadow-dom"))]
328        {
329            let mut results = SmallVec::new();
330            query_selector::<&Node, QueryAll>(
331                self,
332                selector_list,
333                &mut results,
334                MayUseInvalidation::No,
335            );
336            results.iter().map(|node| node.id).collect()
337        }
338    }
339
340    /// Test whether this element matches the selector(s) specified in
341    /// `selector_list`.
342    ///
343    /// Non-element nodes never match.
344    pub fn matches_selector_raw(&self, selector_list: &SelectorList<SelectorImpl>) -> bool {
345        if !self.is_element() {
346            return false;
347        }
348
349        element_matches(&self, selector_list, self.owner_doc().quirks_mode())
350    }
351
352    /// Find the closest matching element at or above this element.
353    ///
354    /// Non-element nodes never match and return `None`.
355    pub fn closest_raw(&self, selector_list: &SelectorList<SelectorImpl>) -> Option<NodeId> {
356        if !self.is_element() {
357            return None;
358        }
359
360        element_closest(self, selector_list, self.owner_doc().quirks_mode()).map(|node| node.id)
361    }
362}