script/dom/html/
htmlformelement.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 std::borrow::ToOwned;
6use std::cell::Cell;
7
8use constellation_traits::{LoadData, LoadOrigin, NavigationHistoryBehavior};
9use content_security_policy::sandboxing_directive::SandboxingFlagSet;
10use dom_struct::dom_struct;
11use encoding_rs::{Encoding, UTF_8};
12use headers::{ContentType, HeaderMapExt};
13use html5ever::{LocalName, Prefix, local_name, ns};
14use http::Method;
15use js::rust::HandleObject;
16use mime::{self, Mime};
17use net_traits::http_percent_encode;
18use net_traits::request::Referrer;
19use rustc_hash::FxBuildHasher;
20use servo_rand::random;
21use style::attr::AttrValue;
22use style::str::split_html_space_chars;
23use stylo_atoms::Atom;
24use stylo_dom::ElementState;
25
26use crate::body::Extractable;
27use crate::dom::attr::Attr;
28use crate::dom::bindings::cell::DomRefCell;
29use crate::dom::bindings::codegen::Bindings::AttrBinding::Attr_Binding::AttrMethods;
30use crate::dom::bindings::codegen::Bindings::BlobBinding::BlobMethods;
31use crate::dom::bindings::codegen::Bindings::DocumentBinding::DocumentMethods;
32use crate::dom::bindings::codegen::Bindings::EventBinding::EventMethods;
33use crate::dom::bindings::codegen::Bindings::HTMLButtonElementBinding::HTMLButtonElementMethods;
34use crate::dom::bindings::codegen::Bindings::HTMLElementBinding::HTMLElementMethods;
35use crate::dom::bindings::codegen::Bindings::HTMLFormControlsCollectionBinding::HTMLFormControlsCollectionMethods;
36use crate::dom::bindings::codegen::Bindings::HTMLFormElementBinding::HTMLFormElementMethods;
37use crate::dom::bindings::codegen::Bindings::HTMLInputElementBinding::HTMLInputElementMethods;
38use crate::dom::bindings::codegen::Bindings::HTMLOrSVGElementBinding::FocusOptions;
39use crate::dom::bindings::codegen::Bindings::HTMLTextAreaElementBinding::HTMLTextAreaElementMethods;
40use crate::dom::bindings::codegen::Bindings::NodeBinding::{NodeConstants, NodeMethods};
41use crate::dom::bindings::codegen::Bindings::NodeListBinding::NodeListMethods;
42use crate::dom::bindings::codegen::Bindings::RadioNodeListBinding::RadioNodeListMethods;
43use crate::dom::bindings::codegen::Bindings::WindowBinding::Window_Binding::WindowMethods;
44use crate::dom::bindings::codegen::UnionTypes::RadioNodeListOrElement;
45use crate::dom::bindings::error::{Error, Fallible};
46use crate::dom::bindings::inheritance::{Castable, ElementTypeId, HTMLElementTypeId, NodeTypeId};
47use crate::dom::bindings::refcounted::Trusted;
48use crate::dom::bindings::reflector::{DomGlobal, DomObject};
49use crate::dom::bindings::root::{Dom, DomOnceCell, DomRoot, MutNullableDom};
50use crate::dom::bindings::str::DOMString;
51use crate::dom::bindings::trace::{HashMapTracedValues, NoTrace};
52use crate::dom::blob::Blob;
53use crate::dom::customelementregistry::CallbackReaction;
54use crate::dom::document::Document;
55use crate::dom::domtokenlist::DOMTokenList;
56use crate::dom::element::{AttributeMutation, Element};
57use crate::dom::event::{Event, EventBubbles, EventCancelable};
58use crate::dom::eventtarget::EventTarget;
59use crate::dom::file::File;
60use crate::dom::formdata::FormData;
61use crate::dom::formdataevent::FormDataEvent;
62use crate::dom::html::htmlbuttonelement::HTMLButtonElement;
63use crate::dom::html::htmlcollection::CollectionFilter;
64use crate::dom::html::htmldatalistelement::HTMLDataListElement;
65use crate::dom::html::htmlelement::HTMLElement;
66use crate::dom::html::htmlfieldsetelement::HTMLFieldSetElement;
67use crate::dom::html::htmlformcontrolscollection::HTMLFormControlsCollection;
68use crate::dom::html::htmlimageelement::HTMLImageElement;
69use crate::dom::html::htmlinputelement::{HTMLInputElement, InputType};
70use crate::dom::html::htmllabelelement::HTMLLabelElement;
71use crate::dom::html::htmllegendelement::HTMLLegendElement;
72use crate::dom::html::htmlobjectelement::HTMLObjectElement;
73use crate::dom::html::htmloutputelement::HTMLOutputElement;
74use crate::dom::html::htmlselectelement::HTMLSelectElement;
75use crate::dom::html::htmltextareaelement::HTMLTextAreaElement;
76use crate::dom::node::{
77    BindContext, Node, NodeFlags, NodeTraits, UnbindContext, VecPreOrderInsertionHelper,
78};
79use crate::dom::nodelist::{NodeList, RadioListMode};
80use crate::dom::radionodelist::RadioNodeList;
81use crate::dom::submitevent::SubmitEvent;
82use crate::dom::virtualmethods::VirtualMethods;
83use crate::dom::window::Window;
84use crate::links::{LinkRelations, get_element_target};
85use crate::script_runtime::CanGc;
86use crate::script_thread::ScriptThread;
87
88#[dom_struct]
89pub(crate) struct HTMLFormElement {
90    htmlelement: HTMLElement,
91    marked_for_reset: Cell<bool>,
92    /// <https://html.spec.whatwg.org/multipage/#constructing-entry-list>
93    constructing_entry_list: Cell<bool>,
94    elements: DomOnceCell<HTMLFormControlsCollection>,
95    controls: DomRefCell<Vec<Dom<Element>>>,
96
97    /// It is safe to use FxBuildHasher here as `Atom` is in the string_cache.
98    #[allow(clippy::type_complexity)]
99    past_names_map:
100        DomRefCell<HashMapTracedValues<Atom, (Dom<Element>, NoTrace<usize>), FxBuildHasher>>,
101
102    /// The current generation of past names, i.e., the number of name changes to the name.
103    current_name_generation: Cell<usize>,
104
105    firing_submission_events: Cell<bool>,
106    rel_list: MutNullableDom<DOMTokenList>,
107
108    /// <https://html.spec.whatwg.org/multipage/#planned-navigation>
109    planned_navigation: Cell<usize>,
110
111    #[no_trace]
112    relations: Cell<LinkRelations>,
113}
114
115impl HTMLFormElement {
116    fn new_inherited(
117        local_name: LocalName,
118        prefix: Option<Prefix>,
119        document: &Document,
120    ) -> HTMLFormElement {
121        HTMLFormElement {
122            htmlelement: HTMLElement::new_inherited_with_state(
123                ElementState::VALID,
124                local_name,
125                prefix,
126                document,
127            ),
128            marked_for_reset: Cell::new(false),
129            constructing_entry_list: Cell::new(false),
130            elements: Default::default(),
131            controls: DomRefCell::new(Vec::new()),
132            past_names_map: DomRefCell::new(HashMapTracedValues::new_fx()),
133            current_name_generation: Cell::new(0),
134            firing_submission_events: Cell::new(false),
135            rel_list: Default::default(),
136            planned_navigation: Default::default(),
137            relations: Cell::new(LinkRelations::empty()),
138        }
139    }
140
141    #[cfg_attr(crown, allow(crown::unrooted_must_root))]
142    pub(crate) fn new(
143        local_name: LocalName,
144        prefix: Option<Prefix>,
145        document: &Document,
146        proto: Option<HandleObject>,
147        can_gc: CanGc,
148    ) -> DomRoot<HTMLFormElement> {
149        Node::reflect_node_with_proto(
150            Box::new(HTMLFormElement::new_inherited(local_name, prefix, document)),
151            document,
152            proto,
153            can_gc,
154        )
155    }
156
157    fn filter_for_radio_list(mode: RadioListMode, child: &Element, name: &Atom) -> bool {
158        if let Some(child) = child.downcast::<Element>() {
159            match mode {
160                RadioListMode::ControlsExceptImageInputs => {
161                    if child
162                        .downcast::<HTMLElement>()
163                        .is_some_and(|c| c.is_listed_element()) &&
164                        (child.get_id().is_some_and(|i| i == *name) ||
165                            child.get_name().is_some_and(|n| n == *name))
166                    {
167                        if let Some(inp) = child.downcast::<HTMLInputElement>() {
168                            // input, only return it if it's not image-button state
169                            return inp.input_type() != InputType::Image;
170                        } else {
171                            // control, but not an input
172                            return true;
173                        }
174                    }
175                    return false;
176                },
177                RadioListMode::Images => {
178                    return child.is::<HTMLImageElement>() &&
179                        (child.get_id().is_some_and(|i| i == *name) ||
180                            child.get_name().is_some_and(|n| n == *name));
181                },
182            }
183        }
184        false
185    }
186
187    pub(crate) fn nth_for_radio_list(
188        &self,
189        index: u32,
190        mode: RadioListMode,
191        name: &Atom,
192    ) -> Option<DomRoot<Node>> {
193        self.controls
194            .borrow()
195            .iter()
196            .filter(|n| HTMLFormElement::filter_for_radio_list(mode, n, name))
197            .nth(index as usize)
198            .map(|n| DomRoot::from_ref(n.upcast::<Node>()))
199    }
200
201    pub(crate) fn count_for_radio_list(&self, mode: RadioListMode, name: &Atom) -> u32 {
202        self.controls
203            .borrow()
204            .iter()
205            .filter(|n| HTMLFormElement::filter_for_radio_list(mode, n, name))
206            .count() as u32
207    }
208}
209
210impl HTMLFormElementMethods<crate::DomTypeHolder> for HTMLFormElement {
211    // https://html.spec.whatwg.org/multipage/#dom-form-acceptcharset
212    make_getter!(AcceptCharset, "accept-charset");
213
214    // https://html.spec.whatwg.org/multipage/#dom-form-acceptcharset
215    make_setter!(SetAcceptCharset, "accept-charset");
216
217    // https://html.spec.whatwg.org/multipage/#dom-fs-action
218    make_form_action_getter!(Action, "action");
219
220    // https://html.spec.whatwg.org/multipage/#dom-fs-action
221    make_setter!(SetAction, "action");
222
223    // https://html.spec.whatwg.org/multipage/#dom-form-autocomplete
224    make_enumerated_getter!(
225        Autocomplete,
226        "autocomplete",
227        "on" | "off",
228        missing => "on",
229        invalid => "on"
230    );
231
232    // https://html.spec.whatwg.org/multipage/#dom-form-autocomplete
233    make_setter!(SetAutocomplete, "autocomplete");
234
235    // https://html.spec.whatwg.org/multipage/#dom-fs-enctype
236    make_enumerated_getter!(
237        Enctype,
238        "enctype",
239        "application/x-www-form-urlencoded" | "text/plain" | "multipart/form-data",
240        missing => "application/x-www-form-urlencoded",
241        invalid => "application/x-www-form-urlencoded"
242    );
243
244    // https://html.spec.whatwg.org/multipage/#dom-fs-enctype
245    make_setter!(SetEnctype, "enctype");
246
247    // https://html.spec.whatwg.org/multipage/#dom-fs-encoding
248    fn Encoding(&self) -> DOMString {
249        self.Enctype()
250    }
251
252    // https://html.spec.whatwg.org/multipage/#dom-fs-encoding
253    fn SetEncoding(&self, value: DOMString) {
254        self.SetEnctype(value)
255    }
256
257    // https://html.spec.whatwg.org/multipage/#dom-fs-method
258    make_enumerated_getter!(
259        Method,
260        "method",
261        "get" | "post" | "dialog",
262        missing => "get",
263        invalid => "get"
264    );
265
266    // https://html.spec.whatwg.org/multipage/#dom-fs-method
267    make_setter!(SetMethod, "method");
268
269    // https://html.spec.whatwg.org/multipage/#dom-form-name
270    make_getter!(Name, "name");
271
272    // https://html.spec.whatwg.org/multipage/#dom-form-name
273    make_atomic_setter!(SetName, "name");
274
275    // https://html.spec.whatwg.org/multipage/#dom-fs-novalidate
276    make_bool_getter!(NoValidate, "novalidate");
277
278    // https://html.spec.whatwg.org/multipage/#dom-fs-novalidate
279    make_bool_setter!(SetNoValidate, "novalidate");
280
281    // https://html.spec.whatwg.org/multipage/#dom-fs-target
282    make_getter!(Target, "target");
283
284    // https://html.spec.whatwg.org/multipage/#dom-fs-target
285    make_setter!(SetTarget, "target");
286
287    // https://html.spec.whatwg.org/multipage/#dom-a-rel
288    make_getter!(Rel, "rel");
289
290    // https://html.spec.whatwg.org/multipage/#the-form-element:concept-form-submit
291    fn Submit(&self, can_gc: CanGc) {
292        self.submit(
293            SubmittedFrom::FromForm,
294            FormSubmitterElement::Form(self),
295            can_gc,
296        );
297    }
298
299    // https://html.spec.whatwg.org/multipage/#dom-form-requestsubmit
300    fn RequestSubmit(&self, submitter: Option<&HTMLElement>, can_gc: CanGc) -> Fallible<()> {
301        let submitter: FormSubmitterElement = match submitter {
302            Some(submitter_element) => {
303                // Step 1.1
304                let error_not_a_submit_button =
305                    Err(Error::Type("submitter must be a submit button".to_string()));
306
307                let element = match submitter_element.upcast::<Node>().type_id() {
308                    NodeTypeId::Element(ElementTypeId::HTMLElement(element)) => element,
309                    _ => {
310                        return error_not_a_submit_button;
311                    },
312                };
313
314                let submit_button = match element {
315                    HTMLElementTypeId::HTMLInputElement => FormSubmitterElement::Input(
316                        submitter_element
317                            .downcast::<HTMLInputElement>()
318                            .expect("Failed to downcast submitter elem to HTMLInputElement."),
319                    ),
320                    HTMLElementTypeId::HTMLButtonElement => FormSubmitterElement::Button(
321                        submitter_element
322                            .downcast::<HTMLButtonElement>()
323                            .expect("Failed to downcast submitter elem to HTMLButtonElement."),
324                    ),
325                    _ => {
326                        return error_not_a_submit_button;
327                    },
328                };
329
330                if !submit_button.is_submit_button() {
331                    return error_not_a_submit_button;
332                }
333
334                let submitters_owner = submit_button.form_owner();
335
336                // Step 1.2
337                let owner = match submitters_owner {
338                    Some(owner) => owner,
339                    None => {
340                        return Err(Error::NotFound(None));
341                    },
342                };
343
344                if *owner != *self {
345                    return Err(Error::NotFound(None));
346                }
347
348                submit_button
349            },
350            None => {
351                // Step 2
352                FormSubmitterElement::Form(self)
353            },
354        };
355        // Step 3
356        self.submit(SubmittedFrom::NotFromForm, submitter, can_gc);
357        Ok(())
358    }
359
360    // https://html.spec.whatwg.org/multipage/#dom-form-reset
361    fn Reset(&self, can_gc: CanGc) {
362        self.reset(ResetFrom::FromForm, can_gc);
363    }
364
365    // https://html.spec.whatwg.org/multipage/#dom-form-elements
366    fn Elements(&self, can_gc: CanGc) -> DomRoot<HTMLFormControlsCollection> {
367        #[derive(JSTraceable, MallocSizeOf)]
368        struct ElementsFilter {
369            form: DomRoot<HTMLFormElement>,
370        }
371        impl CollectionFilter for ElementsFilter {
372            fn filter<'a>(&self, elem: &'a Element, _root: &'a Node) -> bool {
373                let form_owner = match elem.upcast::<Node>().type_id() {
374                    NodeTypeId::Element(ElementTypeId::HTMLElement(t)) => match t {
375                        HTMLElementTypeId::HTMLButtonElement => {
376                            elem.downcast::<HTMLButtonElement>().unwrap().form_owner()
377                        },
378                        HTMLElementTypeId::HTMLFieldSetElement => {
379                            elem.downcast::<HTMLFieldSetElement>().unwrap().form_owner()
380                        },
381                        HTMLElementTypeId::HTMLInputElement => {
382                            let input_elem = elem.downcast::<HTMLInputElement>().unwrap();
383                            if input_elem.input_type() == InputType::Image {
384                                return false;
385                            }
386                            input_elem.form_owner()
387                        },
388                        HTMLElementTypeId::HTMLObjectElement => {
389                            elem.downcast::<HTMLObjectElement>().unwrap().form_owner()
390                        },
391                        HTMLElementTypeId::HTMLOutputElement => {
392                            elem.downcast::<HTMLOutputElement>().unwrap().form_owner()
393                        },
394                        HTMLElementTypeId::HTMLSelectElement => {
395                            elem.downcast::<HTMLSelectElement>().unwrap().form_owner()
396                        },
397                        HTMLElementTypeId::HTMLTextAreaElement => {
398                            elem.downcast::<HTMLTextAreaElement>().unwrap().form_owner()
399                        },
400                        HTMLElementTypeId::HTMLElement => {
401                            let html_element = elem.downcast::<HTMLElement>().unwrap();
402                            if html_element.is_form_associated_custom_element() {
403                                html_element.form_owner()
404                            } else {
405                                return false;
406                            }
407                        },
408                        _ => {
409                            debug_assert!(
410                                !elem.downcast::<HTMLElement>().unwrap().is_listed_element()
411                            );
412                            return false;
413                        },
414                    },
415                    _ => return false,
416                };
417
418                match form_owner {
419                    Some(form_owner) => form_owner == self.form,
420                    None => false,
421                }
422            }
423        }
424        DomRoot::from_ref(self.elements.init_once(|| {
425            let filter = Box::new(ElementsFilter {
426                form: DomRoot::from_ref(self),
427            });
428            let window = self.owner_window();
429            HTMLFormControlsCollection::new(&window, self, filter, can_gc)
430        }))
431    }
432
433    // https://html.spec.whatwg.org/multipage/#dom-form-length
434    fn Length(&self) -> u32 {
435        self.Elements(CanGc::note()).Length()
436    }
437
438    // https://html.spec.whatwg.org/multipage/#dom-form-item
439    fn IndexedGetter(&self, index: u32, can_gc: CanGc) -> Option<DomRoot<Element>> {
440        let elements = self.Elements(can_gc);
441        elements.IndexedGetter(index)
442    }
443
444    // https://html.spec.whatwg.org/multipage/#the-form-element%3Adetermine-the-value-of-a-named-property
445    fn NamedGetter(&self, name: DOMString, can_gc: CanGc) -> Option<RadioNodeListOrElement> {
446        let window = self.owner_window();
447
448        let name = Atom::from(name);
449
450        // Step 1
451        let mut candidates =
452            RadioNodeList::new_controls_except_image_inputs(&window, self, &name, can_gc);
453        let mut candidates_length = candidates.Length();
454
455        // Step 2
456        if candidates_length == 0 {
457            candidates = RadioNodeList::new_images(&window, self, &name, can_gc);
458            candidates_length = candidates.Length();
459        }
460
461        let mut past_names_map = self.past_names_map.borrow_mut();
462
463        // Step 3
464        if candidates_length == 0 {
465            if past_names_map.contains_key(&name) {
466                return Some(RadioNodeListOrElement::Element(DomRoot::from_ref(
467                    &*past_names_map.get(&name).unwrap().0,
468                )));
469            }
470            return None;
471        }
472
473        // Step 4
474        if candidates_length > 1 {
475            return Some(RadioNodeListOrElement::RadioNodeList(candidates));
476        }
477
478        // Step 5
479        // candidates_length is 1, so we can unwrap item 0
480        let element_node = candidates.upcast::<NodeList>().Item(0).unwrap();
481        past_names_map.insert(
482            name,
483            (
484                Dom::from_ref(element_node.downcast::<Element>().unwrap()),
485                NoTrace(self.current_name_generation.get() + 1),
486            ),
487        );
488        self.current_name_generation
489            .set(self.current_name_generation.get() + 1);
490
491        // Step 6
492        Some(RadioNodeListOrElement::Element(DomRoot::from_ref(
493            element_node.downcast::<Element>().unwrap(),
494        )))
495    }
496
497    // https://html.spec.whatwg.org/multipage/#dom-a-rel
498    fn SetRel(&self, rel: DOMString, can_gc: CanGc) {
499        self.upcast::<Element>()
500            .set_tokenlist_attribute(&local_name!("rel"), rel, can_gc);
501    }
502
503    // https://html.spec.whatwg.org/multipage/#dom-a-rellist
504    fn RelList(&self, can_gc: CanGc) -> DomRoot<DOMTokenList> {
505        self.rel_list.or_init(|| {
506            DOMTokenList::new(
507                self.upcast(),
508                &local_name!("rel"),
509                Some(vec![
510                    Atom::from("noopener"),
511                    Atom::from("noreferrer"),
512                    Atom::from("opener"),
513                ]),
514                can_gc,
515            )
516        })
517    }
518
519    // https://html.spec.whatwg.org/multipage/#the-form-element:supported-property-names
520    #[allow(non_snake_case)]
521    fn SupportedPropertyNames(&self) -> Vec<DOMString> {
522        // Step 1
523        #[derive(Debug, Eq, Ord, PartialEq, PartialOrd)]
524        enum SourcedNameSource {
525            Id,
526            Name,
527            Past(usize),
528        }
529
530        impl SourcedNameSource {
531            fn is_past(&self) -> bool {
532                matches!(self, SourcedNameSource::Past(..))
533            }
534        }
535
536        struct SourcedName {
537            name: Atom,
538            element: DomRoot<Element>,
539            source: SourcedNameSource,
540        }
541
542        let mut sourced_names_vec: Vec<SourcedName> = Vec::new();
543
544        // Step 2
545        for child in self.controls.borrow().iter() {
546            if child
547                .downcast::<HTMLElement>()
548                .is_some_and(|c| c.is_listed_element())
549            {
550                if let Some(id_atom) = child.get_id() {
551                    let entry = SourcedName {
552                        name: id_atom,
553                        element: DomRoot::from_ref(child),
554                        source: SourcedNameSource::Id,
555                    };
556                    sourced_names_vec.push(entry);
557                }
558                if let Some(name_atom) = child.get_name() {
559                    let entry = SourcedName {
560                        name: name_atom,
561                        element: DomRoot::from_ref(child),
562                        source: SourcedNameSource::Name,
563                    };
564                    sourced_names_vec.push(entry);
565                }
566            }
567        }
568
569        // Step 3
570        for child in self.controls.borrow().iter() {
571            if child.is::<HTMLImageElement>() {
572                if let Some(id_atom) = child.get_id() {
573                    let entry = SourcedName {
574                        name: id_atom,
575                        element: DomRoot::from_ref(child),
576                        source: SourcedNameSource::Id,
577                    };
578                    sourced_names_vec.push(entry);
579                }
580                if let Some(name_atom) = child.get_name() {
581                    let entry = SourcedName {
582                        name: name_atom,
583                        element: DomRoot::from_ref(child),
584                        source: SourcedNameSource::Name,
585                    };
586                    sourced_names_vec.push(entry);
587                }
588            }
589        }
590
591        // Step 4
592        let past_names_map = self.past_names_map.borrow();
593        for (key, val) in past_names_map.iter() {
594            let entry = SourcedName {
595                name: key.clone(),
596                element: DomRoot::from_ref(&*val.0),
597                source: SourcedNameSource::Past(self.current_name_generation.get() - val.1.0),
598            };
599            sourced_names_vec.push(entry);
600        }
601
602        // Step 5
603        // TODO need to sort as per spec.
604        // if a.CompareDocumentPosition(b) returns 0 that means a=b in which case
605        // the remaining part where sorting is to be done by putting entries whose source is id first,
606        // then entries whose source is name, and finally entries whose source is past,
607        // and sorting entries with the same element and source by their age, oldest first.
608
609        // if a.CompareDocumentPosition(b) has set NodeConstants::DOCUMENT_POSITION_FOLLOWING
610        // (this can be checked by bitwise operations) then b would follow a in tree order and
611        // Ordering::Less should be returned in the closure else Ordering::Greater
612
613        sourced_names_vec.sort_by(|a, b| {
614            if a.element
615                .upcast::<Node>()
616                .CompareDocumentPosition(b.element.upcast::<Node>()) ==
617                0
618            {
619                if a.source.is_past() && b.source.is_past() {
620                    b.source.cmp(&a.source)
621                } else {
622                    a.source.cmp(&b.source)
623                }
624            } else if a
625                .element
626                .upcast::<Node>()
627                .CompareDocumentPosition(b.element.upcast::<Node>()) &
628                NodeConstants::DOCUMENT_POSITION_FOLLOWING ==
629                NodeConstants::DOCUMENT_POSITION_FOLLOWING
630            {
631                std::cmp::Ordering::Less
632            } else {
633                std::cmp::Ordering::Greater
634            }
635        });
636
637        // Step 6
638        sourced_names_vec.retain(|sn| !sn.name.to_string().is_empty());
639
640        // Step 7-8
641        let mut names_vec: Vec<DOMString> = Vec::new();
642        for elem in sourced_names_vec.iter() {
643            if !names_vec.iter().any(|name| *name == *elem.name) {
644                names_vec.push(DOMString::from(&*elem.name));
645            }
646        }
647
648        names_vec
649    }
650
651    /// <https://html.spec.whatwg.org/multipage/#dom-form-checkvalidity>
652    fn CheckValidity(&self, can_gc: CanGc) -> bool {
653        self.static_validation(can_gc).is_ok()
654    }
655
656    /// <https://html.spec.whatwg.org/multipage/#dom-form-reportvalidity>
657    fn ReportValidity(&self, can_gc: CanGc) -> bool {
658        self.interactive_validation(can_gc).is_ok()
659    }
660}
661
662#[derive(Clone, Copy, MallocSizeOf, PartialEq)]
663pub(crate) enum SubmittedFrom {
664    FromForm,
665    NotFromForm,
666}
667
668#[derive(Clone, Copy, MallocSizeOf)]
669pub(crate) enum ResetFrom {
670    FromForm,
671    NotFromForm,
672}
673
674impl HTMLFormElement {
675    // https://html.spec.whatwg.org/multipage/#picking-an-encoding-for-the-form
676    fn pick_encoding(&self) -> &'static Encoding {
677        // Step 2
678        if self
679            .upcast::<Element>()
680            .has_attribute(&local_name!("accept-charset"))
681        {
682            // Substep 1
683            let input = self
684                .upcast::<Element>()
685                .get_string_attribute(&local_name!("accept-charset"));
686
687            // Substep 2, 3, 4
688            let input = input.str();
689            let mut candidate_encodings =
690                split_html_space_chars(&input).filter_map(|c| Encoding::for_label(c.as_bytes()));
691
692            // Substep 5, 6
693            return candidate_encodings.next().unwrap_or(UTF_8);
694        }
695
696        // Step 1, 3
697        self.owner_document().encoding()
698    }
699
700    // https://html.spec.whatwg.org/multipage/#text/plain-encoding-algorithm
701    fn encode_plaintext(&self, form_data: &mut [FormDatum]) -> String {
702        // Step 1
703        let mut result = String::new();
704
705        // Step 2
706        for entry in form_data.iter() {
707            let value = match &entry.value {
708                FormDatumValue::File(f) => f.name(),
709                FormDatumValue::String(s) => s,
710            };
711            result.push_str(&format!("{}={}\r\n", entry.name, value));
712        }
713
714        // Step 3
715        result
716    }
717
718    pub(crate) fn update_validity(&self, can_gc: CanGc) {
719        let is_any_invalid = self
720            .controls
721            .borrow()
722            .iter()
723            .any(|control| control.is_invalid(false, can_gc));
724
725        self.upcast::<Element>()
726            .set_state(ElementState::VALID, !is_any_invalid);
727        self.upcast::<Element>()
728            .set_state(ElementState::INVALID, is_any_invalid);
729    }
730
731    /// [Form submission](https://html.spec.whatwg.org/multipage/#concept-form-submit)
732    pub(crate) fn submit(
733        &self,
734        submit_method_flag: SubmittedFrom,
735        submitter: FormSubmitterElement,
736        can_gc: CanGc,
737    ) {
738        // Step 1
739        if self.upcast::<Element>().cannot_navigate() {
740            return;
741        }
742
743        // Step 2
744        if self.constructing_entry_list.get() {
745            return;
746        }
747        // Step 3. Let form document be form's node document.
748        let doc = self.owner_document();
749
750        // Step 4. If form document's active sandboxing flag set has its sandboxed forms browsing
751        // context flag set, then return.
752        if doc.has_active_sandboxing_flag(SandboxingFlagSet::SANDBOXED_FORMS_BROWSING_CONTEXT_FLAG)
753        {
754            return;
755        }
756
757        let base = doc.base_url();
758        // TODO: Handle browsing contexts (Step 5)
759        // Step 6
760        if submit_method_flag == SubmittedFrom::NotFromForm {
761            // Step 6.1
762            if self.firing_submission_events.get() {
763                return;
764            }
765            // Step 6.2
766            self.firing_submission_events.set(true);
767            // Step 6.3
768            if !submitter.no_validate(self) && self.interactive_validation(can_gc).is_err() {
769                self.firing_submission_events.set(false);
770                return;
771            }
772            // Step 6.4
773            // spec calls this "submitterButton" but it doesn't have to be a button,
774            // just not be the form itself
775            let submitter_button = match submitter {
776                FormSubmitterElement::Form(f) => {
777                    if f == self {
778                        None
779                    } else {
780                        Some(f.upcast::<HTMLElement>())
781                    }
782                },
783                FormSubmitterElement::Input(i) => Some(i.upcast::<HTMLElement>()),
784                FormSubmitterElement::Button(b) => Some(b.upcast::<HTMLElement>()),
785            };
786
787            // Step 6.5
788            let event = SubmitEvent::new(
789                self.global().as_window(),
790                atom!("submit"),
791                true,
792                true,
793                submitter_button.map(DomRoot::from_ref),
794                can_gc,
795            );
796            let event = event.upcast::<Event>();
797            event.fire(self.upcast::<EventTarget>(), can_gc);
798
799            // Step 6.6
800            self.firing_submission_events.set(false);
801            // Step 6.7
802            if event.DefaultPrevented() {
803                return;
804            }
805            // Step 6.8
806            if self.upcast::<Element>().cannot_navigate() {
807                return;
808            }
809        }
810
811        // Step 7
812        let encoding = self.pick_encoding();
813
814        // Step 8
815        let mut form_data = match self.get_form_dataset(Some(submitter), Some(encoding), can_gc) {
816            Some(form_data) => form_data,
817            None => return,
818        };
819
820        // Step 9
821        if self.upcast::<Element>().cannot_navigate() {
822            return;
823        }
824
825        // Step 10
826        let mut action = submitter.action();
827
828        // Step 11
829        if action.is_empty() {
830            action = DOMString::from(base.as_str());
831        }
832        // Step 12-13
833        let action_components = match base.join(&action.str()) {
834            Ok(url) => url,
835            Err(_) => return,
836        };
837        // Step 14-16
838        let scheme = action_components.scheme().to_owned();
839        let enctype = submitter.enctype();
840        let method = submitter.method();
841
842        // Step 17
843        let target_attribute_value =
844            if submitter.is_submit_button() && submitter.target() != DOMString::new() {
845                Some(submitter.target())
846            } else {
847                let form_owner = submitter.form_owner();
848                let form = form_owner.as_deref().unwrap_or(self);
849                get_element_target(form.upcast::<Element>())
850            };
851
852        // Step 18
853        let noopener = self
854            .relations
855            .get()
856            .get_element_noopener(target_attribute_value.as_ref());
857
858        // Step 19
859        let source = doc.browsing_context().unwrap();
860        let (maybe_chosen, _new) = source
861            .choose_browsing_context(target_attribute_value.unwrap_or(DOMString::new()), noopener);
862
863        // Step 20
864        let chosen = match maybe_chosen {
865            Some(proxy) => proxy,
866            None => return,
867        };
868        let target_document = match chosen.document() {
869            Some(doc) => doc,
870            None => return,
871        };
872        // Step 21
873        let target_window = target_document.window();
874        let mut load_data = LoadData::new(
875            LoadOrigin::Script(doc.origin().immutable().clone()),
876            action_components,
877            None,
878            target_window.as_global_scope().get_referrer(),
879            target_document.get_referrer_policy(),
880            Some(target_window.as_global_scope().is_secure_context()),
881            Some(target_document.insecure_requests_policy()),
882            target_document.has_trustworthy_ancestor_origin(),
883            target_document.creation_sandboxing_flag_set_considering_parent_iframe(),
884        );
885
886        // Step 22
887        match (&*scheme, method) {
888            (_, FormMethod::Dialog) => {
889                // TODO: Submit dialog
890                // https://html.spec.whatwg.org/multipage/#submit-dialog
891            },
892            // https://html.spec.whatwg.org/multipage/#submit-mutate-action
893            ("http", FormMethod::Get) | ("https", FormMethod::Get) | ("data", FormMethod::Get) => {
894                load_data
895                    .headers
896                    .typed_insert(ContentType::from(mime::APPLICATION_WWW_FORM_URLENCODED));
897                self.mutate_action_url(&mut form_data, load_data, encoding, target_window);
898            },
899            // https://html.spec.whatwg.org/multipage/#submit-body
900            ("http", FormMethod::Post) | ("https", FormMethod::Post) => {
901                load_data.method = Method::POST;
902                self.submit_entity_body(
903                    &mut form_data,
904                    load_data,
905                    enctype,
906                    encoding,
907                    target_window,
908                    can_gc,
909                );
910            },
911            // https://html.spec.whatwg.org/multipage/#submit-get-action
912            ("file", _) |
913            ("about", _) |
914            ("data", FormMethod::Post) |
915            ("ftp", _) |
916            ("javascript", _) => {
917                self.plan_to_navigate(load_data, target_window);
918            },
919            ("mailto", FormMethod::Post) => {
920                // TODO: Mail as body
921                // https://html.spec.whatwg.org/multipage/#submit-mailto-body
922            },
923            ("mailto", FormMethod::Get) => {
924                // TODO: Mail with headers
925                // https://html.spec.whatwg.org/multipage/#submit-mailto-headers
926            },
927            _ => (),
928        }
929    }
930
931    // https://html.spec.whatwg.org/multipage/#submit-mutate-action
932    fn mutate_action_url(
933        &self,
934        form_data: &mut [FormDatum],
935        mut load_data: LoadData,
936        encoding: &'static Encoding,
937        target: &Window,
938    ) {
939        let charset = encoding.name();
940
941        self.set_url_query_pairs(
942            &mut load_data.url,
943            form_data
944                .iter()
945                .map(|field| (field.name.str(), field.replace_value(charset))),
946        );
947
948        self.plan_to_navigate(load_data, target);
949    }
950
951    // https://html.spec.whatwg.org/multipage/#submit-body
952    fn submit_entity_body(
953        &self,
954        form_data: &mut [FormDatum],
955        mut load_data: LoadData,
956        enctype: FormEncType,
957        encoding: &'static Encoding,
958        target: &Window,
959        can_gc: CanGc,
960    ) {
961        let boundary = generate_boundary();
962        let bytes = match enctype {
963            FormEncType::UrlEncoded => {
964                let charset = encoding.name();
965                load_data
966                    .headers
967                    .typed_insert(ContentType::from(mime::APPLICATION_WWW_FORM_URLENCODED));
968
969                let mut url = load_data.url.clone();
970                self.set_url_query_pairs(
971                    &mut url,
972                    form_data
973                        .iter()
974                        .map(|field| (field.name.str(), field.replace_value(charset))),
975                );
976
977                url.query().unwrap_or("").to_string().into_bytes()
978            },
979            FormEncType::MultipartFormData => {
980                let mime: Mime = format!("multipart/form-data; boundary={}", boundary)
981                    .parse()
982                    .unwrap();
983                load_data.headers.typed_insert(ContentType::from(mime));
984                encode_multipart_form_data(form_data, boundary, encoding)
985            },
986            FormEncType::TextPlain => {
987                load_data
988                    .headers
989                    .typed_insert(ContentType::from(mime::TEXT_PLAIN));
990                self.encode_plaintext(form_data).into_bytes()
991            },
992        };
993
994        let global = self.global();
995
996        let request_body = bytes
997            .extract(&global, can_gc)
998            .expect("Couldn't extract body.")
999            .into_net_request_body()
1000            .0;
1001        load_data.data = Some(request_body);
1002
1003        self.plan_to_navigate(load_data, target);
1004    }
1005
1006    fn set_url_query_pairs<T>(
1007        &self,
1008        url: &mut servo_url::ServoUrl,
1009        pairs: impl Iterator<Item = (T, String)>,
1010    ) where
1011        T: AsRef<str>,
1012    {
1013        let encoding = self.pick_encoding();
1014        url.as_mut_url()
1015            .query_pairs_mut()
1016            .encoding_override(Some(&|s| encoding.encode(s).0))
1017            .clear()
1018            .extend_pairs(pairs);
1019    }
1020
1021    /// [Planned navigation](https://html.spec.whatwg.org/multipage/#planned-navigation)
1022    fn plan_to_navigate(&self, mut load_data: LoadData, target: &Window) {
1023        // 1. Let referrerPolicy be the empty string.
1024        // 2. If the form element's link types include the noreferrer keyword,
1025        //    then set referrerPolicy to "no-referrer".
1026        // Note: both steps done below.
1027        let elem = self.upcast::<Element>();
1028        let referrer = match elem.get_attribute(&ns!(), &local_name!("rel")) {
1029            Some(ref link_types) if link_types.Value().contains("noreferrer") => {
1030                Referrer::NoReferrer
1031            },
1032            _ => target.as_global_scope().get_referrer(),
1033        };
1034
1035        // 3. If the form has a non-null planned navigation, remove it from its task queue.
1036        // Note: done by incrementing `planned_navigation`.
1037        self.planned_navigation
1038            .set(self.planned_navigation.get().wrapping_add(1));
1039        let planned_navigation = self.planned_navigation.get();
1040
1041        // Note: we start to use
1042        // the beginnings of an `ongoing_navigation` concept,
1043        // to cancel planned navigations as part of
1044        // <https://html.spec.whatwg.org/multipage/#nav-stop>
1045        //
1046        // The concept of ongoing navigation must be separated from the form's
1047        // planned navigation concept, because each planned navigation cancels the previous one
1048        // for a given form, whereas an ongoing navigation is a per navigable (read: window for now)
1049        // concept.
1050        //
1051        // Setting the ongoing navigation now means the navigation could be cancelled
1052        // even if the below task has not run yet. This is not how the spec is written: it
1053        // seems instead to imply that a `window.stop` should only cancel the navigation
1054        // that has already started (here the task is queued, but the navigation starts only
1055        // in the task). See <https://github.com/whatwg/html/issues/11562>.
1056        let ongoing_navigation = target.set_ongoing_navigation();
1057
1058        let referrer_policy = target.Document().get_referrer_policy();
1059        load_data.creator_pipeline_id = Some(target.pipeline_id());
1060        load_data.referrer = referrer;
1061        load_data.referrer_policy = referrer_policy;
1062
1063        // 4. Queue an element task on the DOM manipulation task source
1064        // given the form element and the following steps:
1065        let form = Trusted::new(self);
1066        let window = Trusted::new(target);
1067        let task = task!(navigate_to_form_planned_navigation: move || {
1068            // 4.1 Set the form's planned navigation to null.
1069            // Note: we implement the equivalent by incrementing the counter above,
1070            // and checking it here.
1071            if planned_navigation != form.root().planned_navigation.get() {
1072                return;
1073            }
1074
1075            // Note: we also check if the navigation has been cancelled,
1076            // see https://github.com/whatwg/html/issues/11562
1077            if ongoing_navigation != window.root().ongoing_navigation() {
1078                return;
1079            }
1080
1081            // 4.2 Navigate targetNavigable to url
1082            window
1083                .root()
1084                .load_url(
1085                    NavigationHistoryBehavior::Push,
1086                    false,
1087                    load_data,
1088                    CanGc::note(),
1089                );
1090        });
1091
1092        // 5. Set the form's planned navigation to the just-queued task.
1093        // Done above as part of incrementing the planned navigation counter.
1094
1095        // Note: task queued here.
1096        target
1097            .global()
1098            .task_manager()
1099            .dom_manipulation_task_source()
1100            .queue(task)
1101    }
1102
1103    /// Interactively validate the constraints of form elements
1104    /// <https://html.spec.whatwg.org/multipage/#interactively-validate-the-constraints>
1105    fn interactive_validation(&self, can_gc: CanGc) -> Result<(), ()> {
1106        // Step 1 - 2: Statically validate the constraints of form,
1107        // and let `unhandled invalid controls` be the list of elements
1108        // returned if the result was negative.
1109        // If the result was positive, then return that result.
1110        let unhandled_invalid_controls = match self.static_validation(can_gc) {
1111            Ok(()) => return Ok(()),
1112            Err(err) => err,
1113        };
1114
1115        // Step 3: Report the problems with the constraints of at least one of the elements
1116        // given in unhandled invalid controls to the user.
1117        let mut first = true;
1118
1119        for elem in unhandled_invalid_controls {
1120            if let Some(validatable) = elem.as_maybe_validatable() {
1121                println!("Validation error: {}", validatable.validation_message());
1122            }
1123            if first {
1124                if let Some(html_elem) = elem.downcast::<HTMLElement>() {
1125                    // Step 3.1: User agents may focus one of those elements in the process,
1126                    // by running the focusing steps for that element,
1127                    // and may change the scrolling position of the document, or perform
1128                    // some other action that brings the element to the user's attention.
1129
1130                    // Here we run focusing steps and scroll element into view.
1131                    html_elem.Focus(&FocusOptions::default(), can_gc);
1132                    first = false;
1133                }
1134            }
1135        }
1136
1137        // If it's form-associated and has a validation anchor, point the
1138        //  user there instead of the element itself.
1139        // Step 4
1140        Err(())
1141    }
1142
1143    /// Statitically validate the constraints of form elements
1144    /// <https://html.spec.whatwg.org/multipage/#statically-validate-the-constraints>
1145    fn static_validation(&self, can_gc: CanGc) -> Result<(), Vec<DomRoot<Element>>> {
1146        // Step 1-3
1147        let invalid_controls = self
1148            .controls
1149            .borrow()
1150            .iter()
1151            .filter_map(|field| {
1152                if let Some(element) = field.downcast::<Element>() {
1153                    if element.is_invalid(true, can_gc) {
1154                        Some(DomRoot::from_ref(element))
1155                    } else {
1156                        None
1157                    }
1158                } else {
1159                    None
1160                }
1161            })
1162            .collect::<Vec<DomRoot<Element>>>();
1163        // Step 4: If invalid controls is empty, then return a positive result.
1164        if invalid_controls.is_empty() {
1165            return Ok(());
1166        }
1167        // Step 5-6
1168        let unhandled_invalid_controls = invalid_controls
1169            .into_iter()
1170            .filter_map(|field| {
1171                // Step 6.1: Let notCanceled be the result of firing an event named invalid at
1172                // field, with the cancelable attribute initialized to true.
1173                let not_canceled = field
1174                    .upcast::<EventTarget>()
1175                    .fire_cancelable_event(atom!("invalid"), can_gc);
1176                // Step 6.2: If notCanceled is true, then add field to unhandled invalid controls.
1177                if not_canceled {
1178                    return Some(field);
1179                }
1180                None
1181            })
1182            .collect::<Vec<DomRoot<Element>>>();
1183        // Step 7
1184        Err(unhandled_invalid_controls)
1185    }
1186
1187    /// <https://html.spec.whatwg.org/multipage/#constructing-the-form-data-set>
1188    /// terminology note:  "form data set" = "entry list"
1189    /// Steps range from 3 to 5
1190    /// 5.x substeps are mostly handled inside element-specific methods
1191    fn get_unclean_dataset(
1192        &self,
1193        submitter: Option<FormSubmitterElement>,
1194        encoding: Option<&'static Encoding>,
1195        can_gc: CanGc,
1196    ) -> Vec<FormDatum> {
1197        let mut data_set = Vec::new();
1198        for child in self.controls.borrow().iter() {
1199            // Step 5.1: The field element is disabled.
1200            if child.disabled_state() {
1201                continue;
1202            }
1203            let child = child.upcast::<Node>();
1204
1205            // Step 5.1: The field element has a datalist element ancestor.
1206            if child.ancestors().any(|a| a.is::<HTMLDataListElement>()) {
1207                continue;
1208            }
1209            if let NodeTypeId::Element(ElementTypeId::HTMLElement(element)) = child.type_id() {
1210                match element {
1211                    HTMLElementTypeId::HTMLInputElement => {
1212                        let input = child.downcast::<HTMLInputElement>().unwrap();
1213                        data_set.append(&mut input.form_datums(submitter, encoding));
1214                    },
1215                    HTMLElementTypeId::HTMLButtonElement => {
1216                        let button = child.downcast::<HTMLButtonElement>().unwrap();
1217                        if let Some(datum) = button.form_datum(submitter) {
1218                            data_set.push(datum);
1219                        }
1220                    },
1221                    HTMLElementTypeId::HTMLObjectElement => {
1222                        // Unimplemented
1223                    },
1224                    HTMLElementTypeId::HTMLSelectElement => {
1225                        let select = child.downcast::<HTMLSelectElement>().unwrap();
1226                        select.push_form_data(&mut data_set);
1227                    },
1228                    HTMLElementTypeId::HTMLTextAreaElement => {
1229                        let textarea = child.downcast::<HTMLTextAreaElement>().unwrap();
1230                        let name = textarea.Name();
1231                        if !name.is_empty() {
1232                            data_set.push(FormDatum {
1233                                ty: textarea.Type(),
1234                                name,
1235                                value: FormDatumValue::String(textarea.Value()),
1236                            });
1237                        }
1238                    },
1239                    HTMLElementTypeId::HTMLElement => {
1240                        let custom = child.downcast::<HTMLElement>().unwrap();
1241                        if custom.is_form_associated_custom_element() {
1242                            // https://html.spec.whatwg.org/multipage/#face-entry-construction
1243                            let internals =
1244                                custom.upcast::<Element>().ensure_element_internals(can_gc);
1245                            internals.perform_entry_construction(&mut data_set);
1246                            // Otherwise no form value has been set so there is nothing to do.
1247                        }
1248                    },
1249                    _ => (),
1250                }
1251            }
1252
1253            // Step: 5.13. Add an entry if element has dirname attribute
1254            // An element can only have a dirname attribute if it is a textarea element
1255            // or an input element whose type attribute is in either the Text state or the Search state
1256            let child_element = child.downcast::<Element>().unwrap();
1257            let input_matches = child_element
1258                .downcast::<HTMLInputElement>()
1259                .is_some_and(|input| {
1260                    matches!(input.input_type(), InputType::Text | InputType::Search)
1261                });
1262            let textarea_matches = child_element.is::<HTMLTextAreaElement>();
1263            let dirname = child_element.get_string_attribute(&local_name!("dirname"));
1264            if (input_matches || textarea_matches) && !dirname.is_empty() {
1265                let dir = DOMString::from(child_element.directionality());
1266                data_set.push(FormDatum {
1267                    ty: DOMString::from("string"),
1268                    name: dirname,
1269                    value: FormDatumValue::String(dir),
1270                });
1271            }
1272        }
1273        data_set
1274    }
1275
1276    /// <https://html.spec.whatwg.org/multipage/#constructing-the-form-data-set>
1277    pub(crate) fn get_form_dataset(
1278        &self,
1279        submitter: Option<FormSubmitterElement>,
1280        encoding: Option<&'static Encoding>,
1281        can_gc: CanGc,
1282    ) -> Option<Vec<FormDatum>> {
1283        // Step 1
1284        if self.constructing_entry_list.get() {
1285            return None;
1286        }
1287
1288        // Step 2
1289        self.constructing_entry_list.set(true);
1290
1291        // Step 3-6
1292        let ret = self.get_unclean_dataset(submitter, encoding, can_gc);
1293
1294        let window = self.owner_window();
1295
1296        // Step 6
1297        let form_data = FormData::new(Some(ret), &window.global(), can_gc);
1298
1299        // Step 7
1300        let event = FormDataEvent::new(
1301            &window,
1302            atom!("formdata"),
1303            EventBubbles::Bubbles,
1304            EventCancelable::NotCancelable,
1305            &form_data,
1306            can_gc,
1307        );
1308
1309        event
1310            .upcast::<Event>()
1311            .fire(self.upcast::<EventTarget>(), can_gc);
1312
1313        // Step 8
1314        self.constructing_entry_list.set(false);
1315
1316        // Step 9
1317        Some(form_data.datums())
1318    }
1319
1320    /// <https://html.spec.whatwg.org/multipage/#dom-form-reset>
1321    pub(crate) fn reset(&self, _reset_method_flag: ResetFrom, can_gc: CanGc) {
1322        // https://html.spec.whatwg.org/multipage/#locked-for-reset
1323        if self.marked_for_reset.get() {
1324            return;
1325        } else {
1326            self.marked_for_reset.set(true);
1327        }
1328
1329        // https://html.spec.whatwg.org/multipage/#concept-form-reset
1330        // Let reset be the result of firing an event named reset at form,
1331        // with the bubbles and cancelable attributes initialized to true.
1332        let reset = self
1333            .upcast::<EventTarget>()
1334            .fire_bubbling_cancelable_event(atom!("reset"), can_gc);
1335        if !reset {
1336            return;
1337        }
1338
1339        let controls: Vec<_> = self
1340            .controls
1341            .borrow()
1342            .iter()
1343            .map(|c| c.as_rooted())
1344            .collect();
1345
1346        for child in controls {
1347            let child = child.upcast::<Node>();
1348
1349            match child.type_id() {
1350                NodeTypeId::Element(ElementTypeId::HTMLElement(
1351                    HTMLElementTypeId::HTMLInputElement,
1352                )) => {
1353                    child.downcast::<HTMLInputElement>().unwrap().reset(can_gc);
1354                },
1355                NodeTypeId::Element(ElementTypeId::HTMLElement(
1356                    HTMLElementTypeId::HTMLSelectElement,
1357                )) => {
1358                    child.downcast::<HTMLSelectElement>().unwrap().reset();
1359                },
1360                NodeTypeId::Element(ElementTypeId::HTMLElement(
1361                    HTMLElementTypeId::HTMLTextAreaElement,
1362                )) => {
1363                    child.downcast::<HTMLTextAreaElement>().unwrap().reset();
1364                },
1365                NodeTypeId::Element(ElementTypeId::HTMLElement(
1366                    HTMLElementTypeId::HTMLOutputElement,
1367                )) => {
1368                    child.downcast::<HTMLOutputElement>().unwrap().reset(can_gc);
1369                },
1370                NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLElement)) => {
1371                    let html_element = child.downcast::<HTMLElement>().unwrap();
1372                    if html_element.is_form_associated_custom_element() {
1373                        ScriptThread::enqueue_callback_reaction(
1374                            html_element.upcast::<Element>(),
1375                            CallbackReaction::FormReset,
1376                            None,
1377                        )
1378                    }
1379                },
1380                _ => {},
1381            }
1382        }
1383        self.marked_for_reset.set(false);
1384    }
1385
1386    fn add_control<T: ?Sized + FormControl>(&self, control: &T, can_gc: CanGc) {
1387        {
1388            let root = self.upcast::<Element>().root_element();
1389            let root = root.upcast::<Node>();
1390            let mut controls = self.controls.borrow_mut();
1391            controls.insert_pre_order(control.to_element(), root);
1392        }
1393        self.update_validity(can_gc);
1394    }
1395
1396    fn remove_control<T: ?Sized + FormControl>(&self, control: &T, can_gc: CanGc) {
1397        {
1398            let control = control.to_element();
1399            let mut controls = self.controls.borrow_mut();
1400            controls
1401                .iter()
1402                .position(|c| &**c == control)
1403                .map(|idx| controls.remove(idx));
1404
1405            // https://html.spec.whatwg.org/multipage#forms.html#the-form-element:past-names-map-5
1406            // "If an element listed in a form element's past names map
1407            // changes form owner, then its entries must be removed
1408            // from that map."
1409            let mut past_names_map = self.past_names_map.borrow_mut();
1410            past_names_map.0.retain(|_k, v| v.0 != control);
1411        }
1412        self.update_validity(can_gc);
1413    }
1414}
1415
1416#[derive(Clone, JSTraceable, MallocSizeOf)]
1417pub(crate) enum FormDatumValue {
1418    #[allow(dead_code)]
1419    File(DomRoot<File>),
1420    String(DOMString),
1421}
1422
1423#[derive(Clone, JSTraceable, MallocSizeOf)]
1424pub(crate) struct FormDatum {
1425    pub(crate) ty: DOMString,
1426    pub(crate) name: DOMString,
1427    pub(crate) value: FormDatumValue,
1428}
1429
1430impl FormDatum {
1431    pub(crate) fn replace_value(&self, charset: &str) -> String {
1432        if self.name.to_ascii_lowercase() == "_charset_" && self.ty == "hidden" {
1433            return charset.to_string();
1434        }
1435
1436        match self.value {
1437            FormDatumValue::File(ref f) => String::from(f.name().clone()),
1438            FormDatumValue::String(ref s) => String::from(s.clone()),
1439        }
1440    }
1441}
1442
1443#[derive(Clone, Copy, MallocSizeOf)]
1444pub(crate) enum FormEncType {
1445    TextPlain,
1446    UrlEncoded,
1447    MultipartFormData,
1448}
1449
1450#[derive(Clone, Copy, MallocSizeOf)]
1451pub(crate) enum FormMethod {
1452    Get,
1453    Post,
1454    Dialog,
1455}
1456
1457/// <https://html.spec.whatwg.org/multipage/#form-associated-element>
1458#[derive(Clone, Copy, MallocSizeOf)]
1459pub(crate) enum FormSubmitterElement<'a> {
1460    Form(&'a HTMLFormElement),
1461    Input(&'a HTMLInputElement),
1462    Button(&'a HTMLButtonElement),
1463    // TODO: implement other types of form associated elements
1464    // (including custom elements) that can be passed as submitter.
1465}
1466
1467impl FormSubmitterElement<'_> {
1468    fn action(&self) -> DOMString {
1469        match *self {
1470            FormSubmitterElement::Form(form) => form.Action(),
1471            FormSubmitterElement::Input(input_element) => input_element.get_form_attribute(
1472                &local_name!("formaction"),
1473                |i| i.FormAction(),
1474                |f| f.Action(),
1475            ),
1476            FormSubmitterElement::Button(button_element) => button_element.get_form_attribute(
1477                &local_name!("formaction"),
1478                |i| i.FormAction(),
1479                |f| f.Action(),
1480            ),
1481        }
1482    }
1483
1484    fn enctype(&self) -> FormEncType {
1485        let attr = match *self {
1486            FormSubmitterElement::Form(form) => form.Enctype(),
1487            FormSubmitterElement::Input(input_element) => input_element.get_form_attribute(
1488                &local_name!("formenctype"),
1489                |i| i.FormEnctype(),
1490                |f| f.Enctype(),
1491            ),
1492            FormSubmitterElement::Button(button_element) => button_element.get_form_attribute(
1493                &local_name!("formenctype"),
1494                |i| i.FormEnctype(),
1495                |f| f.Enctype(),
1496            ),
1497        };
1498        match &*attr.str() {
1499            "multipart/form-data" => FormEncType::MultipartFormData,
1500            "text/plain" => FormEncType::TextPlain,
1501            // https://html.spec.whatwg.org/multipage/#attr-fs-enctype
1502            // urlencoded is the default
1503            _ => FormEncType::UrlEncoded,
1504        }
1505    }
1506
1507    fn method(&self) -> FormMethod {
1508        let attr = match *self {
1509            FormSubmitterElement::Form(form) => form.Method(),
1510            FormSubmitterElement::Input(input_element) => input_element.get_form_attribute(
1511                &local_name!("formmethod"),
1512                |i| i.FormMethod(),
1513                |f| f.Method(),
1514            ),
1515            FormSubmitterElement::Button(button_element) => button_element.get_form_attribute(
1516                &local_name!("formmethod"),
1517                |i| i.FormMethod(),
1518                |f| f.Method(),
1519            ),
1520        };
1521        match &*attr.str() {
1522            "dialog" => FormMethod::Dialog,
1523            "post" => FormMethod::Post,
1524            _ => FormMethod::Get,
1525        }
1526    }
1527
1528    fn target(&self) -> DOMString {
1529        match *self {
1530            FormSubmitterElement::Form(form) => form.Target(),
1531            FormSubmitterElement::Input(input_element) => input_element.get_form_attribute(
1532                &local_name!("formtarget"),
1533                |i| i.FormTarget(),
1534                |f| f.Target(),
1535            ),
1536            FormSubmitterElement::Button(button_element) => button_element.get_form_attribute(
1537                &local_name!("formtarget"),
1538                |i| i.FormTarget(),
1539                |f| f.Target(),
1540            ),
1541        }
1542    }
1543
1544    fn no_validate(&self, _form_owner: &HTMLFormElement) -> bool {
1545        match *self {
1546            FormSubmitterElement::Form(form) => form.NoValidate(),
1547            FormSubmitterElement::Input(input_element) => input_element.get_form_boolean_attribute(
1548                &local_name!("formnovalidate"),
1549                |i| i.FormNoValidate(),
1550                |f| f.NoValidate(),
1551            ),
1552            FormSubmitterElement::Button(button_element) => button_element
1553                .get_form_boolean_attribute(
1554                    &local_name!("formnovalidate"),
1555                    |i| i.FormNoValidate(),
1556                    |f| f.NoValidate(),
1557                ),
1558        }
1559    }
1560
1561    // https://html.spec.whatwg.org/multipage/#concept-submit-button
1562    pub(crate) fn is_submit_button(&self) -> bool {
1563        match *self {
1564            // https://html.spec.whatwg.org/multipage/#image-button-state-(type=image)
1565            // https://html.spec.whatwg.org/multipage/#submit-button-state-(type=submit)
1566            FormSubmitterElement::Input(input_element) => input_element.is_submit_button(),
1567            // https://html.spec.whatwg.org/multipage/#attr-button-type-submit-state
1568            FormSubmitterElement::Button(button_element) => button_element.is_submit_button(),
1569            _ => false,
1570        }
1571    }
1572
1573    // https://html.spec.whatwg.org/multipage/#form-owner
1574    pub(crate) fn form_owner(&self) -> Option<DomRoot<HTMLFormElement>> {
1575        match *self {
1576            FormSubmitterElement::Button(button_el) => button_el.form_owner(),
1577            FormSubmitterElement::Input(input_el) => input_el.form_owner(),
1578            _ => None,
1579        }
1580    }
1581}
1582
1583pub(crate) trait FormControl: DomObject {
1584    fn form_owner(&self) -> Option<DomRoot<HTMLFormElement>>;
1585
1586    fn set_form_owner(&self, form: Option<&HTMLFormElement>);
1587
1588    fn to_element(&self) -> &Element;
1589
1590    fn is_listed(&self) -> bool {
1591        true
1592    }
1593
1594    // https://html.spec.whatwg.org/multipage/#create-an-element-for-the-token
1595    // Part of step 12.
1596    // '..suppress the running of the reset the form owner algorithm
1597    // when the parser subsequently attempts to insert the element..'
1598    fn set_form_owner_from_parser(&self, form: &HTMLFormElement, can_gc: CanGc) {
1599        let elem = self.to_element();
1600        let node = elem.upcast::<Node>();
1601        node.set_flag(NodeFlags::PARSER_ASSOCIATED_FORM_OWNER, true);
1602        form.add_control(self, can_gc);
1603        self.set_form_owner(Some(form));
1604    }
1605
1606    // https://html.spec.whatwg.org/multipage/#reset-the-form-owner
1607    fn reset_form_owner(&self, can_gc: CanGc) {
1608        let elem = self.to_element();
1609        let node = elem.upcast::<Node>();
1610        let old_owner = self.form_owner();
1611        let has_form_id = elem.has_attribute(&local_name!("form"));
1612        let nearest_form_ancestor = node
1613            .ancestors()
1614            .filter_map(DomRoot::downcast::<HTMLFormElement>)
1615            .next();
1616
1617        // Step 1
1618        if old_owner.is_some() &&
1619            !(self.is_listed() && has_form_id) &&
1620            nearest_form_ancestor == old_owner
1621        {
1622            return;
1623        }
1624
1625        let new_owner = if self.is_listed() && has_form_id && elem.is_connected() {
1626            // Step 3
1627            let doc = node.owner_document();
1628            let form_id = elem.get_string_attribute(&local_name!("form"));
1629            doc.GetElementById(form_id)
1630                .and_then(DomRoot::downcast::<HTMLFormElement>)
1631        } else {
1632            // Step 4
1633            nearest_form_ancestor
1634        };
1635
1636        if old_owner != new_owner {
1637            if let Some(o) = old_owner {
1638                o.remove_control(self, can_gc);
1639            }
1640            if let Some(ref new_owner) = new_owner {
1641                new_owner.add_control(self, can_gc);
1642            }
1643            // https://html.spec.whatwg.org/multipage/#custom-element-reactions:reset-the-form-owner
1644            if let Some(html_elem) = elem.downcast::<HTMLElement>() {
1645                if html_elem.is_form_associated_custom_element() {
1646                    ScriptThread::enqueue_callback_reaction(
1647                        elem,
1648                        CallbackReaction::FormAssociated(
1649                            new_owner.as_ref().map(|form| DomRoot::from_ref(&**form)),
1650                        ),
1651                        None,
1652                    )
1653                }
1654            }
1655            self.set_form_owner(new_owner.as_deref());
1656        }
1657    }
1658
1659    // https://html.spec.whatwg.org/multipage/#association-of-controls-and-forms
1660    fn form_attribute_mutated(&self, mutation: AttributeMutation, can_gc: CanGc) {
1661        match mutation {
1662            AttributeMutation::Set(_) => {
1663                self.register_if_necessary();
1664            },
1665            AttributeMutation::Removed => {
1666                self.unregister_if_necessary();
1667            },
1668        }
1669
1670        self.reset_form_owner(can_gc);
1671    }
1672
1673    // https://html.spec.whatwg.org/multipage/#association-of-controls-and-forms
1674    fn register_if_necessary(&self) {
1675        let elem = self.to_element();
1676        let form_id = elem.get_string_attribute(&local_name!("form"));
1677        let node = elem.upcast::<Node>();
1678
1679        if self.is_listed() && !form_id.is_empty() && node.is_connected() {
1680            node.owner_document()
1681                .register_form_id_listener(form_id, self);
1682        }
1683    }
1684
1685    fn unregister_if_necessary(&self) {
1686        let elem = self.to_element();
1687        let form_id = elem.get_string_attribute(&local_name!("form"));
1688
1689        if self.is_listed() && !form_id.is_empty() {
1690            elem.owner_document()
1691                .unregister_form_id_listener(form_id, self);
1692        }
1693    }
1694
1695    // https://html.spec.whatwg.org/multipage/#association-of-controls-and-forms
1696    fn bind_form_control_to_tree(&self, can_gc: CanGc) {
1697        let elem = self.to_element();
1698        let node = elem.upcast::<Node>();
1699
1700        // https://html.spec.whatwg.org/multipage/#create-an-element-for-the-token
1701        // Part of step 12.
1702        // '..suppress the running of the reset the form owner algorithm
1703        // when the parser subsequently attempts to insert the element..'
1704        let must_skip_reset = node.get_flag(NodeFlags::PARSER_ASSOCIATED_FORM_OWNER);
1705        node.set_flag(NodeFlags::PARSER_ASSOCIATED_FORM_OWNER, false);
1706
1707        if !must_skip_reset {
1708            self.form_attribute_mutated(AttributeMutation::Set(None), can_gc);
1709        }
1710    }
1711
1712    // https://html.spec.whatwg.org/multipage/#association-of-controls-and-forms
1713    fn unbind_form_control_from_tree(&self, can_gc: CanGc) {
1714        let elem = self.to_element();
1715        let has_form_attr = elem.has_attribute(&local_name!("form"));
1716        let same_subtree = self
1717            .form_owner()
1718            .is_none_or(|form| elem.is_in_same_home_subtree(&*form));
1719
1720        self.unregister_if_necessary();
1721
1722        // Since this control has been unregistered from the id->listener map
1723        // in the previous step, reset_form_owner will not be invoked on it
1724        // when the form owner element is unbound (i.e it is in the same
1725        // subtree) if it appears later in the tree order. Hence invoke
1726        // reset from here if this control has the form attribute set.
1727        if !same_subtree || (self.is_listed() && has_form_attr) {
1728            self.reset_form_owner(can_gc);
1729        }
1730    }
1731
1732    fn get_form_attribute<InputFn, OwnerFn>(
1733        &self,
1734        attr: &LocalName,
1735        input: InputFn,
1736        owner: OwnerFn,
1737    ) -> DOMString
1738    where
1739        InputFn: Fn(&Self) -> DOMString,
1740        OwnerFn: Fn(&HTMLFormElement) -> DOMString,
1741        Self: Sized,
1742    {
1743        if self.to_element().has_attribute(attr) {
1744            input(self)
1745        } else {
1746            self.form_owner().map_or(DOMString::new(), |t| owner(&t))
1747        }
1748    }
1749
1750    fn get_form_boolean_attribute<InputFn, OwnerFn>(
1751        &self,
1752        attr: &LocalName,
1753        input: InputFn,
1754        owner: OwnerFn,
1755    ) -> bool
1756    where
1757        InputFn: Fn(&Self) -> bool,
1758        OwnerFn: Fn(&HTMLFormElement) -> bool,
1759        Self: Sized,
1760    {
1761        if self.to_element().has_attribute(attr) {
1762            input(self)
1763        } else {
1764            self.form_owner().is_some_and(|t| owner(&t))
1765        }
1766    }
1767
1768    /// <https://html.spec.whatwg.org/multipage/#candidate-for-constraint-validation>
1769    fn is_candidate_for_constraint_validation(&self) -> bool {
1770        let element = self.to_element();
1771        let html_element = element.downcast::<HTMLElement>();
1772        if let Some(html_element) = html_element {
1773            html_element.is_submittable_element() || element.is_instance_validatable()
1774        } else {
1775            false
1776        }
1777    }
1778
1779    // XXXKiChjang: Implement these on inheritors
1780    // fn satisfies_constraints(&self) -> bool;
1781}
1782
1783impl VirtualMethods for HTMLFormElement {
1784    fn super_type(&self) -> Option<&dyn VirtualMethods> {
1785        Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods)
1786    }
1787
1788    fn unbind_from_tree(&self, context: &UnbindContext, can_gc: CanGc) {
1789        self.super_type().unwrap().unbind_from_tree(context, can_gc);
1790
1791        // Collect the controls to reset because reset_form_owner
1792        // will mutably borrow self.controls
1793        rooted_vec!(let mut to_reset);
1794        to_reset.extend(
1795            self.controls
1796                .borrow()
1797                .iter()
1798                .filter(|c| !c.is_in_same_home_subtree(self))
1799                .cloned(),
1800        );
1801
1802        for control in to_reset.iter() {
1803            control
1804                .as_maybe_form_control()
1805                .expect("Element must be a form control")
1806                .reset_form_owner(can_gc);
1807        }
1808    }
1809
1810    fn parse_plain_attribute(&self, name: &LocalName, value: DOMString) -> AttrValue {
1811        match name {
1812            &local_name!("rel") => AttrValue::from_serialized_tokenlist(value.into()),
1813            _ => self
1814                .super_type()
1815                .unwrap()
1816                .parse_plain_attribute(name, value),
1817        }
1818    }
1819
1820    fn attribute_mutated(&self, attr: &Attr, mutation: AttributeMutation, can_gc: CanGc) {
1821        self.super_type()
1822            .unwrap()
1823            .attribute_mutated(attr, mutation, can_gc);
1824
1825        match *attr.local_name() {
1826            local_name!("rel") | local_name!("rev") => {
1827                self.relations
1828                    .set(LinkRelations::for_element(self.upcast()));
1829            },
1830            _ => {},
1831        }
1832    }
1833
1834    fn bind_to_tree(&self, context: &BindContext, can_gc: CanGc) {
1835        if let Some(s) = self.super_type() {
1836            s.bind_to_tree(context, can_gc);
1837        }
1838
1839        self.relations
1840            .set(LinkRelations::for_element(self.upcast()));
1841    }
1842}
1843
1844pub(crate) trait FormControlElementHelpers {
1845    fn as_maybe_form_control(&self) -> Option<&dyn FormControl>;
1846}
1847
1848impl FormControlElementHelpers for Element {
1849    fn as_maybe_form_control(&self) -> Option<&dyn FormControl> {
1850        let node = self.upcast::<Node>();
1851
1852        match node.type_id() {
1853            NodeTypeId::Element(ElementTypeId::HTMLElement(
1854                HTMLElementTypeId::HTMLButtonElement,
1855            )) => Some(self.downcast::<HTMLButtonElement>().unwrap() as &dyn FormControl),
1856            NodeTypeId::Element(ElementTypeId::HTMLElement(
1857                HTMLElementTypeId::HTMLFieldSetElement,
1858            )) => Some(self.downcast::<HTMLFieldSetElement>().unwrap() as &dyn FormControl),
1859            NodeTypeId::Element(ElementTypeId::HTMLElement(
1860                HTMLElementTypeId::HTMLImageElement,
1861            )) => Some(self.downcast::<HTMLImageElement>().unwrap() as &dyn FormControl),
1862            NodeTypeId::Element(ElementTypeId::HTMLElement(
1863                HTMLElementTypeId::HTMLInputElement,
1864            )) => Some(self.downcast::<HTMLInputElement>().unwrap() as &dyn FormControl),
1865            NodeTypeId::Element(ElementTypeId::HTMLElement(
1866                HTMLElementTypeId::HTMLLabelElement,
1867            )) => Some(self.downcast::<HTMLLabelElement>().unwrap() as &dyn FormControl),
1868            NodeTypeId::Element(ElementTypeId::HTMLElement(
1869                HTMLElementTypeId::HTMLLegendElement,
1870            )) => Some(self.downcast::<HTMLLegendElement>().unwrap() as &dyn FormControl),
1871            NodeTypeId::Element(ElementTypeId::HTMLElement(
1872                HTMLElementTypeId::HTMLObjectElement,
1873            )) => Some(self.downcast::<HTMLObjectElement>().unwrap() as &dyn FormControl),
1874            NodeTypeId::Element(ElementTypeId::HTMLElement(
1875                HTMLElementTypeId::HTMLOutputElement,
1876            )) => Some(self.downcast::<HTMLOutputElement>().unwrap() as &dyn FormControl),
1877            NodeTypeId::Element(ElementTypeId::HTMLElement(
1878                HTMLElementTypeId::HTMLSelectElement,
1879            )) => Some(self.downcast::<HTMLSelectElement>().unwrap() as &dyn FormControl),
1880            NodeTypeId::Element(ElementTypeId::HTMLElement(
1881                HTMLElementTypeId::HTMLTextAreaElement,
1882            )) => Some(self.downcast::<HTMLTextAreaElement>().unwrap() as &dyn FormControl),
1883            _ => self.downcast::<HTMLElement>().and_then(|elem| {
1884                if elem.is_form_associated_custom_element() {
1885                    Some(elem as &dyn FormControl)
1886                } else {
1887                    None
1888                }
1889            }),
1890        }
1891    }
1892}
1893
1894/// <https://html.spec.whatwg.org/multipage/#multipart/form-data-encoding-algorithm>
1895pub(crate) fn encode_multipart_form_data(
1896    form_data: &mut [FormDatum],
1897    boundary: String,
1898    encoding: &'static Encoding,
1899) -> Vec<u8> {
1900    let mut result = vec![];
1901
1902    // Newline replacement routine as described in Step 1
1903    fn clean_crlf(s: &DOMString) -> DOMString {
1904        let mut buf = "".to_owned();
1905        let mut prev = ' ';
1906        for ch in s.str().chars() {
1907            match ch {
1908                '\n' if prev != '\r' => {
1909                    buf.push('\r');
1910                    buf.push('\n');
1911                },
1912                '\n' => {
1913                    buf.push('\n');
1914                },
1915                // This character isn't LF but is
1916                // preceded by CR
1917                _ if prev == '\r' => {
1918                    buf.push('\r');
1919                    buf.push('\n');
1920                    buf.push(ch);
1921                },
1922                _ => buf.push(ch),
1923            };
1924            prev = ch;
1925        }
1926        // In case the last character was CR
1927        if prev == '\r' {
1928            buf.push('\n');
1929        }
1930        DOMString::from(buf)
1931    }
1932
1933    for entry in form_data.iter_mut() {
1934        // Step 1.1: Perform newline replacement on entry's name
1935        entry.name = clean_crlf(&entry.name);
1936
1937        // Step 1.2: If entry's value is not a File object, perform newline replacement on entry's
1938        // value
1939        if let FormDatumValue::String(ref s) = entry.value {
1940            entry.value = FormDatumValue::String(clean_crlf(s));
1941        }
1942
1943        // Step 2: Return the byte sequence resulting from encoding the entry list.
1944        // https://tools.ietf.org/html/rfc7578#section-4
1945        // NOTE(izgzhen): The encoding here expected by most servers seems different from
1946        // what spec says (that it should start with a '\r\n').
1947        let mut boundary_bytes = format!("--{}\r\n", boundary).into_bytes();
1948        result.append(&mut boundary_bytes);
1949
1950        // TODO(eijebong): Everthing related to content-disposition it to redo once typed headers
1951        // are capable of it.
1952        match entry.value {
1953            FormDatumValue::String(ref s) => {
1954                let content_disposition = format!("form-data; name=\"{}\"", entry.name);
1955                let mut bytes =
1956                    format!("Content-Disposition: {}\r\n\r\n{}", content_disposition, s)
1957                        .into_bytes();
1958                result.append(&mut bytes);
1959            },
1960            FormDatumValue::File(ref f) => {
1961                let charset = encoding.name();
1962                let extra = if charset.to_lowercase() == "utf-8" {
1963                    format!("filename=\"{}\"", String::from(f.name().str()))
1964                } else {
1965                    format!(
1966                        "filename*=\"{}\"''{}",
1967                        charset,
1968                        http_percent_encode(&f.name().as_bytes())
1969                    )
1970                };
1971
1972                let content_disposition = format!("form-data; name=\"{}\"; {}", entry.name, extra);
1973                // https://tools.ietf.org/html/rfc7578#section-4.4
1974                let content_type: Mime = f
1975                    .upcast::<Blob>()
1976                    .Type()
1977                    .parse()
1978                    .unwrap_or(mime::TEXT_PLAIN);
1979                let mut type_bytes = format!(
1980                    "Content-Disposition: {}\r\ncontent-type: {}\r\n\r\n",
1981                    content_disposition, content_type
1982                )
1983                .into_bytes();
1984                result.append(&mut type_bytes);
1985
1986                let mut bytes = f.upcast::<Blob>().get_bytes().unwrap_or(vec![]);
1987
1988                result.append(&mut bytes);
1989            },
1990        }
1991    }
1992
1993    let mut boundary_bytes = format!("\r\n--{}--\r\n", boundary).into_bytes();
1994    result.append(&mut boundary_bytes);
1995
1996    result
1997}
1998
1999// https://tools.ietf.org/html/rfc7578#section-4.1
2000pub(crate) fn generate_boundary() -> String {
2001    let i1 = random::<u32>();
2002    let i2 = random::<u32>();
2003
2004    format!("---------------------------{0}{1}", i1, i2)
2005}