use std::cell::Cell;
use dom_struct::dom_struct;
use servo_atoms::Atom;
use crate::dom::bindings::codegen::Bindings::NodeBinding::NodeMethods;
use crate::dom::bindings::codegen::Bindings::NodeListBinding::NodeListMethods;
use crate::dom::bindings::reflector::{reflect_dom_object, Reflector};
use crate::dom::bindings::root::{Dom, DomRoot, MutNullableDom};
use crate::dom::bindings::str::DOMString;
use crate::dom::document::Document;
use crate::dom::htmlelement::HTMLElement;
use crate::dom::htmlformelement::HTMLFormElement;
use crate::dom::node::{ChildrenMutation, Node};
use crate::dom::window::Window;
use crate::script_runtime::CanGc;
#[derive(JSTraceable, MallocSizeOf)]
#[crown::unrooted_must_root_lint::must_root]
pub enum NodeListType {
Simple(Vec<Dom<Node>>),
Children(ChildrenList),
Labels(LabelsList),
Radio(RadioList),
ElementsByName(ElementsByNameList),
}
#[dom_struct]
pub struct NodeList {
reflector_: Reflector,
list_type: NodeListType,
}
impl NodeList {
#[allow(crown::unrooted_must_root)]
pub fn new_inherited(list_type: NodeListType) -> NodeList {
NodeList {
reflector_: Reflector::new(),
list_type,
}
}
#[allow(crown::unrooted_must_root)]
pub fn new(window: &Window, list_type: NodeListType) -> DomRoot<NodeList> {
reflect_dom_object(
Box::new(NodeList::new_inherited(list_type)),
window,
CanGc::note(),
)
}
pub fn new_simple_list<T>(window: &Window, iter: T) -> DomRoot<NodeList>
where
T: Iterator<Item = DomRoot<Node>>,
{
NodeList::new(
window,
NodeListType::Simple(iter.map(|r| Dom::from_ref(&*r)).collect()),
)
}
pub fn new_simple_list_slice(window: &Window, slice: &[&Node]) -> DomRoot<NodeList> {
NodeList::new(
window,
NodeListType::Simple(slice.iter().map(|r| Dom::from_ref(*r)).collect()),
)
}
pub fn new_child_list(window: &Window, node: &Node) -> DomRoot<NodeList> {
NodeList::new(window, NodeListType::Children(ChildrenList::new(node)))
}
pub fn new_labels_list(window: &Window, element: &HTMLElement) -> DomRoot<NodeList> {
NodeList::new(window, NodeListType::Labels(LabelsList::new(element)))
}
pub fn new_elements_by_name_list(
window: &Window,
document: &Document,
name: DOMString,
) -> DomRoot<NodeList> {
NodeList::new(
window,
NodeListType::ElementsByName(ElementsByNameList::new(document, name)),
)
}
pub fn empty(window: &Window) -> DomRoot<NodeList> {
NodeList::new(window, NodeListType::Simple(vec![]))
}
}
impl NodeListMethods<crate::DomTypeHolder> for NodeList {
fn Length(&self) -> u32 {
match self.list_type {
NodeListType::Simple(ref elems) => elems.len() as u32,
NodeListType::Children(ref list) => list.len(),
NodeListType::Labels(ref list) => list.len(),
NodeListType::Radio(ref list) => list.len(),
NodeListType::ElementsByName(ref list) => list.len(),
}
}
fn Item(&self, index: u32) -> Option<DomRoot<Node>> {
match self.list_type {
NodeListType::Simple(ref elems) => elems
.get(index as usize)
.map(|node| DomRoot::from_ref(&**node)),
NodeListType::Children(ref list) => list.item(index),
NodeListType::Labels(ref list) => list.item(index),
NodeListType::Radio(ref list) => list.item(index),
NodeListType::ElementsByName(ref list) => list.item(index),
}
}
fn IndexedGetter(&self, index: u32) -> Option<DomRoot<Node>> {
self.Item(index)
}
}
impl NodeList {
pub fn as_children_list(&self) -> &ChildrenList {
if let NodeListType::Children(ref list) = self.list_type {
list
} else {
panic!("called as_children_list() on a non-children node list")
}
}
pub fn as_radio_list(&self) -> &RadioList {
if let NodeListType::Radio(ref list) = self.list_type {
list
} else {
panic!("called as_radio_list() on a non-radio node list")
}
}
pub fn iter(&self) -> impl Iterator<Item = DomRoot<Node>> + '_ {
let len = self.Length();
(0..len).flat_map(move |i| self.Item(i))
}
}
#[derive(JSTraceable, MallocSizeOf)]
#[crown::unrooted_must_root_lint::must_root]
pub struct ChildrenList {
node: Dom<Node>,
#[ignore_malloc_size_of = "Defined in rust-mozjs"]
last_visited: MutNullableDom<Node>,
last_index: Cell<u32>,
}
impl ChildrenList {
pub fn new(node: &Node) -> ChildrenList {
let last_visited = node.GetFirstChild();
ChildrenList {
node: Dom::from_ref(node),
last_visited: MutNullableDom::new(last_visited.as_deref()),
last_index: Cell::new(0u32),
}
}
pub fn len(&self) -> u32 {
self.node.children_count()
}
pub fn item(&self, index: u32) -> Option<DomRoot<Node>> {
let len = self.len();
if index >= len {
return None;
}
if index == 0u32 {
return self.node.GetFirstChild();
}
let last_index = self.last_index.get();
if index == last_index {
return Some(self.last_visited.get().unwrap());
}
let last_visited = if index - 1u32 == last_index {
self.last_visited.get().unwrap().GetNextSibling().unwrap()
} else if last_index > 0 && index == last_index - 1u32 {
self.last_visited
.get()
.unwrap()
.GetPreviousSibling()
.unwrap()
} else if index > last_index {
if index == len - 1u32 {
return Some(self.node.GetLastChild().unwrap());
}
if index <= last_index + (len - last_index) / 2u32 {
self.last_visited
.get()
.unwrap()
.inclusively_following_siblings()
.nth((index - last_index) as usize)
.unwrap()
} else {
self.node
.GetLastChild()
.unwrap()
.inclusively_preceding_siblings()
.nth((len - index - 1u32) as usize)
.unwrap()
}
} else if index >= last_index / 2u32 {
self.last_visited
.get()
.unwrap()
.inclusively_preceding_siblings()
.nth((last_index - index) as usize)
.unwrap()
} else {
debug_assert!(index < last_index / 2u32);
self.node
.GetFirstChild()
.unwrap()
.inclusively_following_siblings()
.nth(index as usize)
.unwrap()
};
self.last_visited.set(Some(&last_visited));
self.last_index.set(index);
Some(last_visited)
}
pub fn children_changed(&self, mutation: &ChildrenMutation) {
fn prepend(list: &ChildrenList, added: &[&Node], next: &Node) {
let len = added.len() as u32;
if len == 0u32 {
return;
}
let index = list.last_index.get();
if index < len {
list.last_visited.set(Some(added[index as usize]));
} else if index / 2u32 >= len {
list.last_index.set(len + index);
} else {
list.last_visited.set(Some(next));
list.last_index.set(len);
}
}
fn replace(
list: &ChildrenList,
prev: Option<&Node>,
removed: &Node,
added: &[&Node],
next: Option<&Node>,
) {
let index = list.last_index.get();
if removed == &*list.last_visited.get().unwrap() {
let visited = match (prev, added, next) {
(None, _, None) => {
unreachable!()
},
(_, added, _) if !added.is_empty() => added[0],
(_, _, Some(next)) => next,
(Some(prev), _, None) => {
list.last_index.set(index - 1u32);
prev
},
};
list.last_visited.set(Some(visited));
} else if added.len() != 1 {
match (prev, next) {
(Some(_), None) => {},
(None, Some(next)) => {
list.last_index.set(index - 1);
prepend(list, added, next);
},
(Some(_), Some(_)) => {
list.reset();
},
(None, None) => unreachable!(),
}
}
}
match *mutation {
ChildrenMutation::Append { .. } => {},
ChildrenMutation::Insert { .. } => {
self.reset();
},
ChildrenMutation::Prepend { added, next } => {
prepend(self, added, next);
},
ChildrenMutation::Replace {
prev,
removed,
added,
next,
} => {
replace(self, prev, removed, added, next);
},
ChildrenMutation::ReplaceAll { added, .. } => {
let len = added.len();
let index = self.last_index.get();
if len == 0 {
self.last_visited.set(None);
self.last_index.set(0u32);
} else if index < len as u32 {
self.last_visited.set(Some(added[index as usize]));
} else {
let middle = len / 2;
self.last_visited.set(Some(added[middle]));
self.last_index.set(middle as u32);
}
},
ChildrenMutation::ChangeText => {},
}
}
fn reset(&self) {
self.last_visited.set(self.node.GetFirstChild().as_deref());
self.last_index.set(0u32);
}
}
#[derive(JSTraceable, MallocSizeOf)]
#[crown::unrooted_must_root_lint::must_root]
pub struct LabelsList {
element: Dom<HTMLElement>,
}
impl LabelsList {
pub fn new(element: &HTMLElement) -> LabelsList {
LabelsList {
element: Dom::from_ref(element),
}
}
pub fn len(&self) -> u32 {
self.element.labels_count()
}
pub fn item(&self, index: u32) -> Option<DomRoot<Node>> {
self.element.label_at(index)
}
}
#[derive(Clone, Copy, JSTraceable, MallocSizeOf)]
pub enum RadioListMode {
ControlsExceptImageInputs,
Images,
}
#[derive(JSTraceable, MallocSizeOf)]
#[crown::unrooted_must_root_lint::must_root]
pub struct RadioList {
form: Dom<HTMLFormElement>,
mode: RadioListMode,
#[no_trace]
name: Atom,
}
impl RadioList {
pub fn new(form: &HTMLFormElement, mode: RadioListMode, name: Atom) -> RadioList {
RadioList {
form: Dom::from_ref(form),
mode,
name,
}
}
pub fn len(&self) -> u32 {
self.form.count_for_radio_list(self.mode, &self.name)
}
pub fn item(&self, index: u32) -> Option<DomRoot<Node>> {
self.form.nth_for_radio_list(index, self.mode, &self.name)
}
}
#[derive(JSTraceable, MallocSizeOf)]
#[crown::unrooted_must_root_lint::must_root]
pub struct ElementsByNameList {
document: Dom<Document>,
name: DOMString,
}
impl ElementsByNameList {
pub fn new(document: &Document, name: DOMString) -> ElementsByNameList {
ElementsByNameList {
document: Dom::from_ref(document),
name,
}
}
pub fn len(&self) -> u32 {
self.document.elements_by_name_count(&self.name)
}
pub fn item(&self, index: u32) -> Option<DomRoot<Node>> {
self.document
.nth_element_by_name(index, &self.name)
.map(|n| DomRoot::from_ref(&*n))
}
}