1use dom_struct::dom_struct;
6use html5ever::LocalName;
7use js::context::JSContext;
8use js::rust::HandleObject;
9use script_bindings::cell::DomRefCell;
10use script_bindings::reflector::{Reflector, reflect_dom_object_with_proto_and_cx};
11use servo_constellation_traits::BlobImpl;
12
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::bindings::trace::NoTrace;
22use crate::dom::blob::Blob;
23use crate::dom::file::File;
24use crate::dom::globalscope::GlobalScope;
25use crate::dom::html::htmlbuttonelement::HTMLButtonElement;
26use crate::dom::html::htmlelement::HTMLElement;
27use crate::dom::html::htmlformelement::{
28 FormDatum, FormDatumUnrooted, FormDatumValueUnrooted, FormSubmitterElement, HTMLFormElement,
29};
30use crate::dom::html::input_element::HTMLInputElement;
31
32#[dom_struct]
33pub(crate) struct FormData {
34 reflector_: Reflector,
35 data: DomRefCell<Vec<(NoTrace<LocalName>, FormDatumUnrooted)>>,
36}
37
38impl FormData {
39 fn new_inherited(form_datums: Option<Vec<FormDatum>>) -> FormData {
40 FormData {
41 reflector_: Reflector::new(),
42 data: DomRefCell::new(
43 form_datums
44 .into_iter()
45 .flatten()
46 .map(|datum| (NoTrace(LocalName::from(&datum.name)), datum.into()))
47 .collect::<Vec<(NoTrace<LocalName>, FormDatumUnrooted)>>(),
48 ),
49 }
50 }
51
52 pub(crate) fn new(
53 cx: &mut JSContext,
54 form_datums: Option<Vec<FormDatum>>,
55 global: &GlobalScope,
56 ) -> DomRoot<FormData> {
57 Self::new_with_proto(cx, form_datums, global, None)
58 }
59
60 fn new_with_proto(
61 cx: &mut JSContext,
62 form_datums: Option<Vec<FormDatum>>,
63 global: &GlobalScope,
64 proto: Option<HandleObject>,
65 ) -> DomRoot<FormData> {
66 reflect_dom_object_with_proto_and_cx(
67 Box::new(FormData::new_inherited(form_datums)),
68 global,
69 proto,
70 cx,
71 )
72 }
73}
74
75impl FormDataMethods<crate::DomTypeHolder> for FormData {
76 fn Constructor<'a>(
78 cx: &mut JSContext,
79 global: &GlobalScope,
80 proto: Option<HandleObject>,
81 form: Option<&'a HTMLFormElement>,
82 submitter: Option<&'a HTMLElement>,
83 ) -> Fallible<DomRoot<FormData>> {
84 fn validate_submitter<'b>(
86 submitter: &'b HTMLElement,
87 form: &'b HTMLFormElement,
88 ) -> Result<FormSubmitterElement<'b>, Error> {
89 let submit_button = submitter
90 .downcast::<HTMLButtonElement>()
91 .map(FormSubmitterElement::Button)
92 .or_else(|| {
93 submitter
94 .downcast::<HTMLInputElement>()
95 .map(FormSubmitterElement::Input)
96 })
97 .ok_or(Error::Type(
98 c"submitter is not a form submitter element".to_owned(),
99 ))?;
100
101 if !submit_button.is_submit_button() {
103 return Err(Error::Type(c"submitter is not a submit button".to_owned()));
104 }
105
106 if !matches!(submit_button.form_owner(), Some(owner) if *owner == *form) {
109 return Err(Error::NotFound(None));
110 }
111
112 Ok(submit_button)
113 }
114
115 if let Some(opt_form) = form {
117 let submitter_element = submitter
119 .map(|s| validate_submitter(s, opt_form))
120 .transpose()?;
121
122 return match opt_form.get_form_dataset(cx, submitter_element, None) {
124 Some(form_datums) => Ok(FormData::new_with_proto(
125 cx,
126 Some(form_datums),
127 global,
128 proto,
129 )),
130 None => Err(Error::InvalidState(None)),
132 };
133 }
134
135 Ok(FormData::new_with_proto(cx, None, global, proto))
136 }
137
138 fn Append(&self, _cx: &mut JSContext, name: USVString, str_value: USVString) {
141 self.data.borrow_mut().push((
142 NoTrace(LocalName::from(name.0.clone())),
143 FormDatumUnrooted {
144 ty: DOMString::from("string"),
145 name: DOMString::from(name.0),
146 value: FormDatumValueUnrooted::String(DOMString::from(str_value.0)),
147 },
148 ));
149 }
150
151 fn Append_(
153 &self,
154 cx: &mut JSContext,
155 name: USVString,
156 blob: &Blob,
157 filename: Option<USVString>,
158 ) {
159 let file = self.create_an_entry(cx, blob, filename);
160
161 self.data.borrow_mut().push((
162 NoTrace(LocalName::from(name.0.clone())),
163 FormDatumUnrooted {
164 ty: DOMString::from("file"),
165 name: DOMString::from(name.0),
166 value: FormDatumValueUnrooted::File(file.as_traced()),
167 },
168 ));
169 }
170
171 fn Delete(&self, name: USVString) {
173 self.data
174 .borrow_mut()
175 .retain(|(datum_name, _)| datum_name.0 != name.0);
176 }
177
178 fn Get(&self, name: USVString) -> Option<FileOrUSVString> {
180 self.data
181 .borrow()
182 .iter()
183 .find(|(datum_name, _)| datum_name.0 == name.0)
184 .map(|(_, datum)| match &datum.value {
185 FormDatumValueUnrooted::String(s) => {
186 FileOrUSVString::USVString(USVString(s.to_string()))
187 },
188 FormDatumValueUnrooted::File(b) => FileOrUSVString::File(DomRoot::from_ref(b)),
189 })
190 }
191
192 fn GetAll(&self, name: USVString) -> Vec<FileOrUSVString> {
194 self.data
195 .borrow()
196 .iter()
197 .filter_map(|(datum_name, datum)| {
198 if datum_name.0 != name.0 {
199 return None;
200 }
201
202 Some(match &datum.value {
203 FormDatumValueUnrooted::String(s) => {
204 FileOrUSVString::USVString(USVString(s.to_string()))
205 },
206 FormDatumValueUnrooted::File(b) => FileOrUSVString::File(DomRoot::from_ref(b)),
207 })
208 })
209 .collect()
210 }
211
212 fn Has(&self, name: USVString) -> bool {
214 self.data
215 .borrow()
216 .iter()
217 .any(|(datum_name, _0)| datum_name.0 == name.0)
218 }
219
220 fn Set(&self, _cx: &mut JSContext, name: USVString, str_value: USVString) {
222 let mut data = self.data.borrow_mut();
223 let local_name = LocalName::from(name.0.clone());
224
225 data.retain(|(datum_name, _)| datum_name.0 != local_name);
226
227 data.push((
228 NoTrace(local_name),
229 FormDatumUnrooted {
230 ty: DOMString::from("string"),
231 name: DOMString::from(name.0),
232 value: FormDatumValueUnrooted::String(DOMString::from(str_value.0)),
233 },
234 ));
235 }
236
237 fn Set_(&self, cx: &mut JSContext, name: USVString, blob: &Blob, filename: Option<USVString>) {
239 let file = self.create_an_entry(cx, blob, filename);
240
241 let mut data = self.data.borrow_mut();
242 let local_name = LocalName::from(name.0.clone());
243
244 data.retain(|(datum_name, _)| datum_name.0 != local_name);
245
246 data.push((
247 NoTrace(LocalName::from(name.0.clone())),
248 FormDatumUnrooted {
249 ty: DOMString::from("file"),
250 name: DOMString::from(name.0),
251 value: FormDatumValueUnrooted::File(file.as_traced()),
252 },
253 ));
254 }
255}
256
257impl FormData {
258 fn create_an_entry(
260 &self,
261 cx: &mut JSContext,
262 blob: &Blob,
263 opt_filename: Option<USVString>,
264 ) -> DomRoot<File> {
265 let name = match opt_filename {
267 Some(filename) => DOMString::from(filename.0),
268 None => match blob.downcast::<File>() {
269 None => DOMString::from("blob"),
270 Some(file) => {
274 return DomRoot::from_ref(file);
275 },
276 },
277 };
278
279 let bytes = blob.get_bytes().unwrap_or_default();
280 let last_modified = blob.downcast::<File>().map(|file| file.get_modified());
281
282 File::new(
283 cx,
284 &self.global(),
285 BlobImpl::new_from_bytes(bytes, blob.type_string()),
286 name,
287 last_modified,
288 )
289 }
290
291 pub(crate) fn datums(&self) -> Vec<FormDatum> {
292 self.data
293 .borrow()
294 .iter()
295 .map(|(_, datum)| datum.root())
296 .collect()
297 }
298}
299
300impl Iterable for FormData {
301 type Key = USVString;
302 type Value = FileOrUSVString;
303
304 fn get_iterable_length(&self, _cx: &mut JSContext) -> u32 {
305 self.data.borrow().len() as u32
306 }
307
308 fn get_value_at_index(&self, _cx: &mut JSContext, index: u32) -> FileOrUSVString {
309 let data = self.data.borrow();
310 let datum = &data.get(index as usize).unwrap().1;
311 match &datum.value {
312 FormDatumValueUnrooted::String(s) => {
313 FileOrUSVString::USVString(USVString(s.to_string()))
314 },
315 FormDatumValueUnrooted::File(b) => FileOrUSVString::File(DomRoot::from_ref(b)),
316 }
317 }
318
319 fn get_key_at_index(&self, _cx: &mut JSContext, index: u32) -> USVString {
320 let data = self.data.borrow();
321 let key = &data.get(index as usize).unwrap().0;
322 USVString(key.to_string())
323 }
324}