orinium_browser/engine/css/
matcher.rs

1//! CSSセレクターマッチング処理。DOM要素とセレクターの照合を行う。
2
3use super::parser::{Combinator, ComplexSelector, Selector};
4
5#[derive(Debug, Clone)]
6pub struct ElementInfo {
7    pub tag_name: String,
8    pub id: Option<String>,
9    pub classes: Vec<String>,
10}
11
12/// 右(自分)→ 左(祖先)
13pub type ElementChain = Vec<ElementInfo>;
14
15impl Selector {
16    /// Simple selector matcher (tag / class / id)
17    pub fn matches(&self, tag_name: &str, id: Option<&str>, class_list: &[String]) -> bool {
18        // tag
19        if let Some(tag) = &self.tag
20            && tag != tag_name
21        {
22            return false;
23        }
24
25        // id
26        if let Some(expected_id) = &self.id {
27            match id {
28                Some(actual_id) if actual_id == expected_id => {}
29                _ => return false,
30            }
31        }
32
33        // class
34        for class in &self.classes {
35            if !class_list.iter().any(|c| c == class) {
36                return false;
37            }
38        }
39
40        if let Some(_pseudo) = &self.pseudo_class {
41            // TODO
42            return false;
43        }
44
45        if let Some(_pseudo) = &self.pseudo_element {
46            // TODO
47            return false;
48        }
49
50        true
51    }
52}
53
54impl ComplexSelector {
55    pub fn matches(&self, chain: &[ElementInfo]) -> bool {
56        if chain.is_empty() || self.parts.is_empty() {
57            return false;
58        }
59        self.match_from(chain, 0, 0)
60    }
61
62    fn match_from(&self, chain: &[ElementInfo], chain_index: usize, selector_index: usize) -> bool {
63        let element = &chain[chain_index];
64        let part = &self.parts[selector_index];
65
66        if !part
67            .selector
68            .matches(&element.tag_name, element.id.as_deref(), &element.classes)
69        {
70            return false;
71        }
72
73        // セレクタが尽きた → 完全一致
74        if selector_index + 1 == self.parts.len() {
75            return true;
76        }
77
78        match part.combinator {
79            Some(Combinator::Descendant) => {
80                for next in (chain_index + 1)..chain.len() {
81                    if self.match_from(chain, next, selector_index + 1) {
82                        return true;
83                    }
84                }
85                false
86            }
87            None => false,
88        }
89    }
90
91    pub fn specificity(&self) -> (u32, u32, u32) {
92        let mut a = 0; // id
93        let mut b = 0; // class / attr / pseudo-class
94        let mut c = 0; // tag / pseudo-element
95
96        for part in &self.parts {
97            let sel = &part.selector;
98
99            if sel.id.is_some() {
100                a += 1;
101            }
102            b += sel.classes.len() as u32;
103            if sel.tag.is_some() {
104                c += 1;
105            }
106        }
107
108        (a, b, c)
109    }
110}