accessibility_tree/style/
cascade.rs1use crate::dom;
2use crate::style::declaration_block::DeclarationBlock;
3use crate::style::properties::{ComputedValues, Phase};
4use crate::style::rules::{CssRule, RulesParser};
5use accessibility_scraper::{ElementRef, Html};
6use cssparser::{Parser, ParserInput, RuleListParser};
7use smallvec::SmallVec;
8use std::sync::Arc;
9
10pub struct StyleSetBuilder(StyleSet);
11
12#[derive(Clone, Debug, Default)]
13pub struct StyleSet {
14 pub rules: Vec<(crate::style::selectors::Selector, Arc<DeclarationBlock>)>,
15}
16
17lazy_static::lazy_static! {
18 pub static ref USER_AGENT_STYLESHEET: StyleSet = {
19 let mut builder = StyleSetBuilder::new();
20 builder.add_stylesheet(include_str!("user_agent.css"));
21 builder.finish()
22 };
23}
24
25impl StyleSetBuilder {
26 pub fn new() -> Self {
27 StyleSetBuilder(StyleSet { rules: Vec::new() })
28 }
29
30 pub fn add_stylesheet(&mut self, css: &str) {
31 let mut input = ParserInput::new(css);
32 let mut parser = Parser::new(&mut input);
33 for result in RuleListParser::new_for_stylesheet(&mut parser, RulesParser) {
34 match result {
35 Ok(CssRule::StyleRule { selectors, block }) => {
36 for selector in selectors.0 {
37 self.0.rules.push((selector, block.clone()));
38 }
39 }
40 Err(_) => {
41 }
43 }
44 }
45 }
46
47 pub fn finish(mut self) -> StyleSet {
48 self.0
50 .rules
51 .sort_by_key(|&(ref selector, _)| selector.specificity());
52 self.0
53 }
54}
55
56impl StyleSet {
57 pub fn push_matching<'a>(
58 &'a self,
59 document: &dom::Document,
60 node: dom::NodeId,
61 into: &mut SmallVec<impl smallvec::Array<Item = &'a DeclarationBlock>>,
62 ) {
63 for &(ref selector, ref block) in &self.rules {
64 if crate::style::selectors::matches(selector, document, node) {
65 into.push(block)
66 }
67 }
68 }
69}
70
71pub struct MatchingDeclarations<'a> {
72 pub ua: SmallVec<[&'a DeclarationBlock; 8]>,
73 pub author: SmallVec<[&'a DeclarationBlock; 32]>,
74}
75
76impl MatchingDeclarations<'_> {
77 pub fn cascade(&self, p: &mut impl Phase) {
78 self.ua.iter().for_each(|b| b.cascade_normal(p));
80 self.author.iter().for_each(|b| b.cascade_normal(p));
81 self.author.iter().for_each(|b| b.cascade_important(p));
82 self.ua.iter().for_each(|b| b.cascade_important(p));
83 }
84}
85
86pub fn style_for_element(
87 author: &StyleSet,
88 document: &dom::Document,
89 node: dom::NodeId,
90 parent_style: Option<&ComputedValues>,
91) -> Arc<ComputedValues> {
92 match document[node].as_element() {
93 Some(element) => {
94 let style_attr_block;
95 let mut matching = MatchingDeclarations {
96 ua: SmallVec::new(),
97 author: SmallVec::new(),
98 };
99 USER_AGENT_STYLESHEET.push_matching(document, node, &mut matching.ua);
100 author.push_matching(document, node, &mut matching.author);
101 if let ns!(html) | ns!(svg) | ns!(mathml) = element.name.ns {
102 if let Some(style_attr) = element.get_attr(&local_name!("style")) {
103 let mut input = ParserInput::new(style_attr);
104 let mut parser = Parser::new(&mut input);
105 style_attr_block = DeclarationBlock::parse(&mut parser);
106 matching.author.push(&style_attr_block);
107 }
108 }
109 ComputedValues::new(parent_style, Some(&matching))
110 }
111 _ => ComputedValues::new(None, None),
112 }
113}
114
115pub fn _style_for_element<'a>(
116 author: &StyleSet,
117 _document: &accessibility_scraper::Html,
118 node: &ElementRef<'a>,
119 parent_style: Option<&ComputedValues>,
120) -> Arc<ComputedValues> {
121 let style_attr_block;
123 let mut matching = crate::style::cascade::MatchingDeclarations {
124 ua: SmallVec::new(),
125 author: SmallVec::new(),
126 };
127
128 let mut nth_index_cache = selectors::NthIndexCache::from(Default::default());
129 let mut match_context = selectors::matching::MatchingContext::new(
130 selectors::matching::MatchingMode::Normal,
131 None,
132 Some(&mut nth_index_cache),
133 selectors::matching::QuirksMode::NoQuirks,
134 );
137
138 for &(ref selector, ref block) in &USER_AGENT_STYLESHEET.rules {
139 if selectors::matching::matches_selector(
140 selector,
141 0,
142 None,
143 node,
144 &mut match_context,
145 &mut |_, _| {},
146 ) {
147 matching.ua.push(block)
148 }
149 }
150
151 for &(ref selector, ref block) in &author.rules {
153 if selectors::matching::matches_selector(
154 selector,
155 0,
156 None,
157 node,
158 &mut match_context,
159 &mut |_, _| {},
160 ) {
161 matching.author.push(block)
162 }
163 }
164
165 if let ns!(html) | ns!(svg) | ns!(mathml) = node.value().name.ns {
166 if let Some(style_attr) = node.value().attr(&local_name!("style")) {
167 let mut input = ParserInput::new(style_attr);
168 let mut parser = Parser::new(&mut input);
169 style_attr_block = DeclarationBlock::parse(&mut parser);
170 matching.author.push(&style_attr_block);
171 }
172 }
173
174 let styles = ComputedValues::new(parent_style, Some(&matching));
175
176 styles
177}
178
179pub fn style_for_element_ref(
181 node: &ElementRef,
182 style_set: &StyleSet,
183 document: &Html,
184) -> Arc<ComputedValues> {
185 let parent_styles = match node.parent() {
186 Some(n) => match accessibility_scraper::element_ref::ElementRef::wrap(n) {
187 Some(element) => {
188 let _parent_styles = _style_for_element(&style_set, &document, &element, None);
189 Some(_parent_styles)
190 }
191 _ => None,
192 },
193 _ => None,
194 };
195 _style_for_element(&style_set, &document, node, parent_styles.as_deref())
196}