use crate::dom;
use crate::style::declaration_block::DeclarationBlock;
use crate::style::properties::{ComputedValues, Phase};
use crate::style::rules::{CssRule, RulesParser};
use accessibility_scraper::{ElementRef, Html};
use cssparser::{Parser, ParserInput, RuleListParser};
use smallvec::SmallVec;
use std::sync::Arc;
pub struct StyleSetBuilder(StyleSet);
#[derive(Clone, Debug, Default)]
pub struct StyleSet {
pub rules: Vec<(crate::style::selectors::Selector, Arc<DeclarationBlock>)>,
}
lazy_static::lazy_static! {
pub static ref USER_AGENT_STYLESHEET: StyleSet = {
let mut builder = StyleSetBuilder::new();
builder.add_stylesheet(include_str!("user_agent.css"));
builder.finish()
};
}
impl StyleSetBuilder {
pub fn new() -> Self {
StyleSetBuilder(StyleSet { rules: Vec::new() })
}
pub fn add_stylesheet(&mut self, css: &str) {
let mut input = ParserInput::new(css);
let mut parser = Parser::new(&mut input);
for result in RuleListParser::new_for_stylesheet(&mut parser, RulesParser) {
match result {
Ok(CssRule::StyleRule { selectors, block }) => {
for selector in selectors.0 {
self.0.rules.push((selector, block.clone()));
}
}
Err(_) => {
}
}
}
}
pub fn finish(mut self) -> StyleSet {
self.0
.rules
.sort_by_key(|&(ref selector, _)| selector.specificity());
self.0
}
}
impl StyleSet {
pub fn push_matching<'a>(
&'a self,
document: &dom::Document,
node: dom::NodeId,
into: &mut SmallVec<impl smallvec::Array<Item = &'a DeclarationBlock>>,
) {
for &(ref selector, ref block) in &self.rules {
if crate::style::selectors::matches(selector, document, node) {
into.push(block)
}
}
}
}
pub struct MatchingDeclarations<'a> {
pub ua: SmallVec<[&'a DeclarationBlock; 8]>,
pub author: SmallVec<[&'a DeclarationBlock; 32]>,
}
impl MatchingDeclarations<'_> {
pub fn cascade(&self, p: &mut impl Phase) {
self.ua.iter().for_each(|b| b.cascade_normal(p));
self.author.iter().for_each(|b| b.cascade_normal(p));
self.author.iter().for_each(|b| b.cascade_important(p));
self.ua.iter().for_each(|b| b.cascade_important(p));
}
}
pub fn style_for_element(
author: &StyleSet,
document: &dom::Document,
node: dom::NodeId,
parent_style: Option<&ComputedValues>,
) -> Arc<ComputedValues> {
match document[node].as_element() {
Some(element) => {
let style_attr_block;
let mut matching = MatchingDeclarations {
ua: SmallVec::new(),
author: SmallVec::new(),
};
USER_AGENT_STYLESHEET.push_matching(document, node, &mut matching.ua);
author.push_matching(document, node, &mut matching.author);
if let ns!(html) | ns!(svg) | ns!(mathml) = element.name.ns {
if let Some(style_attr) = element.get_attr(&local_name!("style")) {
let mut input = ParserInput::new(style_attr);
let mut parser = Parser::new(&mut input);
style_attr_block = DeclarationBlock::parse(&mut parser);
matching.author.push(&style_attr_block);
}
}
ComputedValues::new(parent_style, Some(&matching))
}
_ => ComputedValues::new(None, None),
}
}
pub fn _style_for_element<'a>(
author: &StyleSet,
_document: &accessibility_scraper::Html,
node: &ElementRef<'a>,
parent_style: Option<&ComputedValues>,
) -> Arc<ComputedValues> {
let style_attr_block;
let mut matching = crate::style::cascade::MatchingDeclarations {
ua: SmallVec::new(),
author: SmallVec::new(),
};
let mut nth_index_cache = selectors::NthIndexCache::from(Default::default());
let mut match_context = selectors::matching::MatchingContext::new(
selectors::matching::MatchingMode::Normal,
None,
Some(&mut nth_index_cache),
selectors::matching::QuirksMode::NoQuirks,
);
for &(ref selector, ref block) in &USER_AGENT_STYLESHEET.rules {
if selectors::matching::matches_selector(
selector,
0,
None,
node,
&mut match_context,
&mut |_, _| {},
) {
matching.ua.push(block)
}
}
for &(ref selector, ref block) in &author.rules {
if selectors::matching::matches_selector(
selector,
0,
None,
node,
&mut match_context,
&mut |_, _| {},
) {
matching.author.push(block)
}
}
if let ns!(html) | ns!(svg) | ns!(mathml) = node.value().name.ns {
if let Some(style_attr) = node.value().attr(&local_name!("style")) {
let mut input = ParserInput::new(style_attr);
let mut parser = Parser::new(&mut input);
style_attr_block = DeclarationBlock::parse(&mut parser);
matching.author.push(&style_attr_block);
}
}
let styles = ComputedValues::new(parent_style, Some(&matching));
styles
}
pub fn style_for_element_ref(
node: &ElementRef,
style_set: &StyleSet,
document: &Html,
) -> Arc<ComputedValues> {
let parent_styles = match node.parent() {
Some(n) => match accessibility_scraper::element_ref::ElementRef::wrap(n) {
Some(element) => {
let _parent_styles = _style_for_element(&style_set, &document, &element, None);
Some(_parent_styles)
}
_ => None,
},
_ => None,
};
_style_for_element(&style_set, &document, node, parent_styles.as_deref())
}