Skip to main content

rosin_core/css/
stylesheet.rs

1use std::{collections::HashMap, fmt::Display, fs, path::Path, str::FromStr, sync::Arc};
2
3use bumpalo::{Bump, collections::Vec as BumpVec};
4use cssparser::{Parser, ParserInput, RuleBodyParser};
5use log::error;
6use parking_lot::{RwLock, RwLockReadGuard};
7use qfilter::Filter;
8use smallvec::SmallVec;
9
10use crate::{
11    css::{self, parser::*, properties::*, style::*},
12    interner::StrId,
13    prelude::*,
14    tree::Node,
15    util::ResourceInfo,
16};
17
18#[derive(Debug, Clone, PartialEq, Eq)]
19pub(crate) enum Selector {
20    /// Represents a `.class` selector (interned)
21    Class(StrId),
22
23    /// Represents a `*` selector
24    Wildcard,
25
26    /// Represents a "space" (U+0020) combinator
27    Descendant,
28
29    /// Represents a `>` combinator
30    Child,
31
32    /// Represents a `:hover` pseudo-class
33    Hover,
34
35    /// Represents a `:focus` pseudo-class
36    Focus,
37
38    /// Represents a `:active` pseudo-class
39    Active,
40
41    /// Represents a `:disabled` pseudo-class
42    Disabled,
43
44    /// Represents a `:enabled` pseudo-class
45    Enabled,
46}
47
48impl Display for Selector {
49    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
50        match self {
51            Selector::Class(value) => write!(f, ".{}", value),
52            Selector::Wildcard => f.write_str("*"),
53            Selector::Descendant => f.write_str(" "),
54            Selector::Child => f.write_str(" > "),
55            Selector::Hover => f.write_str(":hover"),
56            Selector::Focus => f.write_str(":focus"),
57            Selector::Active => f.write_str(":active"),
58            Selector::Disabled => f.write_str(":disabled"),
59            Selector::Enabled => f.write_str(":enabled"),
60        }
61    }
62}
63
64#[derive(Debug, Clone)]
65pub(crate) struct Rule {
66    pub selectors: Vec<Selector>,
67    pub properties: Arc<SmallVec<[Property; 2]>>,
68    pub specificity: u32,
69    pub has_pseudos: bool,
70    pub variables: Vec<(Arc<str>, Arc<str>)>,
71}
72
73impl Eq for Rule {}
74
75impl PartialEq for Rule {
76    fn eq(&self, other: &Self) -> bool {
77        self.specificity == other.specificity
78            && self.selectors == other.selectors
79            && self.properties.as_ref() == other.properties.as_ref()
80            && self.variables == other.variables
81    }
82}
83
84impl Display for Rule {
85    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
86        for selector in &self.selectors {
87            write!(f, "{selector}")?;
88        }
89
90        f.write_str(" {")?;
91
92        for property in self.properties.iter() {
93            write!(f, "\t{property}")?;
94        }
95
96        f.write_str("}\n")
97    }
98}
99
100#[derive(Debug, Default, Clone)]
101pub(crate) struct StylesheetInner {
102    pub info: Option<ResourceInfo>,
103    pub rules: Vec<Rule>,
104
105    /// Inverted index for `*`
106    pub wildcard: Vec<usize>,
107
108    /// Inverted index for `.class`
109    pub index: HashMap<StrId, Vec<usize>>,
110}
111
112/// A parsed CSS stylesheet.
113#[derive(Default, Clone)]
114pub struct Stylesheet {
115    pub(crate) inner: Arc<RwLock<StylesheetInner>>,
116}
117
118impl Eq for Stylesheet {}
119impl PartialEq for Stylesheet {
120    fn eq(&self, other: &Self) -> bool {
121        let self_inner = self.inner.read();
122        let other_inner = other.inner.read();
123
124        self_inner.rules == other_inner.rules
125    }
126}
127
128impl std::fmt::Debug for Stylesheet {
129    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
130        f.debug_struct("Stylesheet").finish_non_exhaustive()
131    }
132}
133
134impl Display for Stylesheet {
135    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
136        let inner = self.inner.read();
137
138        for rule in &inner.rules {
139            writeln!(f, "{rule}")?;
140        }
141
142        Ok(())
143    }
144}
145
146impl FromStr for Stylesheet {
147    type Err = ();
148
149    fn from_str(css: &str) -> Result<Self, Self::Err> {
150        let mut rules = Vec::new();
151        let mut wildcard = Vec::new();
152        let mut index = HashMap::new();
153        Self::parse(css, None, &mut rules, &mut wildcard, &mut index);
154
155        Ok(Self {
156            inner: Arc::new(RwLock::new(StylesheetInner {
157                info: None,
158                rules,
159                wildcard,
160                index,
161            })),
162        })
163    }
164}
165
166impl Stylesheet {
167    /// Parses a CSS file. In debug builds, the file will be reloaded when changed on disk.
168    pub fn from_file(path: impl AsRef<Path>) -> Result<Stylesheet, std::io::Error> {
169        let path_buf = path.as_ref().canonicalize()?;
170        let css = fs::read_to_string(&path_buf)?;
171        let mut rules = Vec::new();
172        let mut wildcard = Vec::new();
173        let mut index = HashMap::new();
174        Self::parse(&css, Some(&path_buf), &mut rules, &mut wildcard, &mut index);
175
176        let info = Some(ResourceInfo {
177            last_modified: fs::metadata(&path_buf)?.modified()?,
178            path: path_buf.clone(),
179        });
180
181        Ok(Self {
182            inner: Arc::new(RwLock::new(StylesheetInner { info, rules, wildcard, index })),
183        })
184    }
185
186    pub(crate) fn reload(&mut self) -> Result<bool, std::io::Error> {
187        let mut write_guard = self.inner.write();
188        let inner = &mut *write_guard;
189
190        if let Some(ref mut resource_info) = inner.info {
191            let current_modified_time = fs::metadata(&resource_info.path)?.modified()?;
192
193            if current_modified_time > resource_info.last_modified {
194                // File has been modified, reload it
195                let new_css = fs::read_to_string(&resource_info.path)?;
196                Self::parse(&new_css, Some(&resource_info.path), &mut inner.rules, &mut inner.wildcard, &mut inner.index);
197
198                resource_info.last_modified = current_modified_time;
199
200                return Ok(true);
201            }
202        }
203
204        Ok(false)
205    }
206
207    fn parse(css: &str, file_name: Option<&Path>, rules: &mut Vec<Rule>, wildcard: &mut Vec<usize>, index: &mut HashMap<StrId, Vec<usize>>) {
208        rules.clear();
209        wildcard.clear();
210        index.clear();
211
212        let mut input = ParserInput::new(css);
213        let mut parser = Parser::new(&mut input);
214        let mut rp = RulesParser { file_name };
215
216        for result in RuleBodyParser::new(&mut parser, &mut rp) {
217            match result {
218                Ok(parsed_rules) => {
219                    for rule in parsed_rules {
220                        rules.push(rule);
221                    }
222                }
223                Err((error, css)) => {
224                    let msg = format_args!("Failed to parse CSS rule: `{}`", css.lines().next().unwrap_or(""));
225                    css::log_error(msg, error.location, file_name);
226                }
227            }
228        }
229
230        // Need to use a stable sort because order in the stylesheet is also important
231        rules.sort_by_key(|r| r.specificity);
232
233        // Build indexes
234        for (idx, rule) in rules.iter().enumerate() {
235            let mut indexed = false;
236
237            for selector in rule.selectors.iter().rev() {
238                match selector {
239                    // Skip pseudos when choosing an index key
240                    Selector::Hover | Selector::Focus | Selector::Active | Selector::Disabled | Selector::Enabled => {
241                        continue;
242                    }
243
244                    // If we reach a combinator before finding a concrete key,
245                    // the rightmost simple selector is effectively "any element"
246                    // (`.a > :hover`), so treat it as a wildcard.
247                    Selector::Wildcard | Selector::Child | Selector::Descendant => {
248                        wildcard.push(idx);
249                        indexed = true;
250                        break;
251                    }
252                    Selector::Class(class_id) => {
253                        index.entry(*class_id).or_default().push(idx);
254                        indexed = true;
255                        break;
256                    }
257                }
258            }
259
260            // If the selector list had no concrete key, index it as wildcard.
261            if !indexed {
262                wildcard.push(idx);
263            }
264        }
265    }
266}
267
268#[derive(Copy, Clone)]
269enum PseudoKind {
270    Hover,
271    Focus,
272    Active,
273    Enabled, // covers :enabled and :disabled
274}
275
276#[derive(Debug)]
277pub(crate) struct VariableContext {
278    /// Maps a variable name to a stack of values. The last value is the current one.
279    css_vars: HashMap<Arc<str>, Vec<Arc<str>>>,
280    /// Tracks which variables were added at which node index to allow efficient popping.
281    scope_history: Vec<(usize, Vec<Arc<str>>)>,
282}
283
284impl VariableContext {
285    fn new() -> Self {
286        Self {
287            css_vars: HashMap::with_capacity(64),
288            scope_history: Vec::with_capacity(32),
289        }
290    }
291
292    /// Empties the context.
293    fn clear(&mut self) {
294        self.css_vars.clear();
295        self.scope_history.clear();
296    }
297
298    /// Get the current value of a variable.
299    pub fn get(&self, name: &str) -> Option<&str> {
300        self.css_vars.get(name).and_then(|stack| stack.last()).map(|v| &**v)
301    }
302
303    /// Add a set of variables to the node's scope.
304    fn push_vars(&mut self, node_idx: usize, vars: &[(Arc<str>, Arc<str>)]) {
305        if vars.is_empty() {
306            return;
307        }
308
309        let mut keys_added = Vec::with_capacity(vars.len());
310
311        for (name, value) in vars {
312            self.css_vars.entry(Arc::clone(name)).or_default().push(value.clone());
313            keys_added.push(name.clone());
314        }
315
316        self.scope_history.push((node_idx, keys_added));
317    }
318
319    /// Remove the most recent set of variables added to the context.
320    fn pop_vars(&mut self) {
321        if let Some((_, keys)) = self.scope_history.pop() {
322            for key in keys {
323                if let Some(stack) = self.css_vars.get_mut(&key) {
324                    stack.pop();
325                    if stack.is_empty() {
326                        self.css_vars.remove(&key);
327                    }
328                }
329            }
330        }
331    }
332
333    /// Remove scopes that are no longer ancestors of the current node.
334    fn prune(&mut self, current_parent_idx: usize) {
335        while let Some((scope_node_idx, _)) = self.scope_history.last() {
336            if *scope_node_idx > current_parent_idx {
337                self.pop_vars();
338            } else {
339                break;
340            }
341        }
342    }
343}
344
345/// Builds per-node CSS variable cache from static rules and computes `style_flags` for each node.
346pub(crate) fn style_pre_pass<S, H>(temp: &Bump, tree: &mut Ui<S, H>, ancestor_classes: &mut Filter) {
347    debug_assert_eq!(tree.var_scope_cache.len(), 0, "Expected empty var_scope_cache before pre-pass.");
348    debug_assert_eq!(tree.style_flags.len(), 0, "Expected empty style_flags before pre-pass.");
349
350    ancestor_classes.clear();
351
352    let mut active_sheets: BumpVec<(usize, Stylesheet)> = BumpVec::new_in(temp);
353    let mut rules_list: BumpVec<usize> = BumpVec::new_in(temp);
354    let mut var_delta: HashMap<Arc<str>, Arc<str>> = HashMap::with_capacity(16);
355    let mut pseudos: Vec<(PseudoKind, usize)> = Vec::with_capacity(32);
356
357    for idx in 0..tree.nodes.len() {
358        tree.style_flags.push(0);
359
360        update_sheets(&tree.nodes, idx, &mut active_sheets);
361        update_ancestors(&tree.nodes, idx, ancestor_classes);
362
363        var_delta.clear();
364
365        // Walk active stylesheets from nearest to farthest ancestor
366        for (_, (_, sheet)) in active_sheets.iter().enumerate().rev() {
367            rules_list.clear();
368
369            let inner = sheet.inner.read();
370
371            rules_list.extend_from_slice(&inner.wildcard);
372            for &class_id in tree.nodes[idx].classes.iter() {
373                if let Some(indexes) = inner.index.get(&class_id) {
374                    rules_list.extend_from_slice(indexes);
375                }
376            }
377
378            rules_list.sort_unstable();
379            rules_list.dedup();
380
381            for &rule_idx in rules_list.iter() {
382                let rule = &inner.rules[rule_idx];
383
384                if !rule.has_pseudos {
385                    // Static rule, build var delta
386                    if rule_matches_node(rule, &tree.nodes, idx, None, None, &[], ancestor_classes, None) {
387                        for (name, value) in &rule.variables {
388                            // later overwrites earlier
389                            var_delta.insert(Arc::clone(name), Arc::clone(value));
390                        }
391                    }
392                } else {
393                    // Dynamic rule, compute invalidation flags
394                    let start = pseudos.len();
395
396                    let matched = rule_matches_node(rule, &tree.nodes, idx, None, None, &[], ancestor_classes, Some(&mut pseudos));
397                    if !matched {
398                        pseudos.truncate(start);
399                        continue;
400                    }
401
402                    // Apply flags for pseudos added by this rule, then clear them.
403                    for &(kind, pseudo_node_idx) in &pseudos[start..] {
404                        let bit = match kind {
405                            PseudoKind::Hover => css::HOVER_DIRTY,
406                            PseudoKind::Focus => css::FOCUS_DIRTY,
407                            PseudoKind::Active => css::ACTIVE_DIRTY,
408                            PseudoKind::Enabled => css::ENABLED_DIRTY,
409                        };
410
411                        tree.style_flags[pseudo_node_idx] |= bit;
412                    }
413
414                    pseudos.truncate(start);
415                }
416            }
417        }
418
419        tree.var_scope_cache
420            .push(var_delta.iter().map(|(k, v)| (Arc::clone(k), Arc::clone(v))).collect());
421    }
422}
423
424/// Perform selector matching and apply CSS classes to a tree.
425///
426/// Returns true if one of the rules could have affected layout.
427pub(crate) fn style_pass<S, H>(
428    temp: &Bump,
429    tree: &mut Ui<S, H>,
430    state: &S,
431    focused_node: Option<NodeId>,
432    active_node: Option<NodeId>,
433    hot_nodes: &[usize],
434    ancestor_classes: &mut Filter,
435) -> bool {
436    tree.merge_dirty_roots();
437    if tree.nodes.is_empty() || tree.dirty_roots.is_empty() {
438        return false;
439    }
440
441    // Split tree into disjoint borrows
442    let Ui {
443        nodes,
444        style_cache,
445        dirty_roots,
446        var_scope_cache,
447        on_style_deps,
448        ..
449    } = tree;
450
451    let len = nodes.len();
452
453    let mut active_sheets: BumpVec<(usize, Stylesheet)> = BumpVec::new_in(temp);
454    let mut candidate_rules: BumpVec<usize> = BumpVec::new_in(temp);
455    let mut matched_rules: BumpVec<(usize, usize)> = BumpVec::new_in(temp);
456    let mut var_ctx = VariableContext::new();
457    let mut path: BumpVec<usize> = BumpVec::new_in(temp);
458    let mut scratch = ApplyScratch::default();
459    let mut affects_layout = false;
460
461    let mut dirty_i: usize = 0;
462    while dirty_i < dirty_roots.len() {
463        let root_idx = dirty_roots[dirty_i];
464        let mut idx: usize = root_idx;
465        let mut region_end = idx + nodes[idx].subtree_size + 1;
466
467        active_sheets.clear();
468        ancestor_classes.clear();
469        var_ctx.clear();
470        candidate_rules.clear();
471
472        // ---------- Rebuild Ancestor State ----------
473        path.clear();
474        let mut curr = idx;
475        while curr != usize::MAX {
476            // Build idx -> root
477            path.push(curr);
478            curr = nodes[curr].parent;
479        }
480
481        // Apply root -> idx
482        for &node_idx in path.iter().rev() {
483            if let Some(sheet) = &nodes[node_idx].style_sheet {
484                active_sheets.push((node_idx, sheet.clone()));
485            }
486
487            if node_idx < var_scope_cache.len() {
488                var_ctx.push_vars(node_idx, &var_scope_cache[node_idx]);
489            }
490
491            // Collect ancestor pseudo-variables
492            for (_, (_, sheet)) in active_sheets.iter().enumerate().rev() {
493                candidate_rules.clear();
494
495                let inner = sheet.inner.read();
496
497                candidate_rules.extend_from_slice(&inner.wildcard);
498                for &class_id in nodes[node_idx].classes.iter() {
499                    if let Some(indexes) = inner.index.get(&class_id) {
500                        candidate_rules.extend_from_slice(indexes);
501                    }
502                }
503
504                candidate_rules.sort_unstable();
505                candidate_rules.dedup();
506
507                for &rule_idx in candidate_rules.iter() {
508                    let rule = &inner.rules[rule_idx];
509
510                    if !rule.has_pseudos || rule.variables.is_empty() {
511                        continue;
512                    }
513
514                    if rule_matches_node(rule, nodes, node_idx, focused_node, active_node, hot_nodes, ancestor_classes, None) {
515                        var_ctx.push_vars(node_idx, &rule.variables);
516                    }
517                }
518            }
519
520            // Now make this node an ancestor for subsequent nodes in the path.
521            if node_idx != idx {
522                for &class_id in nodes[node_idx].classes.iter() {
523                    if let Err(error) = ancestor_classes.insert_duplicated(class_id) {
524                        error!("Selector matching: {error}");
525                    }
526                }
527            }
528        }
529
530        // ---------- Style Dirty Region ----------
531        let mut first_in_region = true;
532        loop {
533            // Consume any dirty roots inside the current region,
534            // expanding region_end for subtree dirties and on_callback
535            while dirty_i < dirty_roots.len() && dirty_roots[dirty_i] < region_end {
536                let i = dirty_roots[dirty_i];
537                dirty_i += 1;
538
539                let subtree_end = i + nodes[i].subtree_size + 1;
540                region_end = region_end.max(subtree_end);
541            }
542            if idx >= region_end || idx >= len {
543                break;
544            }
545
546            if !first_in_region {
547                // Maintain state incrementally
548                update_sheets(nodes, idx, &mut active_sheets);
549                update_ancestors(nodes, idx, ancestor_classes);
550
551                let parent_idx = nodes[idx].parent;
552                var_ctx.prune(parent_idx);
553                if idx < var_scope_cache.len() {
554                    var_ctx.push_vars(idx, &var_scope_cache[idx]);
555                }
556            }
557
558            let subtree_end = idx + nodes[idx].subtree_size + 1;
559
560            let parent_style_snapshot = (idx != 0).then(|| style_cache[nodes[idx].parent].clone());
561            let parent_style_opt = parent_style_snapshot.as_ref();
562
563            let mut new_style = if let Some(parent_style) = parent_style_opt {
564                Style {
565                    color: parent_style.color,
566                    font_width: parent_style.font_width,
567                    font_size: parent_style.font_size,
568                    font_style: parent_style.font_style,
569                    font_family: parent_style.font_family.clone(),
570                    font_weight: parent_style.font_weight,
571                    text_shadow: parent_style.text_shadow.clone(),
572                    letter_spacing: parent_style.letter_spacing,
573                    word_spacing: parent_style.word_spacing,
574                    line_height: parent_style.line_height,
575                    ..Default::default()
576                }
577            } else {
578                Style::default()
579            };
580
581            // ---------- Collect Rules ----------
582            matched_rules.clear();
583
584            // Cache read guards to avoid repeatedly lock/unlocking the same stylesheet inner
585            let sheet_inners: SmallVec<[RwLockReadGuard<'_, StylesheetInner>; 8]> = active_sheets.iter().map(|(_, sheet)| sheet.inner.read()).collect();
586
587            for (sheet_stack_idx, _) in active_sheets.iter().enumerate().rev() {
588                candidate_rules.clear();
589
590                let inner = &sheet_inners[sheet_stack_idx];
591
592                candidate_rules.extend_from_slice(&inner.wildcard);
593                for &class_id in nodes[idx].classes.iter() {
594                    if let Some(indexes) = inner.index.get(&class_id) {
595                        candidate_rules.extend_from_slice(indexes);
596                    }
597                }
598
599                candidate_rules.sort_unstable();
600                candidate_rules.dedup();
601
602                for &rule_idx in candidate_rules.iter() {
603                    let rule = &inner.rules[rule_idx];
604
605                    if rule_matches_node(rule, nodes, idx, focused_node, active_node, hot_nodes, ancestor_classes, None) {
606                        matched_rules.push((sheet_stack_idx, rule_idx));
607
608                        if rule.has_pseudos && !rule.variables.is_empty() {
609                            var_ctx.push_vars(idx, &rule.variables);
610                        }
611                    }
612                }
613            }
614
615            // ---------- Apply properties ----------
616            for phase in 0..2 {
617                for &(sheet_stack_idx, rule_idx) in matched_rules.iter() {
618                    let inner = &sheet_inners[sheet_stack_idx];
619                    let rule = &inner.rules[rule_idx];
620
621                    for property in rule.properties.iter() {
622                        // apply color first so currentColor resolves against final computed color.
623                        let is_color = matches!(property, Property::Color(_));
624                        match phase {
625                            0 if !is_color => continue,
626                            1 if is_color => continue,
627                            _ => {}
628                        }
629                        if phase == 1 {
630                            affects_layout |= property.affects_layout();
631                        }
632                        if let Err(e) = property.apply(&mut scratch, &mut new_style, parent_style_opt, &var_ctx) {
633                            css::log_error(&e, e.location, inner.info.as_ref().map(|i| i.path.as_path()));
634                        }
635                    }
636                }
637            }
638
639            // ---------- Call on_style Callback ----------
640            let mut callback_ran = false;
641            if let Some(callback) = nodes[idx].style_callback.as_deref() {
642                let prev_layout_style = new_style.get_layout_style();
643
644                let base = on_style_deps.remove(&idx).unwrap_or_default();
645                let deps = base.cleared().read_scope(|| {
646                    callback(state, &mut new_style);
647                });
648                on_style_deps.insert(idx, deps);
649
650                affects_layout |= new_style.get_layout_style() != prev_layout_style;
651                callback_ran = true;
652            }
653
654            // Commit
655            style_cache[idx] = new_style;
656
657            if callback_ran {
658                region_end = region_end.max(subtree_end);
659            }
660
661            idx += 1;
662            first_in_region = false;
663        }
664    }
665
666    dirty_roots.clear();
667    affects_layout
668}
669
670fn update_sheets<S, H>(nodes: &[Node<S, H>], idx: usize, active_sheets: &mut BumpVec<(usize, Stylesheet)>) {
671    // If this stylesheet came from a node after this node's parent, it can't apply.
672    while active_sheets.pop_if(|(style_idx, _)| *style_idx > nodes[idx].parent).is_some() {}
673
674    if let Some(stylesheet) = &nodes[idx].style_sheet {
675        active_sheets.push((idx, stylesheet.clone()));
676    }
677}
678
679fn update_ancestors<S, H>(nodes: &[Node<S, H>], idx: usize, ancestor_classes: &mut Filter) {
680    if idx != 0 {
681        let parent = nodes[idx].parent;
682        let prev_parent = nodes[idx - 1].parent;
683
684        // Sibling node, ancestors haven't changed
685        if parent == prev_parent {
686            return;
687        }
688
689        // Moved down tree
690        if parent > prev_parent || prev_parent == usize::MAX {
691            // Add ancestors
692            for &class_id in nodes[parent].classes.iter() {
693                if let Err(error) = ancestor_classes.insert_duplicated(class_id) {
694                    error!("Selector matching: {error}");
695                }
696            }
697
698        // Moved up tree
699        } else {
700            // Remove inapplicable ancestors
701            let mut curr = prev_parent;
702            while curr != parent {
703                for &class_id in nodes[curr].classes.iter() {
704                    ancestor_classes.remove(class_id);
705                }
706                curr = nodes[curr].parent;
707            }
708        }
709    }
710}
711
712/// Checks if a rule matches a node in the tree.
713/// When pseudo_out is None, it checks the actual pseudo state.
714/// When pseudo out is Some, it fills the vec with the pseudo classes that would match this node.
715#[allow(clippy::too_many_arguments)]
716fn rule_matches_node<S, H>(
717    rule: &Rule,
718    nodes: &[Node<S, H>],
719    idx: usize,
720    focused_node: Option<NodeId>,
721    active_node: Option<NodeId>,
722    hot_nodes: &[usize],
723    ancestor_classes: &Filter,
724    mut pseudo_out: Option<&mut Vec<(PseudoKind, usize)>>,
725) -> bool {
726    let mut cmp_node = idx;
727    let mut is_first = true;
728    let mut prev_class = false;
729    let mut prev_child = false;
730
731    'selector: for selector in rule.selectors.iter().rev() {
732        'node: while cmp_node != usize::MAX {
733            match selector {
734                Selector::Class(rule_class_id) => {
735                    if nodes[cmp_node].classes.contains(rule_class_id) {
736                        is_first = false;
737                        prev_class = true;
738                        prev_child = false;
739                        continue 'selector;
740                    } else if is_first || prev_class || prev_child {
741                        return false;
742                    }
743
744                    if !ancestor_classes.contains(rule_class_id) {
745                        return false;
746                    }
747
748                    is_first = false;
749                    prev_class = true;
750                    prev_child = false;
751
752                    cmp_node = nodes[cmp_node].parent;
753                    continue 'node;
754                }
755
756                Selector::Wildcard => {
757                    is_first = false;
758                    prev_class = false;
759                    prev_child = false;
760                    continue 'selector;
761                }
762
763                Selector::Child => {
764                    prev_class = false;
765                    prev_child = true;
766                    cmp_node = nodes[cmp_node].parent;
767                    continue 'selector;
768                }
769
770                Selector::Descendant => {
771                    prev_child = false;
772                    prev_class = false;
773                    cmp_node = nodes[cmp_node].parent;
774                    continue 'selector;
775                }
776                Selector::Hover => {
777                    if let Some(out) = pseudo_out.as_deref_mut() {
778                        out.push((PseudoKind::Hover, cmp_node));
779                        prev_child = false;
780                        prev_class = false;
781                        continue 'selector;
782                    } else if hot_nodes.contains(&cmp_node) {
783                        prev_child = false;
784                        prev_class = false;
785                        continue 'selector;
786                    } else {
787                        return false;
788                    }
789                }
790                Selector::Focus => {
791                    if let Some(out) = pseudo_out.as_deref_mut() {
792                        out.push((PseudoKind::Focus, cmp_node));
793                        prev_child = false;
794                        prev_class = false;
795                        continue 'selector;
796                    } else if let (Some(cmp_nid), Some(focus_nid)) = (nodes[cmp_node].nid, focused_node)
797                        && cmp_nid == focus_nid
798                    {
799                        prev_child = false;
800                        prev_class = false;
801                        continue 'selector;
802                    } else {
803                        return false;
804                    }
805                }
806                Selector::Active => {
807                    if let Some(out) = pseudo_out.as_deref_mut() {
808                        out.push((PseudoKind::Active, cmp_node));
809                        prev_child = false;
810                        prev_class = false;
811                        continue 'selector;
812                    } else if let (Some(cmp_nid), Some(active_nid)) = (nodes[cmp_node].nid, active_node)
813                        && cmp_nid == active_nid
814                    {
815                        prev_child = false;
816                        prev_class = false;
817                        continue 'selector;
818                    } else {
819                        return false;
820                    }
821                }
822                Selector::Enabled | Selector::Disabled => {
823                    if let Some(out) = pseudo_out.as_deref_mut() {
824                        out.push((PseudoKind::Enabled, cmp_node));
825                        prev_child = false;
826                        prev_class = false;
827                        continue 'selector;
828                    } else {
829                        let enabled = nodes[cmp_node].enabled.get().unwrap_or(true);
830                        let want_enabled = matches!(selector, Selector::Enabled);
831                        if enabled == want_enabled {
832                            prev_child = false;
833                            prev_class = false;
834                            continue 'selector;
835                        }
836                        return false;
837                    }
838                }
839            }
840        }
841        return false;
842    }
843    true
844}
845
846#[cfg(feature = "serde")]
847impl serde::Serialize for Stylesheet {
848    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
849    where
850        S: serde::Serializer,
851    {
852        use serde::ser::SerializeMap;
853
854        let inner = self.inner.read();
855
856        let mut map = serializer.serialize_map(Some(1))?;
857        if let Some(info) = &inner.info {
858            let path = info.path.to_string_lossy();
859            map.serialize_entry("path", path.as_ref())?;
860        } else {
861            let data = self.to_string();
862            map.serialize_entry("css", data.as_str())?;
863        }
864        map.end()
865    }
866}
867
868#[cfg(feature = "serde")]
869impl<'de> serde::Deserialize<'de> for Stylesheet {
870    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
871    where
872        D: serde::Deserializer<'de>,
873    {
874        use serde::de::{self, MapAccess, Visitor};
875
876        struct StylesheetVisitor;
877
878        impl<'de> Visitor<'de> for StylesheetVisitor {
879            type Value = Stylesheet;
880
881            fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
882                f.write_str("a stylesheet string or filename")
883            }
884
885            fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
886            where
887                A: MapAccess<'de>,
888            {
889                let mut path: Option<String> = None;
890                let mut css: Option<String> = None;
891
892                while let Some(key) = map.next_key::<&str>()? {
893                    match key {
894                        "path" => {
895                            if path.is_some() {
896                                return Err(de::Error::duplicate_field("path"));
897                            }
898                            path = Some(map.next_value()?);
899                        }
900                        "css" => {
901                            if css.is_some() {
902                                return Err(de::Error::duplicate_field("css"));
903                            }
904                            css = Some(map.next_value()?);
905                        }
906                        other => {
907                            // Unknown key: consume the value so we can keep parsing.
908                            let _ = map.next_value::<de::IgnoredAny>()?;
909                            return Err(de::Error::unknown_field(other, &["path", "css"]));
910                        }
911                    }
912                }
913
914                match (path, css) {
915                    (Some(_), Some(_)) => Err(de::Error::custom("expected exactly one of \"path\" or \"css\"")),
916                    (Some(p), None) => Stylesheet::from_file(p).map_err(de::Error::custom),
917                    (None, Some(c)) => Stylesheet::from_str(&c).map_err(|_| de::Error::custom("failed to parse stylesheet CSS")),
918                    (None, None) => Err(de::Error::custom("missing \"path\" or \"css\"")),
919                }
920            }
921        }
922
923        deserializer.deserialize_map(StylesheetVisitor)
924    }
925}