use std::cell::{Cell, Ref};
use std::collections::hash_map::Entry;
use js::context::NoGC;
use rustc_hash::{FxBuildHasher, FxHashMap};
use script_bindings::assert::assert_in_script;
use script_bindings::cell::DomRefCell;
use script_bindings::inheritance::Castable;
use script_bindings::root::{Dom, DomRoot};
use style::Atom;
use crate::dom::Node;
use crate::dom::bindings::root::{LayoutDom, MutNullableDom};
use crate::dom::bindings::trace::HashMapTracedValues;
use crate::dom::iterators::ShadowIncluding;
use crate::dom::types::Element;
#[derive(JSTraceable, MallocSizeOf)]
#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
struct TreeOrderedIndexMapEntry {
element: MutNullableDom<Element>,
elements: Vec<Dom<Element>>,
count: usize,
}
impl TreeOrderedIndexMapEntry {
fn new(element: &Element) -> Self {
Self {
element: MutNullableDom::new(Some(element)),
elements: vec![Dom::from_ref(element)],
count: 1,
}
}
fn add(&mut self) {
assert!(self.count >= 1);
self.count += 1;
self.element.clear();
self.elements.clear()
}
fn remove(&mut self) -> bool {
self.count -= 1;
self.element.clear();
self.elements.clear();
self.count > 0
}
fn needs_resolution(&self) -> bool {
self.elements.is_empty()
}
}
#[derive(Clone, Copy, JSTraceable, MallocSizeOf)]
enum IndexType {
Id,
Name,
}
impl IndexType {
fn get(&self, element: &Element) -> Option<Atom> {
match self {
IndexType::Id => element.get_id(),
IndexType::Name => element.get_name(),
}
}
}
#[derive(JSTraceable, MallocSizeOf)]
#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
pub(crate) struct TreeOrderedIndexMap {
map: DomRefCell<HashMapTracedValues<Atom, TreeOrderedIndexMapEntry, FxBuildHasher>>,
might_need_rebuild_for_layout: Cell<bool>,
index_type: IndexType,
}
impl TreeOrderedIndexMap {
pub(crate) fn name() -> Self {
Self {
map: Default::default(),
might_need_rebuild_for_layout: Default::default(),
index_type: IndexType::Name,
}
}
pub(crate) fn id() -> Self {
Self {
map: Default::default(),
might_need_rebuild_for_layout: Default::default(),
index_type: IndexType::Id,
}
}
pub(crate) fn add(&self, key: &Atom, element: &Element) {
debug_assert!(
element.upcast::<Node>().is_in_a_document_tree() ||
element.upcast::<Node>().is_in_a_shadow_tree()
);
if key.is_empty() {
return;
}
let mut map = self.map.borrow_mut();
match map.entry(key.clone()) {
Entry::Vacant(entry) => {
entry.insert(TreeOrderedIndexMapEntry::new(element));
},
Entry::Occupied(mut entry) => {
entry.get_mut().add();
self.might_need_rebuild_for_layout.set(true);
},
}
}
pub(crate) fn remove(&self, key: &Atom) {
if key.is_empty() {
return;
}
let mut map = self.map.borrow_mut();
let Entry::Occupied(mut occupied_entry) = map.entry(key.clone()) else {
unreachable!("Tried to remove unknown id or name entry: {key}");
};
if !occupied_entry.get_mut().remove() {
occupied_entry.remove();
} else {
self.might_need_rebuild_for_layout.set(true);
}
}
pub(crate) fn get(&self, no_gc: &NoGC, scope: &Node, key: &Atom) -> Option<DomRoot<Element>> {
assert_in_script();
let mut map = self.map.borrow_mut();
let Entry::Occupied(mut occupied_entry) = map.entry(key.clone()) else {
return None;
};
if let Some(element) = occupied_entry.get().element.get() {
return Some(element);
}
Self::resolve_one(no_gc, scope, key, occupied_entry.get_mut(), self.index_type);
occupied_entry.get().element.get()
}
pub(crate) fn get_all(
&self,
no_gc: &NoGC,
scope: &Node,
key: &Atom,
) -> Ref<'_, [Dom<Element>]> {
assert_in_script();
{
let mut map = self.map.borrow_mut();
if let Entry::Occupied(mut occupied_entry) = map.entry(key.clone()) &&
occupied_entry.get().needs_resolution()
{
Self::resolve_one(no_gc, scope, key, occupied_entry.get_mut(), self.index_type);
}
}
Ref::map(self.map.borrow(), |map| {
map.get(key)
.map(|entry| &*entry.elements)
.unwrap_or_default()
})
}
pub(crate) fn for_each(
&self,
no_gc: &NoGC,
scope: &Node,
mut callback: impl FnMut(&Atom, &[Dom<Element>]),
) {
self.resolve_all(no_gc, scope);
for (key, entry) in self.map.borrow().iter() {
callback(key, &entry.elements);
}
}
#[expect(unsafe_code)]
pub(crate) fn get_all_for_layout(&self, key: &Atom) -> &[LayoutDom<'_, Element>] {
unsafe {
self.map
.borrow_for_layout()
.get(key)
.map_or(&[], |entry| LayoutDom::to_layout_slice(&entry.elements))
}
}
fn resolve_one(
no_gc: &NoGC,
scope: &Node,
key: &Atom,
entry: &mut TreeOrderedIndexMapEntry,
index_type: IndexType,
) {
for node in scope.traverse_preorder_non_rooting(no_gc, ShadowIncluding::No) {
let Some(element) = node.downcast::<Element>() else {
continue;
};
match index_type.get(element) {
Some(id) if id == *key => {
entry.elements.push(Dom::from_ref(element));
entry.element.or_init(|| DomRoot::from_ref(element));
if entry.count == entry.elements.len() {
return;
}
},
_ => {},
}
}
}
pub(crate) fn resolve_all(&self, no_gc: &NoGC, scope: &Node) {
if !self.might_need_rebuild_for_layout.take() {
return;
}
let mut map = self.map.borrow_mut();
let mut entries_needing_rebuild: FxHashMap<_, _> = map
.iter_mut()
.filter(|(_, entry)| entry.needs_resolution())
.collect();
for node in scope.traverse_preorder_non_rooting(no_gc, ShadowIncluding::No) {
let Some(element) = node.downcast::<Element>() else {
continue;
};
let Some(index) = self.index_type.get(element) else {
continue;
};
let mut complete = false;
if let Some(entry) = entries_needing_rebuild.get_mut(&index) {
entry.elements.push(Dom::from_ref(element));
entry.element.or_init(|| DomRoot::from_ref(element));
complete = entry.count == entry.elements.len();
}
if complete {
entries_needing_rebuild.remove(&index);
if entries_needing_rebuild.is_empty() {
return;
}
}
}
}
}