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 value = TrustedTypePolicyFactory::get_trusted_types_compliant_attribute_value(
121                owner.namespace(),
122                owner.local_name(),
123                self.local_name(),
124                Some(self.namespace()),
125                TrustedTypeOrString::String(value),
126                &owner.owner_global(),
127                can_gc,
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, can_gc);
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(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
236#[allow(unsafe_code)]
237pub(crate) trait AttrHelpersForLayout<'dom> {
238    fn value(self) -> &'dom AttrValue;
239    fn as_str(&self) -> &'dom str;
240    fn to_tokens(self) -> Option<&'dom [Atom]>;
241    fn local_name(self) -> &'dom LocalName;
242    fn namespace(self) -> &'dom Namespace;
243}
244
245#[allow(unsafe_code)]
246impl<'dom> AttrHelpersForLayout<'dom> for LayoutDom<'dom, Attr> {
247    #[inline]
248    fn value(self) -> &'dom AttrValue {
249        unsafe { self.unsafe_get().value.borrow_for_layout() }
250    }
251
252    #[inline]
253    fn as_str(&self) -> &'dom str {
254        self.value()
255    }
256
257    #[inline]
258    fn to_tokens(self) -> Option<&'dom [Atom]> {
259        match *self.value() {
260            AttrValue::TokenList(_, ref tokens) => Some(tokens),
261            _ => None,
262        }
263    }
264
265    #[inline]
266    fn local_name(self) -> &'dom LocalName {
267        &self.unsafe_get().identifier.local_name.0
268    }
269
270    #[inline]
271    fn namespace(self) -> &'dom Namespace {
272        &self.unsafe_get().identifier.namespace.0
273    }
274}
275
276pub(crate) fn is_relevant_attribute(namespace: &Namespace, local_name: &LocalName) -> bool {
278    namespace == &ns!() || (namespace == &ns!(xlink) && local_name == &local_name!("href"))
280}
281
282pub(crate) fn is_boolean_attribute(name: &str) -> bool {
284    static BOOLEAN_ATTRIBUTES: LazyLock<[&str; 30]> = LazyLock::new(|| {
291        [
292            "allowfullscreen",
293            "alpha",
294            "async",
295            "autofocus",
296            "autoplay",
297            "checked",
298            "controls",
299            "default",
300            "defer",
301            "disabled",
302            "formnovalidate",
303            "hidden",
304            "inert",
305            "ismap",
306            "itemscope",
307            "loop",
308            "multiple",
309            "muted",
310            "nomodule",
311            "novalidate",
312            "open",
313            "playsinline",
314            "readonly",
315            "required",
316            "reversed",
317            "selected",
318            "shadowrootclonable",
319            "shadowrootcustomelementregistry",
320            "shadowrootdelegatesfocus",
321            "shadowrootserializable",
322        ]
323    });
324
325    BOOLEAN_ATTRIBUTES
326        .iter()
327        .any(|&boolean_attr| boolean_attr.eq_ignore_ascii_case(name))
328}