use crate::tdom::node::{NodeArena, NodeId, NodeType};
use super::selector::{
Combinator, CompoundSelector, CssSelector, DynamicState, NthExpr, PseudoClass, Selector,
};
impl PseudoClass {
pub fn matches(&self, arena: &NodeArena, node_id: NodeId, state: &DynamicState) -> bool {
match self {
PseudoClass::Hover => state
.hovered
.is_some_and(|h| is_hovered_or_ancestor(arena, node_id, h)),
PseudoClass::Focus => state.focused == Some(node_id),
}
}
pub fn affected_candidates(&self, arena: &NodeArena, target: Option<NodeId>) -> Vec<NodeId> {
match self {
PseudoClass::Hover => ancestor_chain(arena, target),
PseudoClass::Focus => target.into_iter().collect(),
}
}
pub fn state_with_target(&self, target: Option<NodeId>) -> DynamicState {
match self {
PseudoClass::Hover => DynamicState {
hovered: target,
focused: None,
},
PseudoClass::Focus => DynamicState {
hovered: None,
focused: target,
},
}
}
}
fn ancestor_chain(arena: &NodeArena, target: Option<NodeId>) -> Vec<NodeId> {
let mut out = Vec::new();
let mut cur = target;
while let Some(id) = cur {
out.push(id);
cur = arena.get(id).and_then(|n| n.parent);
}
out
}
fn matches_css(arena: &NodeArena, node_id: NodeId, sel: &Selector, state: &DynamicState) -> bool {
let node = &arena[node_id];
match sel {
Selector::Universal => !matches!(
node.node_type,
NodeType::Document
| NodeType::DocumentType
| NodeType::DocumentFragment
| NodeType::Text
| NodeType::Comment
),
Selector::Tag(nt) => node.node_type == *nt,
Selector::Class(name) => node.has_class(name),
Selector::Id(id) => node
.get_id()
.map(|s| s.as_str() == id.as_str())
.unwrap_or(false),
Selector::NthChild(expr) => nth_child_matches(arena, node_id, *expr),
Selector::PseudoClass(p) => p.matches(arena, node_id, state),
}
}
fn matches_compound(
arena: &NodeArena,
node_id: NodeId,
compound: &CompoundSelector,
state: &DynamicState,
) -> bool {
compound
.iter()
.all(|s| matches_css(arena, node_id, s, state))
}
fn nth_child_matches(arena: &NodeArena, node_id: NodeId, expr: NthExpr) -> bool {
let Some(parent_id) = arena[node_id].parent else {
return false;
};
let parent = &arena[parent_id];
let mut index = 1usize;
for &cid in &parent.children {
if cid == node_id {
return expr.matches(index);
}
index = index.saturating_add(1);
}
false
}
#[doc(hidden)]
pub fn selector_chain_matches(arena: &NodeArena, node_id: NodeId, selector: &CssSelector) -> bool {
selector_chain_matches_with_hover(arena, node_id, selector, None)
}
pub fn selector_chain_matches_with_state(
arena: &NodeArena,
node_id: NodeId,
selector: &CssSelector,
state: &DynamicState,
) -> bool {
let n = selector.compounds.len();
if n == 0 {
return false;
}
if !matches_compound(arena, node_id, &selector.compounds[n - 1], state) {
return false;
}
if n == 1 {
return true;
}
match_combinators(
arena,
node_id,
&selector.compounds,
&selector.combinators,
n - 1,
state,
)
}
pub fn selector_chain_matches_with_hover(
arena: &NodeArena,
node_id: NodeId,
selector: &CssSelector,
hovered: Option<NodeId>,
) -> bool {
selector_chain_matches_with_state(
arena,
node_id,
selector,
&DynamicState {
hovered,
focused: None,
},
)
}
fn is_hovered_or_ancestor(arena: &NodeArena, node_id: NodeId, hovered: NodeId) -> bool {
let mut current = Some(hovered);
while let Some(id) = current {
if id == node_id {
return true;
}
current = arena.get(id).and_then(|n| n.parent);
}
false
}
fn match_combinators(
arena: &NodeArena,
node_id: NodeId,
compounds: &[CompoundSelector],
combinators: &[Combinator],
idx: usize,
state: &DynamicState,
) -> bool {
if idx == 0 {
return true;
}
let combinator = combinators[idx - 1];
let target = &compounds[idx - 1];
match combinator {
Combinator::Descendant => {
let mut cur = arena[node_id].parent;
while let Some(pid) = cur {
let ancestor = &arena[pid];
if matches_compound(arena, pid, target, state)
&& match_combinators(arena, pid, compounds, combinators, idx - 1, state)
{
return true;
}
cur = ancestor.parent;
}
false
}
Combinator::Child => match arena[node_id].parent {
Some(pid) if matches_compound(arena, pid, target, state) => {
match_combinators(arena, pid, compounds, combinators, idx - 1, state)
}
_ => false,
},
Combinator::NextSibling => match prev_sibling_id(arena, node_id) {
Some(sid) if matches_compound(arena, sid, target, state) => {
match_combinators(arena, sid, compounds, combinators, idx - 1, state)
}
_ => false,
},
Combinator::SubsequentSibling => {
let mut cur = prev_sibling_id(arena, node_id);
while let Some(sid) = cur {
if matches_compound(arena, sid, target, state)
&& match_combinators(arena, sid, compounds, combinators, idx - 1, state)
{
return true;
}
cur = prev_sibling_id(arena, sid);
}
false
}
}
}
fn prev_sibling_id(arena: &NodeArena, node_id: NodeId) -> Option<NodeId> {
let node = &arena[node_id];
let parent_id = node.parent?;
let parent = &arena[parent_id];
let mut prev = None;
for &cid in &parent.children {
if cid == node_id {
return prev;
}
prev = Some(cid);
}
None
}