use std::cell::Ref;
use std::ops::Deref;
use devtools_traits::AttrInfo;
use html5ever::{LocalName, Namespace, Prefix, ns};
use js::context::JSContext;
use script_bindings::cell::DomRefCell;
use script_bindings::root::{Dom, DomRoot};
use script_bindings::str::DOMString;
use style::attr::{AttrIdentifier, AttrValue};
use style::values::GenericAtomIdent;
use crate::dom::attr::Attr;
use crate::dom::bindings::root::{LayoutDom, ToLayout};
use crate::dom::element::Element;
use crate::dom::node::node::NodeTraits;
static EMPTY_NAMESPACE: Namespace = ns!();
#[derive(Clone, MallocSizeOf)]
pub(crate) enum AttrName {
Unqualified(LocalName),
Qualified(Box<AttrIdentifier>),
}
impl AttrName {
pub(crate) fn new(
local_name: LocalName,
name: LocalName,
namespace: Namespace,
prefix: Option<Prefix>,
) -> Self {
if prefix.is_none() && name == local_name && namespace == ns!() {
AttrName::Unqualified(local_name)
} else {
AttrName::Qualified(Box::new(AttrIdentifier {
local_name: GenericAtomIdent(local_name),
name: GenericAtomIdent(name),
namespace: GenericAtomIdent(namespace),
prefix: prefix.map(GenericAtomIdent),
}))
}
}
#[inline]
pub(crate) fn local_name(&self) -> &LocalName {
match self {
AttrName::Unqualified(local_name) => local_name,
AttrName::Qualified(identifier) => &identifier.local_name.0,
}
}
#[inline]
pub(crate) fn name(&self) -> &LocalName {
match self {
AttrName::Unqualified(local_name) => local_name,
AttrName::Qualified(identifier) => &identifier.name.0,
}
}
#[inline]
pub(crate) fn namespace(&self) -> &Namespace {
match self {
AttrName::Unqualified(_) => &EMPTY_NAMESPACE,
AttrName::Qualified(identifier) => &identifier.namespace.0,
}
}
#[inline]
pub(crate) fn prefix(&self) -> Option<&Prefix> {
match self {
AttrName::Unqualified(_) => None,
AttrName::Qualified(identifier) => Some(&identifier.prefix.as_ref()?.0),
}
}
pub(crate) fn into_parts(self) -> (LocalName, LocalName, Namespace, Option<Prefix>) {
match self {
AttrName::Unqualified(local_name) => (local_name.clone(), local_name, ns!(), None),
AttrName::Qualified(identifier) => {
let AttrIdentifier {
local_name,
name,
namespace,
prefix,
} = *identifier;
(local_name.0, name.0, namespace.0, prefix.map(|p| p.0))
},
}
}
pub(crate) fn as_identifier(&self) -> AttrIdentifier {
match self {
AttrName::Unqualified(local_name) => AttrIdentifier {
local_name: GenericAtomIdent(local_name.clone()),
name: GenericAtomIdent(local_name.clone()),
namespace: GenericAtomIdent(ns!()),
prefix: None,
},
AttrName::Qualified(identifier) => (**identifier).clone(),
}
}
}
#[derive(MallocSizeOf)]
pub(crate) struct ContentAttributeData {
pub identifier: AttrName,
pub value: AttrValue,
}
impl ContentAttributeData {
#[inline]
pub(crate) fn local_name(&self) -> &LocalName {
self.identifier.local_name()
}
#[inline]
pub(crate) fn name(&self) -> &LocalName {
self.identifier.name()
}
#[inline]
pub(crate) fn namespace(&self) -> &Namespace {
self.identifier.namespace()
}
#[inline]
pub(crate) fn prefix(&self) -> Option<&Prefix> {
self.identifier.prefix()
}
#[inline]
pub(crate) fn value(&self) -> &AttrValue {
&self.value
}
}
pub(crate) enum AttrValueRef<'a> {
Direct(&'a AttrValue),
Borrowed(Ref<'a, AttrValue>),
}
impl Deref for AttrValueRef<'_> {
type Target = AttrValue;
fn deref(&self) -> &AttrValue {
match self {
AttrValueRef::Direct(value) => value,
AttrValueRef::Borrowed(value) => value,
}
}
}
impl AsRef<str> for AttrValueRef<'_> {
fn as_ref(&self) -> &str {
self.deref()
}
}
#[derive(Clone, Copy)]
pub(crate) enum AttrRef<'a> {
Raw(&'a ContentAttributeData),
Dom(&'a Attr),
}
impl<'a> AttrRef<'a> {
#[inline]
pub(crate) fn local_name(&self) -> &'a LocalName {
match self {
AttrRef::Raw(data) => data.local_name(),
AttrRef::Dom(attr) => attr.local_name(),
}
}
#[inline]
pub(crate) fn name(&self) -> &'a LocalName {
match self {
AttrRef::Raw(data) => data.name(),
AttrRef::Dom(attr) => attr.name(),
}
}
#[inline]
pub(crate) fn namespace(&self) -> &'a Namespace {
match self {
AttrRef::Raw(data) => data.namespace(),
AttrRef::Dom(attr) => attr.namespace(),
}
}
#[inline]
pub(crate) fn prefix(&self) -> Option<&'a Prefix> {
match self {
AttrRef::Raw(data) => data.prefix(),
AttrRef::Dom(attr) => attr.prefix(),
}
}
#[inline]
pub(crate) fn value(&self) -> AttrValueRef<'a> {
match self {
AttrRef::Raw(data) => AttrValueRef::Direct(data.value()),
AttrRef::Dom(attr) => AttrValueRef::Borrowed(attr.value()),
}
}
#[inline]
pub(crate) fn as_attr(&self) -> Option<&'a Attr> {
match self {
AttrRef::Dom(attr) => Some(attr),
AttrRef::Raw(_) => None,
}
}
pub(crate) fn to_dom_string(self) -> DOMString {
DOMString::from(&**self.value())
}
pub(crate) fn summarize(&self) -> AttrInfo {
AttrInfo {
namespace: (**self.namespace()).to_owned(),
name: (**self.name()).to_owned(),
value: (**self.value()).to_owned(),
}
}
pub(crate) fn as_identifier(&self) -> AttrIdentifier {
match self {
AttrRef::Raw(data) => data.identifier.as_identifier(),
AttrRef::Dom(attr) => attr.identifier().clone(),
}
}
}
#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
#[derive(MallocSizeOf)]
pub(crate) enum AttributeEntry {
Raw(ContentAttributeData),
Dom(Dom<Attr>),
}
impl AttributeEntry {
#[inline]
pub(crate) fn as_ref(&self) -> AttrRef<'_> {
match self {
AttributeEntry::Raw(data) => AttrRef::Raw(data),
AttributeEntry::Dom(attr) => AttrRef::Dom(attr),
}
}
#[expect(unsafe_code)]
#[inline]
pub(crate) fn value_for_layout(&self) -> &AttrValue {
match self {
AttributeEntry::Raw(data) => &data.value,
AttributeEntry::Dom(attr) => unsafe {
let value: LayoutDom<'_, _> = attr.to_layout();
value
}
.value(),
}
}
#[expect(unsafe_code)]
#[inline]
pub(crate) fn local_name_for_layout(&self) -> &LocalName {
match self {
AttributeEntry::Raw(data) => data.local_name(),
AttributeEntry::Dom(attr) => unsafe {
let value: LayoutDom<'_, _> = attr.to_layout();
value
}
.local_name(),
}
}
#[expect(unsafe_code)]
#[inline]
pub(crate) fn namespace_for_layout(&self) -> &Namespace {
match self {
AttributeEntry::Raw(data) => data.namespace(),
AttributeEntry::Dom(attr) => unsafe {
let value: LayoutDom<'_, _> = attr.to_layout();
value
}
.namespace(),
}
}
}
#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
#[derive(Default, MallocSizeOf)]
pub(crate) struct AttributeStorage(DomRefCell<Vec<AttributeEntry>>);
pub(crate) struct AttributesBorrow<'a>(Ref<'a, Vec<AttributeEntry>>);
impl<'a> AttributesBorrow<'a> {
#[inline]
pub(crate) fn iter(&self) -> impl Iterator<Item = AttrRef<'_>> + '_ {
self.0.iter().map(AttributeEntry::as_ref)
}
#[inline]
pub(crate) fn len(&self) -> usize {
self.0.len()
}
#[inline]
pub(crate) fn is_empty(&self) -> bool {
self.0.is_empty()
}
#[inline]
pub(crate) fn first(&self) -> Option<AttrRef<'_>> {
self.0.first().map(AttributeEntry::as_ref)
}
#[inline]
pub(crate) fn get(&self, index: usize) -> Option<AttrRef<'_>> {
self.0.get(index).map(AttributeEntry::as_ref)
}
}
impl AttributeStorage {
#[inline]
pub(crate) fn borrow(&self) -> AttributesBorrow<'_> {
AttributesBorrow(self.0.borrow())
}
#[expect(unsafe_code)]
#[inline]
pub(crate) unsafe fn borrow_for_layout(&self) -> &Vec<AttributeEntry> {
unsafe { self.0.borrow_for_layout() }
}
#[inline]
pub(crate) fn reserve_exact(&self, additional: usize) {
self.0.borrow_mut().reserve_exact(additional);
}
pub(crate) fn push_raw(&self, data: ContentAttributeData) {
self.0.borrow_mut().push(AttributeEntry::Raw(data));
}
pub(crate) fn push_dom(&self, attr: &Attr) {
self.0
.borrow_mut()
.push(AttributeEntry::Dom(Dom::from_ref(attr)));
}
pub(crate) fn remove(&self, index: usize) -> AttributeEntry {
self.0.borrow_mut().remove(index)
}
#[cfg_attr(crown, expect(crown::unrooted_must_root))]
pub(crate) fn set(&self, index: usize, entry: AttributeEntry) {
self.0.borrow_mut()[index] = entry;
}
#[cfg_attr(crown, expect(crown::unrooted_must_root))]
pub(crate) fn ensure_dom(
&self,
cx: &mut JSContext,
index: usize,
element: &Element,
) -> DomRoot<Attr> {
if let AttributeEntry::Dom(attr) = &self.0.borrow()[index] {
return DomRoot::from_ref(&**attr);
}
let data = {
let mut entries = self.0.borrow_mut();
let placeholder = AttributeEntry::Raw(ContentAttributeData {
identifier: AttrName::Unqualified(html5ever::local_name!("")),
value: AttrValue::String(String::new()),
});
let old = std::mem::replace(&mut entries[index], placeholder);
match old {
AttributeEntry::Raw(data) => data,
_ => unreachable!(),
}
};
let doc = element.owner_document();
let (local_name, name, namespace, prefix) = data.identifier.into_parts();
let attr = Attr::new(
cx,
&doc,
local_name,
data.value,
name,
namespace,
prefix,
Some(element),
);
self.0.borrow_mut()[index] = AttributeEntry::Dom(Dom::from_ref(&*attr));
attr
}
}
#[expect(unsafe_code)]
unsafe impl crate::dom::bindings::trace::JSTraceable for AttributeStorage {
unsafe fn trace(&self, trc: *mut js::jsapi::JSTracer) {
for entry in self.0.borrow().iter() {
if let AttributeEntry::Dom(attr) = entry {
unsafe { js::rust::Trace::trace(attr, trc) };
}
}
}
}