Skip to main content

script/dom/element/attributes/
accessors.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5use html5ever::{LocalName, local_name, ns};
6use js::context::JSContext;
7use servo_arc::Arc as ServoArc;
8use style::attr::AttrValue;
9use stylo_atoms::Atom;
10
11use crate::dom::bindings::codegen::Bindings::AttrBinding::AttrMethods;
12use crate::dom::bindings::codegen::UnionTypes::{TrustedHTMLOrString, TrustedScriptURLOrUSVString};
13use crate::dom::bindings::str::{DOMString, USVString};
14use crate::dom::element::attributes::storage::AttrRef;
15use crate::dom::element::{AttributeMutationReason, Element};
16use crate::dom::node::NodeTraits;
17
18impl Element {
19    pub(crate) fn get_int_attribute(&self, local_name: &LocalName, default: i32) -> i32 {
20        match self.get_attribute(local_name) {
21            Some(ref attribute) => match *attribute.value() {
22                AttrValue::Int(_, value) => value,
23                _ => unreachable!("Expected an AttrValue::Int: implement parse_plain_attribute"),
24            },
25            None => default,
26        }
27    }
28
29    pub(crate) fn set_atomic_attribute(
30        &self,
31        cx: &mut JSContext,
32        local_name: &LocalName,
33        value: DOMString,
34    ) {
35        self.set_attribute(cx, local_name, AttrValue::from_atomic(value.into()));
36    }
37
38    pub(crate) fn set_bool_attribute(
39        &self,
40        cx: &mut JSContext,
41        local_name: &LocalName,
42        value: bool,
43    ) {
44        if self.has_attribute(local_name) == value {
45            return;
46        }
47        if value {
48            self.set_string_attribute(cx, local_name, DOMString::new());
49        } else {
50            self.remove_attribute(cx, &ns!(), local_name);
51        }
52    }
53
54    pub(crate) fn get_url_attribute(&self, local_name: &LocalName) -> USVString {
55        let Some(attribute) = self.get_attribute(local_name) else {
56            return Default::default();
57        };
58        let value = &**attribute.value();
59        self.owner_document()
60            .encoding_parse_a_url(value)
61            .map(|parsed| USVString(parsed.into_string()))
62            .unwrap_or_else(|_| USVString(value.to_owned()))
63    }
64
65    pub(crate) fn set_url_attribute(
66        &self,
67        cx: &mut JSContext,
68        local_name: &LocalName,
69        value: USVString,
70    ) {
71        self.set_attribute(cx, local_name, AttrValue::String(value.to_string()));
72    }
73
74    pub(crate) fn get_trusted_type_url_attribute(
75        &self,
76        local_name: &LocalName,
77    ) -> TrustedScriptURLOrUSVString {
78        let Some(attribute) = self.get_attribute(local_name) else {
79            return TrustedScriptURLOrUSVString::USVString(USVString::default());
80        };
81        let value = &**attribute.value();
82        self.owner_document()
83            .encoding_parse_a_url(value)
84            .map(|parsed| TrustedScriptURLOrUSVString::USVString(USVString(parsed.into_string())))
85            .unwrap_or_else(|_| TrustedScriptURLOrUSVString::USVString(USVString(value.to_owned())))
86    }
87
88    pub(crate) fn get_trusted_html_attribute(&self, local_name: &LocalName) -> TrustedHTMLOrString {
89        TrustedHTMLOrString::String(self.get_string_attribute(local_name))
90    }
91
92    pub(crate) fn get_string_attribute(&self, local_name: &LocalName) -> DOMString {
93        self.get_attribute(local_name)
94            .map(|attribute| attribute.Value())
95            .unwrap_or_default()
96    }
97
98    pub(crate) fn set_string_attribute(
99        &self,
100        cx: &mut JSContext,
101        local_name: &LocalName,
102        value: DOMString,
103    ) {
104        self.set_attribute(cx, local_name, AttrValue::String(value.into()));
105    }
106
107    /// Used for string attribute reflections where absence of the attribute returns `null`,
108    /// e.g. `element.ariaLabel` returning `null` when the `aria-label` attribute is absent.
109    pub(crate) fn get_nullable_string_attribute(
110        &self,
111        local_name: &LocalName,
112    ) -> Option<DOMString> {
113        if self.has_attribute(local_name) {
114            Some(self.get_string_attribute(local_name))
115        } else {
116            None
117        }
118    }
119
120    /// Used for string attribute reflections where setting `null`/`undefined` removes the
121    /// attribute, e.g. `element.ariaLabel = null` removing the `aria-label` attribute.
122    pub(crate) fn set_nullable_string_attribute(
123        &self,
124        cx: &mut JSContext,
125        local_name: &LocalName,
126        value: Option<DOMString>,
127    ) {
128        match value {
129            Some(val) => {
130                self.set_string_attribute(cx, local_name, val);
131            },
132            None => {
133                self.remove_attribute(cx, &ns!(), local_name);
134            },
135        }
136    }
137
138    pub(crate) fn get_tokenlist_attribute(&self, local_name: &LocalName) -> Vec<Atom> {
139        self.get_attribute(local_name)
140            .map(|attribute| attribute.value().as_tokens().to_vec())
141            .unwrap_or_default()
142    }
143
144    pub(crate) fn set_tokenlist_attribute(
145        &self,
146        cx: &mut JSContext,
147        local_name: &LocalName,
148        value: DOMString,
149    ) {
150        self.set_attribute(
151            cx,
152            local_name,
153            AttrValue::from_serialized_tokenlist(value.into()),
154        );
155    }
156
157    pub(crate) fn set_atomic_tokenlist_attribute(
158        &self,
159        cx: &mut JSContext,
160        local_name: &LocalName,
161        tokens: Vec<Atom>,
162    ) {
163        self.set_attribute(cx, local_name, AttrValue::from_atomic_tokens(tokens));
164    }
165
166    pub(crate) fn set_int_attribute(&self, cx: &mut JSContext, local_name: &LocalName, value: i32) {
167        self.set_attribute(cx, local_name, AttrValue::Int(value.to_string(), value));
168    }
169
170    pub(crate) fn get_uint_attribute(&self, local_name: &LocalName, default: u32) -> u32 {
171        match self.get_attribute(local_name) {
172            Some(ref attribute) => match *attribute.value() {
173                AttrValue::UInt(_, value) => value,
174                _ => unreachable!("Expected an AttrValue::UInt: implement parse_plain_attribute"),
175            },
176            None => default,
177        }
178    }
179
180    pub(crate) fn set_uint_attribute(
181        &self,
182        cx: &mut JSContext,
183        local_name: &LocalName,
184        value: u32,
185    ) {
186        self.set_attribute(cx, local_name, AttrValue::UInt(value.to_string(), value));
187    }
188
189    /// Ensure that for styles, we clone the already-parsed property declaration block.
190    /// This does two things:
191    /// 1. It uses the same fast-path as CSSStyleDeclaration
192    /// 2. It also avoids the CSP checks when cloning (it shouldn't run any when cloning
193    ///    existing valid attributes)
194    fn compute_attribute_value_with_style_fast_path(&self, attr: AttrRef<'_>) -> AttrValue {
195        if *attr.local_name() == local_name!("style") {
196            if let Some(ref pdb) = *self.style_attribute().borrow() {
197                let document = self.owner_document();
198                let shared_lock = document.style_shared_lock();
199                let new_pdb = pdb.read_with(&shared_lock.read()).clone();
200                return AttrValue::Declaration(
201                    (**attr.value()).to_owned(),
202                    ServoArc::new(shared_lock.wrap(new_pdb)),
203                );
204            }
205        }
206
207        attr.value().clone()
208    }
209
210    /// <https://dom.spec.whatwg.org/#concept-node-clone>
211    pub(crate) fn copy_all_attributes_to_other_element(
212        &self,
213        cx: &mut JSContext,
214        target_element: &Element,
215    ) {
216        // Step 2.5. For each attribute of node’s attribute list:
217        for attr in self.attrs().borrow().iter() {
218            // Step 2.5.1. Let copyAttribute be the result of cloning a single node given attribute, document, and null.
219            let new_value = self.compute_attribute_value_with_style_fast_path(attr);
220            // Step 2.5.2. Append copyAttribute to copy.
221            target_element.push_new_attribute(
222                cx,
223                attr.local_name().clone(),
224                new_value,
225                attr.name().clone(),
226                attr.namespace().clone(),
227                attr.prefix().cloned(),
228                AttributeMutationReason::ByCloning,
229            );
230        }
231    }
232}