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