use std::cell::{Cell, Ref};
use dom_struct::dom_struct;
use html5ever::{local_name, LocalName, Prefix};
use js::rust::HandleObject;
use crate::dom::attr::Attr;
use crate::dom::bindings::cell::DomRefCell;
use crate::dom::bindings::codegen::Bindings::HTMLDetailsElementBinding::HTMLDetailsElementMethods;
use crate::dom::bindings::codegen::Bindings::HTMLSlotElementBinding::HTMLSlotElement_Binding::HTMLSlotElementMethods;
use crate::dom::bindings::codegen::Bindings::NodeBinding::Node_Binding::NodeMethods;
use crate::dom::bindings::codegen::Bindings::ShadowRootBinding::{
ShadowRootMode, SlotAssignmentMode,
};
use crate::dom::bindings::codegen::UnionTypes::ElementOrText;
use crate::dom::bindings::inheritance::Castable;
use crate::dom::bindings::refcounted::Trusted;
use crate::dom::bindings::root::{Dom, DomRoot};
use crate::dom::document::Document;
use crate::dom::element::{AttributeMutation, Element};
use crate::dom::eventtarget::EventTarget;
use crate::dom::htmlelement::HTMLElement;
use crate::dom::htmlslotelement::HTMLSlotElement;
use crate::dom::node::{BindContext, ChildrenMutation, Node, NodeDamage, NodeTraits};
use crate::dom::shadowroot::IsUserAgentWidget;
use crate::dom::text::Text;
use crate::dom::virtualmethods::VirtualMethods;
use crate::script_runtime::CanGc;
const DEFAULT_SUMMARY: &str = "Details";
#[derive(Clone, JSTraceable, MallocSizeOf)]
#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
struct ShadowTree {
summary: Dom<HTMLSlotElement>,
descendants: Dom<HTMLSlotElement>,
implicit_summary: Dom<HTMLElement>,
}
#[dom_struct]
pub(crate) struct HTMLDetailsElement {
htmlelement: HTMLElement,
toggle_counter: Cell<u32>,
shadow_tree: DomRefCell<Option<ShadowTree>>,
}
impl HTMLDetailsElement {
fn new_inherited(
local_name: LocalName,
prefix: Option<Prefix>,
document: &Document,
) -> HTMLDetailsElement {
HTMLDetailsElement {
htmlelement: HTMLElement::new_inherited(local_name, prefix, document),
toggle_counter: Cell::new(0),
shadow_tree: Default::default(),
}
}
#[cfg_attr(crown, allow(crown::unrooted_must_root))]
pub(crate) fn new(
local_name: LocalName,
prefix: Option<Prefix>,
document: &Document,
proto: Option<HandleObject>,
can_gc: CanGc,
) -> DomRoot<HTMLDetailsElement> {
Node::reflect_node_with_proto(
Box::new(HTMLDetailsElement::new_inherited(
local_name, prefix, document,
)),
document,
proto,
can_gc,
)
}
pub(crate) fn toggle(&self) {
self.SetOpen(!self.Open());
}
fn shadow_tree(&self, can_gc: CanGc) -> Ref<'_, ShadowTree> {
if !self.upcast::<Element>().is_shadow_host() {
self.create_shadow_tree(can_gc);
}
Ref::filter_map(self.shadow_tree.borrow(), Option::as_ref)
.ok()
.expect("UA shadow tree was not created")
}
fn create_shadow_tree(&self, can_gc: CanGc) {
let document = self.owner_document();
let root = self
.upcast::<Element>()
.attach_shadow(
IsUserAgentWidget::Yes,
ShadowRootMode::Closed,
false,
SlotAssignmentMode::Manual,
can_gc,
)
.expect("Attaching UA shadow root failed");
let summary = HTMLSlotElement::new(local_name!("slot"), None, &document, None, can_gc);
root.upcast::<Node>()
.AppendChild(summary.upcast::<Node>())
.unwrap();
let fallback_summary =
HTMLElement::new(local_name!("summary"), None, &document, None, can_gc);
fallback_summary
.upcast::<Node>()
.SetTextContent(Some(DEFAULT_SUMMARY.into()), can_gc);
summary
.upcast::<Node>()
.AppendChild(fallback_summary.upcast::<Node>())
.unwrap();
let descendants = HTMLSlotElement::new(local_name!("slot"), None, &document, None, can_gc);
root.upcast::<Node>()
.AppendChild(descendants.upcast::<Node>())
.unwrap();
let _ = self.shadow_tree.borrow_mut().insert(ShadowTree {
summary: summary.as_traced(),
descendants: descendants.as_traced(),
implicit_summary: fallback_summary.as_traced(),
});
self.upcast::<Node>()
.dirty(crate::dom::node::NodeDamage::OtherNodeDamage);
}
pub(crate) fn find_corresponding_summary_element(&self) -> Option<DomRoot<HTMLElement>> {
self.upcast::<Node>()
.children()
.filter_map(DomRoot::downcast::<HTMLElement>)
.find(|html_element| {
html_element.upcast::<Element>().local_name() == &local_name!("summary")
})
}
fn update_shadow_tree_contents(&self, can_gc: CanGc) {
let shadow_tree = self.shadow_tree(can_gc);
if let Some(summary) = self.find_corresponding_summary_element() {
shadow_tree
.summary
.Assign(vec![ElementOrText::Element(DomRoot::upcast(summary))]);
}
let mut slottable_children = vec![];
for child in self.upcast::<Node>().children() {
if let Some(element) = child.downcast::<Element>() {
if element.local_name() == &local_name!("summary") {
continue;
}
slottable_children.push(ElementOrText::Element(DomRoot::from_ref(element)));
}
if let Some(text) = child.downcast::<Text>() {
slottable_children.push(ElementOrText::Text(DomRoot::from_ref(text)));
}
}
shadow_tree.descendants.Assign(slottable_children);
self.upcast::<Node>().dirty(NodeDamage::OtherNodeDamage);
}
fn update_shadow_tree_styles(&self, can_gc: CanGc) {
let shadow_tree = self.shadow_tree(can_gc);
let value = if self.Open() {
"display: block;"
} else {
"display: none;"
};
shadow_tree
.descendants
.upcast::<Element>()
.set_string_attribute(&local_name!("style"), value.into(), can_gc);
let implicit_summary_list_item_style = if self.Open() {
"disclosure-open"
} else {
"disclosure-closed"
};
let implicit_summary_style = format!(
"display: list-item;
counter-increment: list-item 0;
list-style: {implicit_summary_list_item_style} inside;"
);
shadow_tree
.implicit_summary
.upcast::<Element>()
.set_string_attribute(&local_name!("style"), implicit_summary_style.into(), can_gc);
self.upcast::<Node>().dirty(NodeDamage::OtherNodeDamage);
}
}
impl HTMLDetailsElementMethods<crate::DomTypeHolder> for HTMLDetailsElement {
make_bool_getter!(Open, "open");
make_bool_setter!(SetOpen, "open");
}
impl VirtualMethods for HTMLDetailsElement {
fn super_type(&self) -> Option<&dyn VirtualMethods> {
Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods)
}
fn attribute_mutated(&self, attr: &Attr, mutation: AttributeMutation) {
self.super_type().unwrap().attribute_mutated(attr, mutation);
if attr.local_name() == &local_name!("open") {
self.update_shadow_tree_styles(CanGc::note());
let counter = self.toggle_counter.get() + 1;
self.toggle_counter.set(counter);
let this = Trusted::new(self);
self.owner_global()
.task_manager()
.dom_manipulation_task_source()
.queue(task!(details_notification_task_steps: move || {
let this = this.root();
if counter == this.toggle_counter.get() {
this.upcast::<EventTarget>().fire_event(atom!("toggle"), CanGc::note());
}
}));
self.upcast::<Node>().dirty(NodeDamage::OtherNodeDamage);
}
}
fn children_changed(&self, mutation: &ChildrenMutation) {
self.super_type().unwrap().children_changed(mutation);
self.update_shadow_tree_contents(CanGc::note());
}
fn bind_to_tree(&self, context: &BindContext) {
self.super_type().unwrap().bind_to_tree(context);
self.update_shadow_tree_contents(CanGc::note());
self.update_shadow_tree_styles(CanGc::note());
}
}