Skip to main content

script/dom/
formdata.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 dom_struct::dom_struct;
6use html5ever::LocalName;
7use js::rust::HandleObject;
8use script_bindings::cell::DomRefCell;
9use script_bindings::reflector::{Reflector, reflect_dom_object_with_proto};
10use servo_constellation_traits::BlobImpl;
11
12use super::bindings::trace::NoTrace;
13use crate::dom::bindings::codegen::Bindings::FormDataBinding::FormDataMethods;
14use crate::dom::bindings::codegen::UnionTypes::FileOrUSVString;
15use crate::dom::bindings::error::{Error, Fallible};
16use crate::dom::bindings::inheritance::Castable;
17use crate::dom::bindings::iterable::Iterable;
18use crate::dom::bindings::reflector::DomGlobal;
19use crate::dom::bindings::root::DomRoot;
20use crate::dom::bindings::str::{DOMString, USVString};
21use crate::dom::blob::Blob;
22use crate::dom::file::File;
23use crate::dom::globalscope::GlobalScope;
24use crate::dom::html::htmlbuttonelement::HTMLButtonElement;
25use crate::dom::html::htmlelement::HTMLElement;
26use crate::dom::html::htmlformelement::{
27    FormDatum, FormDatumValue, FormSubmitterElement, HTMLFormElement,
28};
29use crate::dom::html::input_element::HTMLInputElement;
30use crate::script_runtime::CanGc;
31
32#[dom_struct]
33pub(crate) struct FormData {
34    reflector_: Reflector,
35    data: DomRefCell<Vec<(NoTrace<LocalName>, FormDatum)>>,
36}
37
38impl FormData {
39    fn new_inherited(form_datums: Option<Vec<FormDatum>>) -> FormData {
40        let data = match form_datums {
41            Some(data) => data
42                .iter()
43                .map(|datum| (NoTrace(LocalName::from(&datum.name)), datum.clone()))
44                .collect::<Vec<(NoTrace<LocalName>, FormDatum)>>(),
45            None => Vec::new(),
46        };
47
48        FormData {
49            reflector_: Reflector::new(),
50            data: DomRefCell::new(data),
51        }
52    }
53
54    pub(crate) fn new(
55        form_datums: Option<Vec<FormDatum>>,
56        global: &GlobalScope,
57        can_gc: CanGc,
58    ) -> DomRoot<FormData> {
59        Self::new_with_proto(form_datums, global, None, can_gc)
60    }
61
62    fn new_with_proto(
63        form_datums: Option<Vec<FormDatum>>,
64        global: &GlobalScope,
65        proto: Option<HandleObject>,
66        can_gc: CanGc,
67    ) -> DomRoot<FormData> {
68        reflect_dom_object_with_proto(
69            Box::new(FormData::new_inherited(form_datums)),
70            global,
71            proto,
72            can_gc,
73        )
74    }
75}
76
77impl FormDataMethods<crate::DomTypeHolder> for FormData {
78    /// <https://xhr.spec.whatwg.org/#dom-formdata>
79    fn Constructor<'a>(
80        global: &GlobalScope,
81        proto: Option<HandleObject>,
82        can_gc: CanGc,
83        form: Option<&'a HTMLFormElement>,
84        submitter: Option<&'a HTMLElement>,
85    ) -> Fallible<DomRoot<FormData>> {
86        // Helper to validate the submitter
87        fn validate_submitter<'b>(
88            submitter: &'b HTMLElement,
89            form: &'b HTMLFormElement,
90        ) -> Result<FormSubmitterElement<'b>, Error> {
91            let submit_button = submitter
92                .downcast::<HTMLButtonElement>()
93                .map(FormSubmitterElement::Button)
94                .or_else(|| {
95                    submitter
96                        .downcast::<HTMLInputElement>()
97                        .map(FormSubmitterElement::Input)
98                })
99                .ok_or(Error::Type(
100                    c"submitter is not a form submitter element".to_owned(),
101                ))?;
102
103            // Step 1.1.1. If submitter is not a submit button, then throw a TypeError.
104            if !submit_button.is_submit_button() {
105                return Err(Error::Type(c"submitter is not a submit button".to_owned()));
106            }
107
108            // Step 1.1.2. If submitter’s form owner is not form, then throw a "NotFoundError"
109            // DOMException.
110            if !matches!(submit_button.form_owner(), Some(owner) if *owner == *form) {
111                return Err(Error::NotFound(None));
112            }
113
114            Ok(submit_button)
115        }
116
117        // Step 1. If form is given, then:
118        if let Some(opt_form) = form {
119            // Step 1.1. If submitter is non-null, then:
120            let submitter_element = submitter
121                .map(|s| validate_submitter(s, opt_form))
122                .transpose()?;
123
124            // Step 1.2. Let list be the result of constructing the entry list for form and submitter.
125            return match opt_form.get_form_dataset(submitter_element, None, can_gc) {
126                Some(form_datums) => Ok(FormData::new_with_proto(
127                    Some(form_datums),
128                    global,
129                    proto,
130                    can_gc,
131                )),
132                // Step 1.3. If list is null, then throw an "InvalidStateError" DOMException.
133                None => Err(Error::InvalidState(None)),
134            };
135        }
136
137        Ok(FormData::new_with_proto(None, global, proto, can_gc))
138    }
139
140    /// <https://xhr.spec.whatwg.org/#dom-formdata-append>
141    fn Append(&self, name: USVString, str_value: USVString) {
142        let datum = FormDatum {
143            ty: DOMString::from("string"),
144            name: DOMString::from(name.0.clone()),
145            value: FormDatumValue::String(DOMString::from(str_value.0)),
146        };
147
148        self.data
149            .borrow_mut()
150            .push((NoTrace(LocalName::from(name.0)), datum));
151    }
152
153    /// <https://xhr.spec.whatwg.org/#dom-formdata-append>
154    fn Append_(&self, name: USVString, blob: &Blob, filename: Option<USVString>) {
155        let datum = FormDatum {
156            ty: DOMString::from("file"),
157            name: DOMString::from(name.0.clone()),
158            value: FormDatumValue::File(DomRoot::from_ref(&*self.create_an_entry(
159                blob,
160                filename,
161                CanGc::deprecated_note(),
162            ))),
163        };
164
165        self.data
166            .borrow_mut()
167            .push((NoTrace(LocalName::from(name.0)), datum));
168    }
169
170    /// <https://xhr.spec.whatwg.org/#dom-formdata-delete>
171    fn Delete(&self, name: USVString) {
172        self.data
173            .borrow_mut()
174            .retain(|(datum_name, _)| datum_name.0 != name.0);
175    }
176
177    /// <https://xhr.spec.whatwg.org/#dom-formdata-get>
178    fn Get(&self, name: USVString) -> Option<FileOrUSVString> {
179        self.data
180            .borrow()
181            .iter()
182            .find(|(datum_name, _)| datum_name.0 == name.0)
183            .map(|(_, datum)| match &datum.value {
184                FormDatumValue::String(s) => FileOrUSVString::USVString(USVString(s.to_string())),
185                FormDatumValue::File(b) => FileOrUSVString::File(DomRoot::from_ref(b)),
186            })
187    }
188
189    /// <https://xhr.spec.whatwg.org/#dom-formdata-getall>
190    fn GetAll(&self, name: USVString) -> Vec<FileOrUSVString> {
191        self.data
192            .borrow()
193            .iter()
194            .filter_map(|(datum_name, datum)| {
195                if datum_name.0 != name.0 {
196                    return None;
197                }
198
199                Some(match &datum.value {
200                    FormDatumValue::String(s) => {
201                        FileOrUSVString::USVString(USVString(s.to_string()))
202                    },
203                    FormDatumValue::File(b) => FileOrUSVString::File(DomRoot::from_ref(b)),
204                })
205            })
206            .collect()
207    }
208
209    /// <https://xhr.spec.whatwg.org/#dom-formdata-has>
210    fn Has(&self, name: USVString) -> bool {
211        self.data
212            .borrow()
213            .iter()
214            .any(|(datum_name, _0)| datum_name.0 == name.0)
215    }
216
217    /// <https://xhr.spec.whatwg.org/#dom-formdata-set>
218    fn Set(&self, name: USVString, str_value: USVString) {
219        let mut data = self.data.borrow_mut();
220        let local_name = LocalName::from(name.0.clone());
221
222        data.retain(|(datum_name, _)| datum_name.0 != local_name);
223
224        data.push((
225            NoTrace(local_name),
226            FormDatum {
227                ty: DOMString::from("string"),
228                name: DOMString::from(name.0),
229                value: FormDatumValue::String(DOMString::from(str_value.0)),
230            },
231        ));
232    }
233
234    /// <https://xhr.spec.whatwg.org/#dom-formdata-set>
235    fn Set_(&self, name: USVString, blob: &Blob, filename: Option<USVString>) {
236        let file = self.create_an_entry(blob, filename, CanGc::deprecated_note());
237
238        let mut data = self.data.borrow_mut();
239        let local_name = LocalName::from(name.0.clone());
240
241        data.retain(|(datum_name, _)| datum_name.0 != local_name);
242
243        data.push((
244            NoTrace(LocalName::from(name.0.clone())),
245            FormDatum {
246                ty: DOMString::from("file"),
247                name: DOMString::from(name.0),
248                value: FormDatumValue::File(file),
249            },
250        ));
251    }
252}
253
254impl FormData {
255    /// <https://xhr.spec.whatwg.org/#create-an-entry>
256    fn create_an_entry(
257        &self,
258        blob: &Blob,
259        opt_filename: Option<USVString>,
260        can_gc: CanGc,
261    ) -> DomRoot<File> {
262        // Steps 3-4
263        let name = match opt_filename {
264            Some(filename) => DOMString::from(filename.0),
265            None => match blob.downcast::<File>() {
266                None => DOMString::from("blob"),
267                // If it is already a file and no filename was given,
268                // then neither step 3 nor step 4 happens, so instead of
269                // creating a new File object we use the existing one.
270                Some(file) => {
271                    return DomRoot::from_ref(file);
272                },
273            },
274        };
275
276        let bytes = blob.get_bytes().unwrap_or_default();
277        let last_modified = blob.downcast::<File>().map(|file| file.get_modified());
278
279        File::new(
280            &self.global(),
281            BlobImpl::new_from_bytes(bytes, blob.type_string()),
282            name,
283            last_modified,
284            can_gc,
285        )
286    }
287
288    pub(crate) fn datums(&self) -> Vec<FormDatum> {
289        self.data
290            .borrow()
291            .iter()
292            .map(|(_, datum)| datum.clone())
293            .collect()
294    }
295}
296
297impl Iterable for FormData {
298    type Key = USVString;
299    type Value = FileOrUSVString;
300
301    fn get_iterable_length(&self) -> u32 {
302        self.data.borrow().len() as u32
303    }
304
305    fn get_value_at_index(&self, n: u32) -> FileOrUSVString {
306        let data = self.data.borrow();
307        let datum = &data.get(n as usize).unwrap().1;
308        match &datum.value {
309            FormDatumValue::String(s) => FileOrUSVString::USVString(USVString(s.to_string())),
310            FormDatumValue::File(b) => FileOrUSVString::File(DomRoot::from_ref(b)),
311        }
312    }
313
314    fn get_key_at_index(&self, n: u32) -> USVString {
315        let data = self.data.borrow();
316        let key = &data.get(n as usize).unwrap().0;
317        USVString(key.to_string())
318    }
319}