use html5ever::{LocalName, Namespace, local_name, ns};
use js::context::JSContext;
use servo_arc::Arc as ServoArc;
use style::attr::AttrValue;
use stylo_atoms::Atom;
use crate::dom::bindings::codegen::UnionTypes::{TrustedHTMLOrString, TrustedScriptURLOrUSVString};
use crate::dom::bindings::str::{DOMString, USVString};
use crate::dom::element::attributes::storage::AttrRef;
use crate::dom::element::{AttributeMutationReason, Element};
use crate::dom::node::NodeTraits;
impl Element {
pub(crate) fn get_attribute_string_value(&self, local_name: &LocalName) -> Option<String> {
debug_assert_eq!(
*local_name,
local_name.to_ascii_lowercase(),
"All namespace-less attribute accesses should use a lowercase ASCII name"
);
self.get_attribute_string_value_with_namespace(&ns!(), local_name)
}
pub(crate) fn get_attribute_string_value_with_namespace(
&self,
namespace: &Namespace,
local_name: &LocalName,
) -> Option<String> {
self.with_attribute(namespace, local_name, |attribute| {
String::from(&**attribute.value())
})
}
pub(crate) fn get_int_attribute(&self, local_name: &LocalName, default: i32) -> i32 {
self.with_attribute(&ns!(), local_name, |attribute| {
if let AttrValue::Int(_, value) = *attribute.value() {
value
} else {
unreachable!("Expected an AttrValue::Int: implement parse_plain_attribute")
}
})
.unwrap_or(default)
}
pub(crate) fn set_atomic_attribute(
&self,
cx: &mut JSContext,
local_name: &LocalName,
value: DOMString,
) {
self.set_attribute(cx, local_name, AttrValue::from_atomic(value.into()));
}
pub(crate) fn set_bool_attribute(
&self,
cx: &mut JSContext,
local_name: &LocalName,
value: bool,
) {
if self.has_attribute(local_name) == value {
return;
}
if value {
self.set_string_attribute(cx, local_name, DOMString::new());
} else {
self.remove_attribute(cx, &ns!(), local_name);
}
}
pub(crate) fn get_url_attribute(&self, local_name: &LocalName) -> USVString {
let Some(value) = self.get_attribute_string_value(local_name) else {
return Default::default();
};
self.owner_document()
.encoding_parse_a_url(&value)
.map(|parsed| USVString(parsed.into_string()))
.unwrap_or_else(|_| USVString(value))
}
pub(crate) fn set_url_attribute(
&self,
cx: &mut JSContext,
local_name: &LocalName,
value: USVString,
) {
self.set_attribute(cx, local_name, AttrValue::String(value.into()));
}
pub(crate) fn get_trusted_type_url_attribute(
&self,
local_name: &LocalName,
) -> TrustedScriptURLOrUSVString {
let Some(value) = self.get_attribute_string_value(local_name) else {
return TrustedScriptURLOrUSVString::USVString(USVString::default());
};
self.owner_document()
.encoding_parse_a_url(&value)
.map(|parsed| TrustedScriptURLOrUSVString::USVString(USVString(parsed.into_string())))
.unwrap_or_else(|_| TrustedScriptURLOrUSVString::USVString(USVString(value)))
}
pub(crate) fn get_trusted_html_attribute(&self, local_name: &LocalName) -> TrustedHTMLOrString {
TrustedHTMLOrString::String(self.get_string_attribute(local_name))
}
pub(crate) fn get_string_attribute(&self, local_name: &LocalName) -> DOMString {
self.get_attribute_string_value(local_name)
.map(|value| value.into())
.unwrap_or_default()
}
pub(crate) fn set_string_attribute(
&self,
cx: &mut JSContext,
local_name: &LocalName,
value: DOMString,
) {
self.set_attribute(cx, local_name, value.str().to_string().into());
}
pub(crate) fn get_nullable_string_attribute(
&self,
local_name: &LocalName,
) -> Option<DOMString> {
if self.has_attribute(local_name) {
Some(self.get_string_attribute(local_name))
} else {
None
}
}
pub(crate) fn set_nullable_string_attribute(
&self,
cx: &mut JSContext,
local_name: &LocalName,
value: Option<DOMString>,
) {
match value {
Some(val) => {
self.set_string_attribute(cx, local_name, val);
},
None => {
self.remove_attribute(cx, &ns!(), local_name);
},
}
}
pub(crate) fn any_tokenlist_attribute(
&self,
local_name: &LocalName,
f: impl Fn(&Atom) -> bool,
) -> bool {
self.with_attribute(&ns!(), local_name, |attribute| {
attribute.value().as_tokens().iter().any(f)
})
.unwrap_or(false)
}
pub(crate) fn get_tokenlist_attribute(&self, local_name: &LocalName) -> Vec<Atom> {
self.with_attribute(&ns!(), local_name, |attribute| {
attribute.value().as_tokens().to_vec()
})
.unwrap_or_default()
}
pub(crate) fn set_tokenlist_attribute(
&self,
cx: &mut JSContext,
local_name: &LocalName,
value: DOMString,
) {
self.set_attribute(
cx,
local_name,
AttrValue::from_serialized_tokenlist(value.into()),
);
}
pub(crate) fn get_uint_attribute(&self, local_name: &LocalName, default: u32) -> u32 {
self.with_attribute(&ns!(), local_name, |attribute| {
if let AttrValue::UInt(_, value) = *attribute.value() {
value
} else {
unreachable!("Expected an AttrValue::Int: implement parse_plain_attribute")
}
})
.unwrap_or(default)
}
fn compute_attribute_value_with_style_fast_path(&self, attr: AttrRef<'_>) -> AttrValue {
if *attr.local_name() == local_name!("style") {
let document = self.owner_document();
if let AttrValue::Declaration {
block,
lock,
serialization,
} = &*attr.value()
{
let cloned_block = block.read_with(&lock.read()).clone();
return AttrValue::Declaration {
block: ServoArc::new(lock.wrap(cloned_block)),
lock: lock.clone(),
serialization: serialization.clone(),
};
}
if let Some(ref pdb) = *self.style_attribute().borrow() {
let shared_lock = document.style_shared_author_lock();
let new_pdb = pdb.read_with(&shared_lock.read()).clone();
return AttrValue::Declaration {
block: ServoArc::new(shared_lock.wrap(new_pdb)),
lock: shared_lock.clone(),
serialization: (**attr.value()).to_owned().into(),
};
}
}
attr.value().clone()
}
pub(crate) fn copy_all_attributes_to_other_element(
&self,
cx: &mut JSContext,
target_element: &Element,
) {
for attr in self.attrs().borrow().iter() {
let new_value = self.compute_attribute_value_with_style_fast_path(attr);
target_element.push_new_attribute(
cx,
attr.local_name().clone(),
new_value,
attr.name().clone(),
attr.namespace().clone(),
attr.prefix().cloned(),
AttributeMutationReason::ByCloning,
);
}
}
}