use std::cell::{Cell, RefCell};
use std::rc::Rc;
use dom_struct::dom_struct;
use js::context::JSContext;
use js::jsapi::JSTracer;
use script_bindings::callback::{OwnerWindow, RootedCallback, TracedCallback};
use script_bindings::reflector::{Reflector, reflect_weak_referenceable_dom_object};
use smallvec::SmallVec;
use crate::dom::bindings::callback::ExceptionHandling::Rethrow;
use crate::dom::bindings::codegen::Bindings::NodeBinding::NodeMethods;
use crate::dom::bindings::codegen::Bindings::NodeFilterBinding::{NodeFilter, NodeFilterConstants};
use crate::dom::bindings::codegen::Bindings::NodeIteratorBinding::NodeIteratorMethods;
use crate::dom::bindings::error::{Error, Fallible};
use crate::dom::bindings::root::{Dom, DomRoot, MutDom};
use crate::dom::bindings::trace::JSTraceable;
use crate::dom::bindings::weakref::{WeakRef, WeakRefVec};
use crate::dom::document::Document;
use crate::dom::iterators::ShadowIncluding;
use crate::dom::node::Node;
#[dom_struct]
pub(crate) struct NodeIterator {
reflector_: Reflector,
root_node: Dom<Node>,
#[ignore_malloc_size_of = "Defined in rust-mozjs"]
reference_node: MutDom<Node>,
pointer_before_reference_node: Cell<bool>,
what_to_show: u32,
filter: Filter,
active: Cell<bool>,
}
impl NodeIterator {
fn new_inherited(
root_node: &Node,
what_to_show: u32,
node_filter: Option<RootedCallback<NodeFilter>>,
) -> NodeIterator {
NodeIterator {
reflector_: Reflector::new(),
root_node: Dom::from_ref(root_node),
reference_node: MutDom::new(root_node),
pointer_before_reference_node: Cell::new(true),
what_to_show,
filter: match node_filter {
None => Filter::None,
Some(callback) => Filter::Callback(callback.to_traced()),
},
active: Cell::new(false),
}
}
pub(crate) fn new_with_filter(
cx: &mut JSContext,
document: &Document,
root_node: &Node,
what_to_show: u32,
node_filter: Option<RootedCallback<NodeFilter>>,
) -> DomRoot<NodeIterator> {
let iterator = reflect_weak_referenceable_dom_object(
cx,
Rc::new(NodeIterator::new_inherited(
root_node,
what_to_show,
node_filter,
)),
document.window(),
);
root_node
.owner_doc()
.node_iterators()
.push(WeakRef::new(&iterator));
iterator
}
pub(crate) fn new(
cx: &mut JSContext,
document: &Document,
root_node: &Node,
what_to_show: u32,
node_filter: Option<RootedCallback<NodeFilter>>,
) -> DomRoot<NodeIterator> {
NodeIterator::new_with_filter(cx, document, root_node, what_to_show, node_filter)
}
}
impl NodeIteratorMethods<crate::DomTypeHolder> for NodeIterator {
fn Root(&self) -> DomRoot<Node> {
DomRoot::from_ref(&*self.root_node)
}
fn WhatToShow(&self) -> u32 {
self.what_to_show
}
fn GetFilter(&self) -> Option<RootedCallback<NodeFilter>> {
match self.filter {
Filter::None => None,
Filter::Callback(ref nf) => Some(nf.root()),
}
}
fn ReferenceNode(&self) -> DomRoot<Node> {
self.reference_node.get()
}
fn PointerBeforeReferenceNode(&self) -> bool {
self.pointer_before_reference_node.get()
}
fn NextNode(&self, cx: &mut JSContext) -> Fallible<Option<DomRoot<Node>>> {
let node = self.reference_node.get();
let mut before_node = self.pointer_before_reference_node.get();
if before_node {
before_node = false;
let result = self.accept_node(cx, &node)?;
if result == NodeFilterConstants::FILTER_ACCEPT {
self.reference_node.set(&node);
self.pointer_before_reference_node.set(before_node);
return Ok(Some(node));
}
}
for following_node in node.following_nodes(&self.root_node, ShadowIncluding::No) {
let result = self.accept_node(cx, &following_node)?;
if result == NodeFilterConstants::FILTER_ACCEPT {
self.reference_node.set(&following_node);
self.pointer_before_reference_node.set(before_node);
return Ok(Some(following_node));
}
}
Ok(None)
}
fn PreviousNode(&self, cx: &mut JSContext) -> Fallible<Option<DomRoot<Node>>> {
let node = self.reference_node.get();
let mut before_node = self.pointer_before_reference_node.get();
if !before_node {
before_node = true;
let result = self.accept_node(cx, &node)?;
if result == NodeFilterConstants::FILTER_ACCEPT {
self.reference_node.set(&node);
self.pointer_before_reference_node.set(before_node);
return Ok(Some(node));
}
}
for preceding_node in node.preceding_nodes(&self.root_node) {
let result = self.accept_node(cx, &preceding_node)?;
if result == NodeFilterConstants::FILTER_ACCEPT {
self.reference_node.set(&preceding_node);
self.pointer_before_reference_node.set(before_node);
return Ok(Some(preceding_node));
}
}
Ok(None)
}
fn Detach(&self) {
}
}
impl NodeIterator {
fn accept_node(&self, cx: &mut JSContext, node: &Node) -> Fallible<u16> {
if self.active.get() {
return Err(Error::InvalidState(Some(
"Node iterator cannot be active".into(),
)));
}
let n = node.NodeType() - 1;
if (self.what_to_show & (1 << n)) == 0 {
return Ok(NodeFilterConstants::FILTER_SKIP);
}
match self.filter {
Filter::None => Ok(NodeFilterConstants::FILTER_ACCEPT),
Filter::Callback(ref callback) => {
self.active.set(true);
let result = callback.AcceptNode_(cx, self, node, Rethrow);
self.active.set(false);
result
},
}
}
}
#[derive(JSTraceable, MallocSizeOf)]
#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
pub(crate) enum Filter {
None,
Callback(TracedCallback<NodeFilter>),
}
impl OwnerWindow<crate::DomTypeHolder> for NodeIterator {}
pub(crate) fn node_iterator_pre_remove(node: &Node) {
let document = node.owner_doc();
for iterator in document.node_iterators().live_node_iterators() {
iterator.pre_remove_step(node);
}
}
impl NodeIterator {
fn pre_remove_step(&self, removed: &Node) {
if let Some((node, pointer_before)) = Self::adjust_node_pointer(
&self.root_node,
&self.reference_node.get(),
self.pointer_before_reference_node.get(),
removed,
) {
self.reference_node.set(&node);
self.pointer_before_reference_node.set(pointer_before);
}
}
fn adjust_node_pointer(
root: &Node,
reference: &Node,
pointer_before: bool,
removed: &Node,
) -> Option<(DomRoot<Node>, bool)> {
if !removed.is_inclusive_ancestor_of(reference) ||
removed.is_inclusive_ancestor_of(root)
{
return None;
}
if pointer_before {
let mut current = DomRoot::from_ref(removed);
let next = loop {
if let Some(sibling) = current.GetNextSibling() {
break Some(sibling);
}
match current.GetParentNode() {
Some(parent) if &*parent != root => current = parent,
_ => break None,
}
};
if let Some(next) = next {
return Some((next, true));
}
}
let new_node = match removed.GetPreviousSibling() {
Some(sibling) => {
let mut last = sibling;
while let Some(child) = last.GetLastChild() {
last = child;
}
last
},
None => removed
.GetParentNode()
.expect("toBeRemovedNode is not an inclusive ancestor of root"),
};
Some((new_node, false))
}
}
#[derive(MallocSizeOf)]
pub(crate) struct WeakNodeIteratorVec {
cell: RefCell<WeakRefVec<NodeIterator>>,
}
impl Default for WeakNodeIteratorVec {
fn default() -> Self {
WeakNodeIteratorVec {
cell: RefCell::new(WeakRefVec::new()),
}
}
}
impl WeakNodeIteratorVec {
pub(crate) fn live_node_iterators(&self) -> SmallVec<[DomRoot<NodeIterator>; 4]> {
let cell = self.cell.borrow();
if cell.is_empty() {
return Default::default();
}
cell.iter().filter_map(|iterator| iterator.root()).collect()
}
pub(crate) fn push(&self, ref_: WeakRef<NodeIterator>) {
self.cell.borrow_mut().push(ref_);
}
}
#[expect(unsafe_code)]
unsafe impl JSTraceable for WeakNodeIteratorVec {
unsafe fn trace(&self, _: *mut JSTracer) {
self.cell.borrow_mut().retain_alive()
}
}