///|
/// Shadow-scoped `querySelector` / `querySelectorAll` (#286).
///
/// `query_selector` (`query.mbt`) scopes to a node's descendants using the plain
/// `mizchi/css` matcher, which cannot resolve the shadow-scoping pseudo-classes.
/// These entries query *inside a shadow root* and route matching through
/// `matches_shadow_scoped` (#285) so `:host(...)` and `:host-context(...)`
/// resolve against the live shadow relationships. (`::slotted()` targets light
/// nodes, which are not shadow-tree descendants, and is a styling pseudo-element
/// not matched by `querySelector` — see #286 — so it never selects here.)
///
/// Candidates are the shadow root's element descendants in document order;
/// nested shadow trees are not crossed (a shadow host inside the tree contributes
/// only its light children, never its own shadow root). The scope root itself is
/// never returned, matching `querySelectorAll` semantics.
///|
/// Shadow-scoped `querySelectorAll`: every shadow-tree element selected by
/// `selector`, in document order. `shadow_root` must be a shadow root node; its
/// host (resolved from the shadow root) anchors `:host` / `:host-context` /
/// `::slotted` resolution. An invalid selector yields `InvalidOperation`.
pub fn DomTree::shadow_query_selector_all(
self : DomTree,
shadow_root : NodeId,
selector : String,
) -> Result[Array[NodeId], CoreError] {
let root_node = match self.nodes.get(shadow_root.to_int()) {
Some(node) => node
None => return Err(NodeNotFound(node_id=shadow_root))
}
guard root_node.node_type == ShadowRoot else {
return Err(
InvalidOperation(message="node is not a shadow root: \{shadow_root}"),
)
}
let host = match root_node.host_id {
Some(id) => NodeId(id)
None =>
return Err(
InvalidOperation(message="shadow root has no host: \{shadow_root}"),
)
}
let list = match @selector.parse_selector_list_text(selector) {
Some(list) => list
None =>
return Err(InvalidOperation(message="invalid selector: \{selector}"))
}
let results : Array[NodeId] = []
self.shadow_query_collect(shadow_root, host, list, results)
Ok(results)
}
///|
/// Shadow-scoped `querySelector`: the first shadow-tree element selected by
/// `selector` in document order, or `None`.
pub fn DomTree::shadow_query_selector(
self : DomTree,
shadow_root : NodeId,
selector : String,
) -> Result[NodeId?, CoreError] {
match self.shadow_query_selector_all(shadow_root, selector) {
Ok(results) =>
if results.length() > 0 {
Ok(Some(results[0]))
} else {
Ok(None)
}
Err(e) => Err(e)
}
}
///|
/// Walk the element descendants of `node` in document order, testing each with
/// the shadow-aware matcher and recursing through light children only (nested
/// shadow roots are not entered).
fn DomTree::shadow_query_collect(
self : DomTree,
node : NodeId,
host : NodeId,
list : @selector.SelectorList,
results : Array[NodeId],
) -> Unit {
let n = match self.nodes.get(node.to_int()) {
Some(n) => n
None => return
}
for child_id in n.children {
match self.nodes.get(child_id) {
Some(child) if child.node_type == Element => {
let child_node = NodeId(child_id)
if self.matches_shadow_scoped_list(child_node, list, host) {
results.push(child_node)
}
self.shadow_query_collect(child_node, host, list, results)
}
_ => ()
}
}
}