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 js::context::JSContext;
13use script_bindings::cell::{DomRefCell, Ref};
14use style::attr::{AttrIdentifier, AttrValue};
15use style::values::GenericAtomIdent;
16
17use crate::dom::bindings::codegen::Bindings::AttrBinding::AttrMethods;
18use crate::dom::bindings::codegen::UnionTypes::TrustedHTMLOrTrustedScriptOrTrustedScriptURLOrString as TrustedTypeOrString;
19use crate::dom::bindings::error::Fallible;
20use crate::dom::bindings::root::{DomRoot, LayoutDom, MutNullableDom};
21use crate::dom::bindings::str::DOMString;
22use crate::dom::document::Document;
23use crate::dom::element::Element;
24use crate::dom::node::{Node, NodeTraits};
25use crate::dom::trustedtypes::trustedtypepolicyfactory::TrustedTypePolicyFactory;
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 cx: &mut js::context::JSContext,
65 document: &Document,
66 local_name: LocalName,
67 value: AttrValue,
68 name: LocalName,
69 namespace: Namespace,
70 prefix: Option<Prefix>,
71 owner: Option<&Element>,
72 ) -> DomRoot<Attr> {
73 Node::reflect_node(
74 cx,
75 Box::new(Attr::new_inherited(
76 document, local_name, value, name, namespace, prefix, owner,
77 )),
78 document,
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, cx: &mut JSContext, value: DOMString) -> Fallible<()> {
113 if let Some(owner) = self.owner() {
115 let value = TrustedTypePolicyFactory::get_trusted_types_compliant_attribute_value(
120 cx,
121 owner.namespace(),
122 owner.local_name(),
123 self.local_name(),
124 Some(self.namespace()),
125 TrustedTypeOrString::String(value),
126 &owner.owner_global(),
127 )?;
128 if let Some(owner) = self.owner() {
129 let value = owner.parse_attribute(self.namespace(), self.local_name(), value);
131 owner.change_attribute(cx, self, value);
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, cx: &mut JSContext, owner: Option<&Element>) {
199 let ns = self.namespace();
200 match (self.owner(), owner) {
201 (Some(old), None) => {
202 assert!(
204 old.get_attribute_with_namespace(cx, 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
235#[expect(unsafe_code)]
236impl<'dom> LayoutDom<'dom, Attr> {
237 #[inline]
238 pub(crate) fn value(self) -> &'dom AttrValue {
239 unsafe { self.unsafe_get().value.borrow_for_layout() }
240 }
241
242 #[inline]
243 pub(crate) fn local_name(self) -> &'dom LocalName {
244 &self.unsafe_get().identifier.local_name.0
245 }
246
247 #[inline]
248 pub(crate) fn namespace(self) -> &'dom Namespace {
249 &self.unsafe_get().identifier.namespace.0
250 }
251}
252
253pub(crate) fn is_relevant_attribute(namespace: &Namespace, local_name: &LocalName) -> bool {
255 namespace == &ns!() || (namespace == &ns!(xlink) && local_name == &local_name!("href"))
257}
258
259pub(crate) fn is_boolean_attribute(name: &str) -> bool {
261 static BOOLEAN_ATTRIBUTES: LazyLock<[&str; 30]> = LazyLock::new(|| {
268 [
269 "allowfullscreen",
270 "alpha",
271 "async",
272 "autofocus",
273 "autoplay",
274 "checked",
275 "controls",
276 "default",
277 "defer",
278 "disabled",
279 "formnovalidate",
280 "hidden",
281 "inert",
282 "ismap",
283 "itemscope",
284 "loop",
285 "multiple",
286 "muted",
287 "nomodule",
288 "novalidate",
289 "open",
290 "playsinline",
291 "readonly",
292 "required",
293 "reversed",
294 "selected",
295 "shadowrootclonable",
296 "shadowrootcustomelementregistry",
297 "shadowrootdelegatesfocus",
298 "shadowrootserializable",
299 ]
300 });
301
302 BOOLEAN_ATTRIBUTES
303 .iter()
304 .any(|&boolean_attr| boolean_attr.eq_ignore_ascii_case(name))
305}