///|
/// Create a document fragment node
pub fn DomTree::create_document_fragment(self : DomTree) -> NodeId {
self.allocate_node(DomNode::new_document_fragment())
}
///|
/// Attach a new shadow root to a host element
pub fn DomTree::attach_shadow(
self : DomTree,
host : NodeId,
mode : String,
) -> Result[NodeId, CoreError] {
self.attach_shadow_with_init(host, ShadowRootInit::default(mode~))
}
///|
pub fn DomTree::attach_shadow_with_init(
self : DomTree,
host : NodeId,
init : ShadowRootInit,
) -> Result[NodeId, CoreError] {
let shadow_root = self.create_document_fragment()
match self.attach_existing_shadow_root_with_init(host, shadow_root, init) {
Ok(_) => Ok(shadow_root)
Err(err) => {
self.nodes.remove(shadow_root.to_int())
Err(err)
}
}
}
///|
/// Mark an existing document fragment as the host's shadow root
pub fn DomTree::attach_existing_shadow_root(
self : DomTree,
host : NodeId,
shadow_root : NodeId,
mode : String,
) -> Result[Unit, CoreError] {
self.attach_existing_shadow_root_with_init(
host,
shadow_root,
ShadowRootInit::default(mode~),
)
}
///|
pub fn DomTree::attach_existing_shadow_root_with_init(
self : DomTree,
host : NodeId,
shadow_root : NodeId,
init : ShadowRootInit,
) -> Result[Unit, CoreError] {
let host_id = host.to_int()
let shadow_id = shadow_root.to_int()
match (self.nodes.get(host_id), self.nodes.get(shadow_id)) {
(Some(host_node), Some(shadow_node)) => {
if host_node.node_type != Element {
return Err(
InvalidOperation(message="Only element nodes can host shadow roots"),
)
}
if host_node.shadow_root_id != None {
return Err(InvalidOperation(message="Host already has a shadow root"))
}
if shadow_node.node_type != DocumentFragment &&
shadow_node.node_type != ShadowRoot {
return Err(
InvalidOperation(
message="Shadow root must be created from a document fragment",
),
)
}
shadow_node.node_type = ShadowRoot
shadow_node.tag_name = "#shadow-root"
shadow_node.host_id = Some(host_id)
shadow_node.shadow_mode = Some(init.mode)
shadow_node.shadow_delegates_focus = init.delegates_focus
shadow_node.shadow_slot_assignment = init.slot_assignment
shadow_node.shadow_clonable = init.clonable
shadow_node.shadow_serializable = init.serializable
host_node.shadow_root_id = Some(shadow_id)
self.mutations.push(MutationRecord::child_list(host, added=[shadow_root]))
self.invalidate_layout(host_id)
Ok(())
}
(None, _) => Err(NodeNotFound(node_id=host))
(_, None) => Err(NodeNotFound(node_id=shadow_root))
}
}
///|
/// Get shadow root for a host element
pub fn DomTree::get_shadow_root(
self : DomTree,
host : NodeId,
) -> Result[NodeId?, CoreError] {
let id = host.to_int()
match self.nodes.get(id) {
Some(node) => Ok(node.shadow_root_id.map(NodeId::from_int))
None => Err(NodeNotFound(node_id=host))
}
}
///|
/// Get host for a shadow root
pub fn DomTree::get_host(
self : DomTree,
node : NodeId,
) -> Result[NodeId?, CoreError] {
let id = node.to_int()
match self.nodes.get(id) {
Some(dom_node) => Ok(dom_node.host_id.map(NodeId::from_int))
None => Err(NodeNotFound(node_id=node))
}
}
///|
/// Get shadow mode for a shadow root
pub fn DomTree::get_shadow_mode(
self : DomTree,
node : NodeId,
) -> Result[String?, CoreError] {
let id = node.to_int()
match self.nodes.get(id) {
Some(dom_node) => Ok(dom_node.shadow_mode)
None => Err(NodeNotFound(node_id=node))
}
}
///|
pub fn DomTree::get_shadow_init(
self : DomTree,
node : NodeId,
) -> Result[ShadowRootInit?, CoreError] {
let id = node.to_int()
match self.nodes.get(id) {
Some(dom_node) =>
match dom_node.shadow_mode {
Some(mode) =>
Ok(
Some({
mode,
delegates_focus: dom_node.shadow_delegates_focus,
slot_assignment: dom_node.shadow_slot_assignment,
clonable: dom_node.shadow_clonable,
serializable: dom_node.shadow_serializable,
}),
)
None => Ok(None)
}
None => Err(NodeNotFound(node_id=node))
}
}
///|
/// Get root node for a node
pub fn DomTree::get_root_node(
self : DomTree,
node : NodeId,
composed? : Bool = false,
) -> Result[NodeId, CoreError] {
let id = node.to_int()
guard self.nodes.contains(id) else { return Err(NodeNotFound(node_id=node)) }
let root_id = self.get_root_node_internal(id)
if !composed {
return Ok(NodeId::from_int(root_id))
}
match self.nodes.get(root_id) {
Some(root) =>
match root.host_id {
Some(host_id) =>
self.get_root_node(NodeId::from_int(host_id), composed=true)
None => Ok(NodeId::from_int(root_id))
}
None => Err(NodeNotFound(node_id=NodeId::from_int(root_id)))
}
}
///|
fn DomTree::get_root_node_internal(self : DomTree, node_id : Int) -> Int {
match self.nodes.get(node_id) {
Some(node) =>
match node.parent_id {
Some(parent_id) => self.get_root_node_internal(parent_id)
None => node_id
}
None => node_id
}
}
///|
fn DomTree::get_invalidation_parent_id(self : DomTree, node_id : Int) -> Int? {
match self.nodes.get(node_id) {
Some(node) =>
match node.parent_id {
Some(parent_id) => Some(parent_id)
None => node.host_id
}
None => None
}
}
///|
pub fn DomTree::has_shadow_roots(self : DomTree) -> Bool {
for _, node in self.nodes {
if node.node_type == ShadowRoot || node.shadow_root_id != None {
return true
}
}
false
}
///|
/// Whether a node is a slot element
pub fn DomTree::is_slot_element(self : DomTree, node : NodeId) -> Bool {
match self.get_node_info(node) {
Ok(info) =>
match info.node_type {
Element => info.node_name.to_lower() == "slot"
_ => false
}
Err(_) => false
}
}
///|
/// Return the composed children for a node in the current tree scope.
///
/// - Shadow hosts expose their shadow root children instead of light DOM children.
/// - Slot elements inside a shadow tree expose assigned light DOM nodes, falling
/// back to their own children when no assignment exists.
pub fn DomTree::get_composed_children(
self : DomTree,
node : NodeId,
shadow_host? : NodeId? = None,
) -> Array[NodeId] {
match self.get_shadow_root(node) {
Ok(Some(shadow_root)) =>
match self.get_children(shadow_root) {
Ok(children) => children
Err(_) => []
}
_ =>
if shadow_host != None && self.is_slot_element(node) {
self.get_assigned_nodes_for_slot(node, shadow_host.unwrap())
} else {
match self.get_children(node) {
Ok(children) => children
Err(_) => []
}
}
}
}
///|
fn DomTree::get_slot_name_for_light_node(
self : DomTree,
node : NodeId,
) -> String {
match self.get_attribute(node, "slot") {
Ok(Some(value)) => value
_ => ""
}
}
///|
fn DomTree::find_first_slot_for_name(
self : DomTree,
root : NodeId,
slot_name : String,
) -> NodeId? {
if self.is_slot_element(root) {
let current_name = match self.get_attribute(root, "name") {
Ok(Some(value)) => value
_ => ""
}
if current_name == slot_name {
return Some(root)
}
}
match self.get_children(root) {
Ok(children) =>
for child in children {
match self.find_first_slot_for_name(child, slot_name) {
Some(found) => return Some(found)
None => ()
}
}
Err(_) => ()
}
None
}
///|
fn DomTree::get_assigned_slot_for_light_node(
self : DomTree,
shadow_host : NodeId,
node : NodeId,
) -> NodeId? {
match self.get_parent(node) {
Ok(Some(parent)) if parent == shadow_host =>
match self.get_shadow_root(shadow_host) {
Ok(Some(shadow_root)) =>
self.find_first_slot_for_name(
shadow_root,
self.get_slot_name_for_light_node(node),
)
_ => None
}
_ => None
}
}
///|
fn DomTree::get_assigned_nodes_for_slot(
self : DomTree,
slot : NodeId,
shadow_host : NodeId,
) -> Array[NodeId] {
let assigned : Array[NodeId] = []
match self.get_children(shadow_host) {
Ok(children) =>
for child in children {
match self.get_assigned_slot_for_light_node(shadow_host, child) {
Some(target_slot) if target_slot == slot => assigned.push(child)
_ => ()
}
}
Err(_) => ()
}
if !assigned.is_empty() {
return assigned
}
match self.get_children(slot) {
Ok(children) => children
Err(_) => []
}
}
///|
/// Whether a node is a slottable (only elements and text nodes can be assigned
/// to a slot).
fn DomTree::is_slottable(self : DomTree, node : NodeId) -> Bool {
match self.nodes.get(node.to_int()) {
Some(n) => n.node_type == Element || n.node_type == Text
None => false
}
}
///|
/// Find the shadow host of the shadow tree that contains `node`, or `None` when
/// `node` is not inside a shadow tree. Climbs the node's tree scope to its
/// shadow root and returns that root's host.
fn DomTree::shadow_host_of(self : DomTree, node : NodeId) -> NodeId? {
let root_id = self.get_root_node_internal(node.to_int())
match self.nodes.get(root_id) {
Some(root) if root.node_type == ShadowRoot =>
root.host_id.map(NodeId::from_int)
_ => None
}
}
///|
/// Slottables actually assigned to `slot`, in tree order, without fallback
/// content. Empty when `slot` is not a slot element, is outside a shadow tree,
/// or has no assigned nodes.
fn DomTree::slot_assigned_nodes_raw(
self : DomTree,
slot : NodeId,
) -> Array[NodeId] {
guard self.is_slot_element(slot) else { return [] }
match self.shadow_host_of(slot) {
Some(host) => {
let assigned : Array[NodeId] = []
match self.get_children(host) {
Ok(children) =>
for child in children {
if self.is_slottable(child) {
match self.get_assigned_slot_for_light_node(host, child) {
Some(target) if target == slot => assigned.push(child)
_ => ()
}
}
}
Err(_) => ()
}
assigned
}
None => []
}
}
///|
/// `HTMLSlotElement.assignedNodes()`. Returns the slottable nodes (elements and
/// text) assigned to `slot`, in tree order.
///
/// With `flatten`, an empty assignment falls back to the slot's own children
/// (its default content), and any assigned slot is recursively replaced by its
/// own flattened assigned nodes — matching `assignedNodes({ flatten: true })`.
pub fn DomTree::slot_assigned_nodes(
self : DomTree,
slot : NodeId,
flatten? : Bool = false,
) -> Array[NodeId] {
guard self.is_slot_element(slot) else { return [] }
let assigned = self.slot_assigned_nodes_raw(slot)
if !flatten {
return assigned
}
let source = if assigned.is_empty() {
match self.get_children(slot) {
Ok(children) => children
Err(_) => []
}
} else {
assigned
}
let flattened : Array[NodeId] = []
for n in source {
if self.is_slot_element(n) {
for m in self.slot_assigned_nodes(n, flatten=true) {
flattened.push(m)
}
} else {
flattened.push(n)
}
}
flattened
}
///|
/// `HTMLSlotElement.assignedElements()`. Like `slot_assigned_nodes` but
/// filtered to element nodes.
pub fn DomTree::slot_assigned_elements(
self : DomTree,
slot : NodeId,
flatten? : Bool = false,
) -> Array[NodeId] {
let result : Array[NodeId] = []
for n in self.slot_assigned_nodes(slot, flatten~) {
match self.nodes.get(n.to_int()) {
Some(node) if node.node_type == Element => result.push(n)
_ => ()
}
}
result
}
///|
/// `Node.assignedSlot`. Returns the slot that `node` is assigned to, or `None`
/// when `node` is not a slottable assigned to a slot in its parent host's
/// shadow tree.
pub fn DomTree::assigned_slot(self : DomTree, node : NodeId) -> NodeId? {
guard self.is_slottable(node) else { return None }
match self.get_parent(node) {
Ok(Some(parent)) => self.get_assigned_slot_for_light_node(parent, node)
_ => None
}
}