script/dom/element/attributes/
attr.rs1use 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 script_bindings::domstring::TracedDOMString;
13use style::attr::{AttrIdentifier, AttrValue};
14use style::values::GenericAtomIdent;
15
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::trustedtypes::trustedtypepolicyfactory::TrustedTypePolicyFactory;
25
26#[dom_struct]
28pub(crate) struct Attr {
29 node_: Node,
30 #[no_trace]
31 identifier: AttrIdentifier,
32 #[no_trace]
33 value: DomRefCell<AttrValue>,
34
35 owner: MutNullableDom<Element>,
37}
38
39impl Attr {
40 fn new_inherited(
41 document: &Document,
42 local_name: LocalName,
43 value: AttrValue,
44 name: LocalName,
45 namespace: Namespace,
46 prefix: Option<Prefix>,
47 owner: Option<&Element>,
48 ) -> Attr {
49 Attr {
50 node_: Node::new_inherited(document),
51 identifier: AttrIdentifier {
52 local_name: GenericAtomIdent(local_name),
53 name: GenericAtomIdent(name),
54 namespace: GenericAtomIdent(namespace),
55 prefix: prefix.map(GenericAtomIdent),
56 },
57 value: DomRefCell::new(value),
58 owner: MutNullableDom::new(owner),
59 }
60 }
61 #[expect(clippy::too_many_arguments)]
62 pub(crate) fn new(
63 cx: &mut js::context::JSContext,
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 ) -> DomRoot<Attr> {
72 Node::reflect_node(
73 cx,
74 Box::new(Attr::new_inherited(
75 document, local_name, value, name, namespace, prefix, owner,
76 )),
77 document,
78 )
79 }
80
81 #[inline]
82 pub(crate) fn name(&self) -> &LocalName {
83 &self.identifier.name.0
84 }
85
86 #[inline]
87 pub(crate) fn namespace(&self) -> &Namespace {
88 &self.identifier.namespace.0
89 }
90
91 #[inline]
92 pub(crate) fn prefix(&self) -> Option<&Prefix> {
93 Some(&self.identifier.prefix.as_ref()?.0)
94 }
95}
96
97impl AttrMethods<crate::DomTypeHolder> for Attr {
98 fn LocalName(&self) -> DOMString {
100 TracedDOMString::from(self.local_name().clone()).root()
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 TracedDOMString::from(self.name().clone()).root()
144 }
145
146 fn GetNamespaceURI(&self) -> Option<DOMString> {
148 match *self.namespace() {
149 ns!() => None,
150 ref url => Some(TracedDOMString::from(url.clone()).root()),
151 }
152 }
153
154 fn GetPrefix(&self) -> Option<DOMString> {
156 self.prefix()
157 .map(|p| TracedDOMString::from(p.clone()).root())
158 }
159
160 fn GetOwnerElement(&self) -> Option<DomRoot<Element>> {
162 self.owner()
163 }
164
165 fn Specified(&self) -> bool {
167 true }
169}
170
171impl Attr {
172 pub(crate) fn swap_value(&self, value: &mut AttrValue) {
174 mem::swap(&mut *self.value.borrow_mut(), value);
175 }
176
177 pub(crate) fn identifier(&self) -> &AttrIdentifier {
178 &self.identifier
179 }
180
181 pub(crate) fn value(&self) -> Ref<'_, AttrValue> {
182 self.value.borrow()
183 }
184
185 fn set_value(&self, value: DOMString) {
186 *self.value.borrow_mut() = AttrValue::String(value.into());
187 }
188
189 pub(crate) fn local_name(&self) -> &LocalName {
190 &self.identifier.local_name
191 }
192
193 pub(crate) fn set_owner(&self, cx: &mut JSContext, owner: Option<&Element>) {
196 let ns = self.namespace();
197 match (self.owner(), owner) {
198 (Some(old), None) => {
199 assert!(
201 old.get_attribute_with_namespace(cx, ns, &self.identifier.local_name)
202 .as_deref() !=
203 Some(self)
204 )
205 },
206 (Some(old), Some(new)) => assert_eq!(&*old, new),
207 _ => {},
208 }
209 self.owner.set(owner);
210 }
211
212 pub(crate) fn owner(&self) -> Option<DomRoot<Element>> {
213 self.owner.get()
214 }
215
216 pub(crate) fn qualified_name(&self) -> DOMString {
217 match self.prefix() {
218 Some(ref prefix) => DOMString::from(format!("{}:{}", prefix, &**self.local_name())),
219 None => TracedDOMString::from(self.local_name().clone()).root(),
220 }
221 }
222}
223
224#[expect(unsafe_code)]
225impl<'dom> LayoutDom<'dom, Attr> {
226 #[inline]
227 pub(crate) fn value(self) -> &'dom AttrValue {
228 unsafe { self.unsafe_get().value.borrow_for_layout() }
229 }
230
231 #[inline]
232 pub(crate) fn local_name(self) -> &'dom LocalName {
233 &self.unsafe_get().identifier.local_name.0
234 }
235
236 #[inline]
237 pub(crate) fn namespace(self) -> &'dom Namespace {
238 &self.unsafe_get().identifier.namespace.0
239 }
240}
241
242pub(crate) fn is_relevant_attribute(namespace: &Namespace, local_name: &LocalName) -> bool {
244 namespace == &ns!() || (namespace == &ns!(xlink) && local_name == &local_name!("href"))
246}
247
248pub(crate) fn is_boolean_attribute(name: &str) -> bool {
250 static BOOLEAN_ATTRIBUTES: LazyLock<[&str; 30]> = LazyLock::new(|| {
257 [
258 "allowfullscreen",
259 "alpha",
260 "async",
261 "autofocus",
262 "autoplay",
263 "checked",
264 "controls",
265 "default",
266 "defer",
267 "disabled",
268 "formnovalidate",
269 "hidden",
270 "inert",
271 "ismap",
272 "itemscope",
273 "loop",
274 "multiple",
275 "muted",
276 "nomodule",
277 "novalidate",
278 "open",
279 "playsinline",
280 "readonly",
281 "required",
282 "reversed",
283 "selected",
284 "shadowrootclonable",
285 "shadowrootcustomelementregistry",
286 "shadowrootdelegatesfocus",
287 "shadowrootserializable",
288 ]
289 });
290
291 BOOLEAN_ATTRIBUTES
292 .iter()
293 .any(|&boolean_attr| boolean_attr.eq_ignore_ascii_case(name))
294}