accessibility_tree/style/
cascade.rs

1use 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                    // FIXME: error reporting
42                }
43            }
44        }
45    }
46
47    pub fn finish(mut self) -> StyleSet {
48        // Sort stability preserves document order for rules of equal specificity
49        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        // https://drafts.csswg.org/css-cascade-4/#cascade-origin
79        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    // use smallvec::SmallVec;
122    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        // selectors::matching::NeedsSelectorFlags::No,
135        // selectors::matching::IgnoreNthChildForInvalidation::No,
136    );
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    // push author style sheet
152    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
179/// get the style for a node parsed with accessibility_scraper
180pub 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}