accessibility_tree/style/
selectors.rs

1use crate::dom::{Document, Node, NodeId};
2use crate::style::errors::RuleParseErrorKind;
3use accessibility_scraper::selector::CssLocalName;
4use accessibility_scraper::selector::Simple;
5use fast_html5ever::{LocalName, Namespace};
6use selectors::attr::{AttrSelectorOperation, CaseSensitivity, NamespaceConstraint};
7use selectors::context::{MatchingContext, MatchingMode, QuirksMode};
8use selectors::matching::{matches_selector, ElementSelectorFlags};
9
10pub type SelectorList = selectors::SelectorList<Simple>;
11pub type Selector = selectors::parser::Selector<Simple>;
12
13pub fn matches(selector: &Selector, document: &Document, element: NodeId) -> bool {
14    matches_selector(
15        selector,
16        0,
17        None,
18        &NodeRef {
19            document,
20            node: element,
21        },
22        &mut MatchingContext::new(MatchingMode::Normal, None, None, QuirksMode::NoQuirks),
23        &mut |_, _| {},
24    )
25}
26
27/// css parser
28pub struct Parser;
29
30impl<'i> selectors::parser::Parser<'i> for Parser {
31    type Impl = Simple;
32    type Error = RuleParseErrorKind<'i>;
33}
34
35#[derive(Copy, Clone)]
36struct NodeRef<'a> {
37    document: &'a Document,
38    node: NodeId,
39}
40
41impl<'a> std::fmt::Debug for NodeRef<'a> {
42    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
43        self.node.fmt(f)
44    }
45}
46
47impl<'a> NodeRef<'a> {
48    fn node(self) -> &'a Node {
49        &self.document[self.node]
50    }
51    /// Returns the `Element` referenced by `self`.
52    pub fn value(&self) -> &crate::dom::ElementData {
53        self.node().as_element().unwrap()
54    }
55    /// Returns the value of an attribute.
56    pub fn attr(&self, attr: &str) -> Option<&str> {
57        self.value().get_attr(&LocalName::from(attr))
58    }
59    /// Returns if the element has the attibute and not empty
60    pub fn has_attribute(&self, attr: &str) -> bool {
61        match self.attr(attr) {
62            Some(val) => !val.trim().is_empty(),
63            None => false,
64        }
65    }
66}
67
68fn find_element<'a, F>(
69    document: &'a Document,
70    first: Option<NodeId>,
71    next: F,
72) -> Option<NodeRef<'a>>
73where
74    F: Fn(&Node) -> Option<NodeId>,
75{
76    let mut node = first?;
77    loop {
78        if document[node].as_element().is_some() {
79            return Some(NodeRef { document, node });
80        }
81        node = next(&document[node])?
82    }
83}
84
85impl<'a> selectors::Element for NodeRef<'a> {
86    type Impl = Simple;
87
88    #[inline]
89    fn is_part(&self, _name: &LocalName) -> bool {
90        false
91    }
92
93    fn opaque(&self) -> selectors::OpaqueElement {
94        selectors::OpaqueElement::new::<Node>(self.node())
95    }
96
97    fn parent_element(&self) -> Option<Self> {
98        let parent = self.node().parent?;
99        self.document[parent].as_element()?;
100        Some(NodeRef {
101            document: self.document,
102            node: parent,
103        })
104    }
105
106    fn next_sibling_element(&self) -> Option<Self> {
107        find_element(self.document, self.node().next_sibling, |node| {
108            node.next_sibling
109        })
110    }
111
112    fn prev_sibling_element(&self) -> Option<Self> {
113        find_element(self.document, self.node().previous_sibling, |node| {
114            node.previous_sibling
115        })
116    }
117
118    fn is_html_element_in_html_document(&self) -> bool {
119        self.node().as_element().unwrap().name.ns == ns!(html) && self.node().in_html_document()
120    }
121
122    #[inline]
123    fn imported_part(&self, _: &LocalName) -> Option<LocalName> {
124        None
125    }
126
127    #[inline]
128    fn exported_part(&self, _: &LocalName) -> Option<LocalName> {
129        None
130    }
131
132    #[inline]
133    fn is_same_type(&self, other: &Self) -> bool {
134        self.value().name == other.value().name
135    }
136
137    #[inline]
138    fn is_pseudo_element(&self) -> bool {
139        false
140    }
141
142    #[inline]
143    fn has_local_name(&self, name: &CssLocalName) -> bool {
144        self.value().name.local == *name.0
145    }
146
147    #[inline]
148    fn has_namespace(&self, namespace: &Namespace) -> bool {
149        &self.value().name.ns == *&namespace
150    }
151
152    fn is_html_slot_element(&self) -> bool {
153        false
154    }
155
156    fn parent_node_is_shadow_root(&self) -> bool {
157        false
158    }
159
160    fn containing_shadow_host(&self) -> Option<Self> {
161        None
162    }
163
164    fn attr_matches(
165        &self,
166        ns: &NamespaceConstraint<&Namespace>,
167        local_name: &CssLocalName,
168        operation: &AttrSelectorOperation<&accessibility_scraper::selector::CssString>,
169    ) -> bool {
170        self.node().as_element().unwrap().attrs.iter().any(|attr| {
171            attr.name.local == *local_name.0
172                && match *ns {
173                    NamespaceConstraint::Any => true,
174                    NamespaceConstraint::Specific(ns) => attr.name.ns == *ns,
175                }
176                && operation.eval_str(&attr.value)
177        })
178    }
179
180    fn match_non_ts_pseudo_class<F>(
181        &self,
182        pseudo_class: &accessibility_scraper::selector::NonTSPseudoClass,
183        _context: &mut MatchingContext<Self::Impl>,
184        _flags_setter: &mut F,
185    ) -> bool
186    where
187        F: FnMut(&Self, ElementSelectorFlags),
188    {
189        use accessibility_scraper::selector::NonTSPseudoClass::*;
190
191        match *pseudo_class {
192            Active | Focus | Hover | Enabled | Disabled | Checked | Indeterminate | Visited => {
193                false
194            }
195            AnyLink | Link => {
196                self.value().name.ns == ns!(html)
197                    && matches!(
198                        self.value().name.local,
199                        local_name!("a") | local_name!("area") | local_name!("link")
200                    )
201                    && self.has_attribute("href")
202            }
203        }
204    }
205
206    fn match_pseudo_element(
207        &self,
208        pseudo_element: &accessibility_scraper::selector::PseudoElement,
209        _context: &mut MatchingContext<Self::Impl>,
210    ) -> bool {
211        match *pseudo_element {}
212    }
213
214    fn is_link(&self) -> bool {
215        let element = self.node().as_element().unwrap();
216        element.name.ns == ns!(html)
217            && matches!(
218                element.name.local,
219                local_name!("a") | local_name!("area") | local_name!("link")
220            )
221            && element.get_attr(&local_name!("href")).is_some()
222    }
223
224    fn has_id(&self, id: &CssLocalName, case_sensitivity: CaseSensitivity) -> bool {
225        self.node()
226            .as_element()
227            .unwrap()
228            .get_attr(&local_name!("id"))
229            .map_or(false, |attr| {
230                case_sensitivity.eq(id.0.as_bytes(), attr.as_bytes())
231            })
232    }
233
234    fn has_class(&self, class: &CssLocalName, case_sensitivity: CaseSensitivity) -> bool {
235        self.node()
236            .as_element()
237            .unwrap()
238            .get_attr(&local_name!("class"))
239            .map_or(false, |attr| {
240                case_sensitivity.eq(class.0.as_bytes(), attr.as_bytes())
241            })
242    }
243
244    fn is_empty(&self) -> bool {
245        match self.node().first_child {
246            None => true,
247            Some(mut node) => loop {
248                if self.document[node].as_element().is_some() {
249                    return false;
250                }
251                if let Some(text) = self.document[node].as_text() {
252                    if !text.is_empty() {
253                        return false;
254                    }
255                }
256                match self.document[node].next_sibling {
257                    None => return true,
258                    Some(n) => node = n,
259                }
260            },
261        }
262    }
263
264    fn is_root(&self) -> bool {
265        self.parent_element().is_none()
266    }
267}