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