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