accessibility_tree/style/
selectors.rs1use 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
27pub 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 pub fn value(&self) -> &crate::dom::ElementData {
53 self.node().as_element().unwrap()
54 }
55 pub fn attr(&self, attr: &str) -> Option<&str> {
57 self.value().get_attr(&LocalName::from(attr))
58 }
59 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}