1use std::borrow::ToOwned;
6use std::mem;
7use std::sync::LazyLock;
8
9use devtools_traits::AttrInfo;
10use dom_struct::dom_struct;
11use html5ever::{LocalName, Namespace, Prefix, local_name, ns};
12use style::attr::{AttrIdentifier, AttrValue};
13use style::values::GenericAtomIdent;
14
15use crate::dom::bindings::cell::{DomRefCell, Ref};
16use crate::dom::bindings::codegen::Bindings::AttrBinding::AttrMethods;
17use crate::dom::bindings::codegen::UnionTypes::TrustedHTMLOrTrustedScriptOrTrustedScriptURLOrString as TrustedTypeOrString;
18use crate::dom::bindings::error::Fallible;
19use crate::dom::bindings::root::{DomRoot, LayoutDom, MutNullableDom};
20use crate::dom::bindings::str::DOMString;
21use crate::dom::document::Document;
22use crate::dom::element::Element;
23use crate::dom::node::{Node, NodeTraits};
24use crate::dom::trustedtypepolicyfactory::TrustedTypePolicyFactory;
25use crate::script_runtime::CanGc;
26
27#[dom_struct]
29pub(crate) struct Attr {
30 node_: Node,
31 #[no_trace]
32 identifier: AttrIdentifier,
33 #[no_trace]
34 value: DomRefCell<AttrValue>,
35
36 owner: MutNullableDom<Element>,
38}
39
40impl Attr {
41 fn new_inherited(
42 document: &Document,
43 local_name: LocalName,
44 value: AttrValue,
45 name: LocalName,
46 namespace: Namespace,
47 prefix: Option<Prefix>,
48 owner: Option<&Element>,
49 ) -> Attr {
50 Attr {
51 node_: Node::new_inherited(document),
52 identifier: AttrIdentifier {
53 local_name: GenericAtomIdent(local_name),
54 name: GenericAtomIdent(name),
55 namespace: GenericAtomIdent(namespace),
56 prefix: prefix.map(GenericAtomIdent),
57 },
58 value: DomRefCell::new(value),
59 owner: MutNullableDom::new(owner),
60 }
61 }
62 #[expect(clippy::too_many_arguments)]
63 pub(crate) fn new(
64 document: &Document,
65 local_name: LocalName,
66 value: AttrValue,
67 name: LocalName,
68 namespace: Namespace,
69 prefix: Option<Prefix>,
70 owner: Option<&Element>,
71 can_gc: CanGc,
72 ) -> DomRoot<Attr> {
73 Node::reflect_node(
74 Box::new(Attr::new_inherited(
75 document, local_name, value, name, namespace, prefix, owner,
76 )),
77 document,
78 can_gc,
79 )
80 }
81
82 #[inline]
83 pub(crate) fn name(&self) -> &LocalName {
84 &self.identifier.name.0
85 }
86
87 #[inline]
88 pub(crate) fn namespace(&self) -> &Namespace {
89 &self.identifier.namespace.0
90 }
91
92 #[inline]
93 pub(crate) fn prefix(&self) -> Option<&Prefix> {
94 Some(&self.identifier.prefix.as_ref()?.0)
95 }
96}
97
98impl AttrMethods<crate::DomTypeHolder> for Attr {
99 fn LocalName(&self) -> DOMString {
101 DOMString::from(&**self.local_name())
103 }
104
105 fn Value(&self) -> DOMString {
107 DOMString::from(&**self.value())
109 }
110
111 fn SetValue(&self, value: DOMString, can_gc: CanGc) -> Fallible<()> {
113 if let Some(owner) = self.owner() {
115 let value = TrustedTypePolicyFactory::get_trusted_types_compliant_attribute_value(
120 owner.namespace(),
121 owner.local_name(),
122 self.local_name(),
123 Some(self.namespace()),
124 TrustedTypeOrString::String(value),
125 &owner.owner_global(),
126 can_gc,
127 )?;
128 if let Some(owner) = self.owner() {
129 let value = owner.parse_attribute(self.namespace(), self.local_name(), value);
131 owner.change_attribute(self, value, can_gc);
132 } else {
133 self.set_value(value);
135 }
136 } else {
137 self.set_value(value);
139 }
140 Ok(())
141 }
142
143 fn Name(&self) -> DOMString {
145 DOMString::from(&**self.name())
147 }
148
149 fn GetNamespaceURI(&self) -> Option<DOMString> {
151 match *self.namespace() {
152 ns!() => None,
153 ref url => Some(DOMString::from(&**url)),
154 }
155 }
156
157 fn GetPrefix(&self) -> Option<DOMString> {
159 self.prefix().map(|p| DOMString::from(&**p))
161 }
162
163 fn GetOwnerElement(&self) -> Option<DomRoot<Element>> {
165 self.owner()
166 }
167
168 fn Specified(&self) -> bool {
170 true }
172}
173
174impl Attr {
175 pub(crate) fn swap_value(&self, value: &mut AttrValue) {
177 mem::swap(&mut *self.value.borrow_mut(), value);
178 }
179
180 pub(crate) fn identifier(&self) -> &AttrIdentifier {
181 &self.identifier
182 }
183
184 pub(crate) fn value(&self) -> Ref<'_, AttrValue> {
185 self.value.borrow()
186 }
187
188 fn set_value(&self, value: DOMString) {
189 *self.value.borrow_mut() = AttrValue::String(value.into());
190 }
191
192 pub(crate) fn local_name(&self) -> &LocalName {
193 &self.identifier.local_name
194 }
195
196 pub(crate) fn set_owner(&self, owner: Option<&Element>) {
199 let ns = self.namespace();
200 match (self.owner(), owner) {
201 (Some(old), None) => {
202 assert!(
204 old.get_attribute(ns, &self.identifier.local_name)
205 .as_deref() !=
206 Some(self)
207 )
208 },
209 (Some(old), Some(new)) => assert_eq!(&*old, new),
210 _ => {},
211 }
212 self.owner.set(owner);
213 }
214
215 pub(crate) fn owner(&self) -> Option<DomRoot<Element>> {
216 self.owner.get()
217 }
218
219 pub(crate) fn summarize(&self) -> AttrInfo {
220 AttrInfo {
221 namespace: (**self.namespace()).to_owned(),
222 name: (**self.name()).to_owned(),
223 value: (**self.value()).to_owned(),
224 }
225 }
226
227 pub(crate) fn qualified_name(&self) -> DOMString {
228 match self.prefix() {
229 Some(ref prefix) => DOMString::from(format!("{}:{}", prefix, &**self.local_name())),
230 None => DOMString::from(&**self.local_name()),
231 }
232 }
233}
234
235pub(crate) trait AttrHelpersForLayout<'dom> {
236 fn value(self) -> &'dom AttrValue;
237 fn local_name(self) -> &'dom LocalName;
238 fn namespace(self) -> &'dom Namespace;
239}
240
241#[expect(unsafe_code)]
242impl<'dom> AttrHelpersForLayout<'dom> for LayoutDom<'dom, Attr> {
243 #[inline]
244 fn value(self) -> &'dom AttrValue {
245 unsafe { self.unsafe_get().value.borrow_for_layout() }
246 }
247
248 #[inline]
249 fn local_name(self) -> &'dom LocalName {
250 &self.unsafe_get().identifier.local_name.0
251 }
252
253 #[inline]
254 fn namespace(self) -> &'dom Namespace {
255 &self.unsafe_get().identifier.namespace.0
256 }
257}
258
259pub(crate) fn is_relevant_attribute(namespace: &Namespace, local_name: &LocalName) -> bool {
261 namespace == &ns!() || (namespace == &ns!(xlink) && local_name == &local_name!("href"))
263}
264
265pub(crate) fn is_boolean_attribute(name: &str) -> bool {
267 static BOOLEAN_ATTRIBUTES: LazyLock<[&str; 30]> = LazyLock::new(|| {
274 [
275 "allowfullscreen",
276 "alpha",
277 "async",
278 "autofocus",
279 "autoplay",
280 "checked",
281 "controls",
282 "default",
283 "defer",
284 "disabled",
285 "formnovalidate",
286 "hidden",
287 "inert",
288 "ismap",
289 "itemscope",
290 "loop",
291 "multiple",
292 "muted",
293 "nomodule",
294 "novalidate",
295 "open",
296 "playsinline",
297 "readonly",
298 "required",
299 "reversed",
300 "selected",
301 "shadowrootclonable",
302 "shadowrootcustomelementregistry",
303 "shadowrootdelegatesfocus",
304 "shadowrootserializable",
305 ]
306 });
307
308 BOOLEAN_ATTRIBUTES
309 .iter()
310 .any(|&boolean_attr| boolean_attr.eq_ignore_ascii_case(name))
311}