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;
14use stylo_atoms::Atom;
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::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 #[allow(clippy::too_many_arguments)]
64 pub(crate) fn new(
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 can_gc: CanGc,
73 ) -> DomRoot<Attr> {
74 Node::reflect_node(
75 Box::new(Attr::new_inherited(
76 document, local_name, value, name, namespace, prefix, owner,
77 )),
78 document,
79 can_gc,
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, value: DOMString, can_gc: CanGc) -> Fallible<()> {
114 if let Some(owner) = self.owner() {
116 let original_element = owner.clone();
118 let value = TrustedTypePolicyFactory::get_trusted_types_compliant_attribute_value(
122 owner.namespace(),
123 owner.local_name(),
124 self.local_name(),
125 Some(self.namespace()),
126 TrustedTypeOrString::String(value),
127 &owner.owner_global(),
128 can_gc,
129 )?;
130 if let Some(owner) = self.owner() {
131 if owner != original_element {
133 return Ok(());
134 }
135 let value = owner.parse_attribute(self.namespace(), self.local_name(), value);
137 owner.change_attribute(self, value, can_gc);
138 } else {
139 self.set_value(value);
141 }
142 } else {
143 self.set_value(value);
145 }
146 Ok(())
147 }
148
149 fn Name(&self) -> DOMString {
151 DOMString::from(&**self.name())
153 }
154
155 fn GetNamespaceURI(&self) -> Option<DOMString> {
157 match *self.namespace() {
158 ns!() => None,
159 ref url => Some(DOMString::from(&**url)),
160 }
161 }
162
163 fn GetPrefix(&self) -> Option<DOMString> {
165 self.prefix().map(|p| DOMString::from(&**p))
167 }
168
169 fn GetOwnerElement(&self) -> Option<DomRoot<Element>> {
171 self.owner()
172 }
173
174 fn Specified(&self) -> bool {
176 true }
178}
179
180impl Attr {
181 pub(crate) fn swap_value(&self, value: &mut AttrValue) {
183 mem::swap(&mut *self.value.borrow_mut(), value);
184 }
185
186 pub(crate) fn identifier(&self) -> &AttrIdentifier {
187 &self.identifier
188 }
189
190 pub(crate) fn value(&self) -> Ref<'_, AttrValue> {
191 self.value.borrow()
192 }
193
194 fn set_value(&self, value: DOMString) {
195 *self.value.borrow_mut() = AttrValue::String(value.into());
196 }
197
198 pub(crate) fn local_name(&self) -> &LocalName {
199 &self.identifier.local_name
200 }
201
202 pub(crate) fn set_owner(&self, owner: Option<&Element>) {
205 let ns = self.namespace();
206 match (self.owner(), owner) {
207 (Some(old), None) => {
208 assert!(
210 old.get_attribute(ns, &self.identifier.local_name)
211 .as_deref() !=
212 Some(self)
213 )
214 },
215 (Some(old), Some(new)) => assert_eq!(&*old, new),
216 _ => {},
217 }
218 self.owner.set(owner);
219 }
220
221 pub(crate) fn owner(&self) -> Option<DomRoot<Element>> {
222 self.owner.get()
223 }
224
225 pub(crate) fn summarize(&self) -> AttrInfo {
226 AttrInfo {
227 namespace: (**self.namespace()).to_owned(),
228 name: String::from(self.Name()),
229 value: String::from(self.Value()),
230 }
231 }
232
233 pub(crate) fn qualified_name(&self) -> DOMString {
234 match self.prefix() {
235 Some(ref prefix) => DOMString::from(format!("{}:{}", prefix, &**self.local_name())),
236 None => DOMString::from(&**self.local_name()),
237 }
238 }
239}
240
241#[allow(unsafe_code)]
242pub(crate) trait AttrHelpersForLayout<'dom> {
243 fn value(self) -> &'dom AttrValue;
244 fn as_str(&self) -> &'dom str;
245 fn to_tokens(self) -> Option<&'dom [Atom]>;
246 fn local_name(self) -> &'dom LocalName;
247 fn namespace(self) -> &'dom Namespace;
248}
249
250#[allow(unsafe_code)]
251impl<'dom> AttrHelpersForLayout<'dom> for LayoutDom<'dom, Attr> {
252 #[inline]
253 fn value(self) -> &'dom AttrValue {
254 unsafe { self.unsafe_get().value.borrow_for_layout() }
255 }
256
257 #[inline]
258 fn as_str(&self) -> &'dom str {
259 self.value()
260 }
261
262 #[inline]
263 fn to_tokens(self) -> Option<&'dom [Atom]> {
264 match *self.value() {
265 AttrValue::TokenList(_, ref tokens) => Some(tokens),
266 _ => None,
267 }
268 }
269
270 #[inline]
271 fn local_name(self) -> &'dom LocalName {
272 &self.unsafe_get().identifier.local_name.0
273 }
274
275 #[inline]
276 fn namespace(self) -> &'dom Namespace {
277 &self.unsafe_get().identifier.namespace.0
278 }
279}
280
281pub(crate) fn is_relevant_attribute(namespace: &Namespace, local_name: &LocalName) -> bool {
283 namespace == &ns!() || (namespace == &ns!(xlink) && local_name == &local_name!("href"))
285}
286
287pub(crate) fn is_boolean_attribute(name: &str) -> bool {
289 static BOOLEAN_ATTRIBUTES: LazyLock<[&str; 30]> = LazyLock::new(|| {
296 [
297 "allowfullscreen",
298 "alpha",
299 "async",
300 "autofocus",
301 "autoplay",
302 "checked",
303 "controls",
304 "default",
305 "defer",
306 "disabled",
307 "formnovalidate",
308 "hidden",
309 "inert",
310 "ismap",
311 "itemscope",
312 "loop",
313 "multiple",
314 "muted",
315 "nomodule",
316 "novalidate",
317 "open",
318 "playsinline",
319 "readonly",
320 "required",
321 "reversed",
322 "selected",
323 "shadowrootclonable",
324 "shadowrootcustomelementregistry",
325 "shadowrootdelegatesfocus",
326 "shadowrootserializable",
327 ]
328 });
329
330 BOOLEAN_ATTRIBUTES
331 .iter()
332 .any(|&boolean_attr| boolean_attr.eq_ignore_ascii_case(name))
333}