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