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, valid_navigable_target_name_or_keyword};
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    #[expect(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    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(c"submitter must be a submit button".to_owned()));
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    fn SupportedPropertyNames(&self) -> Vec<DOMString> {
522        // Step 1
523        #[derive(Debug, Eq, Ord, PartialEq, PartialOrd)]
524        enum SourcedNameSource {
525            Id,
526            Name,
527            Past(usize),
528        }
529
530        impl SourcedNameSource {
531            fn is_past(&self) -> bool {
532                matches!(self, SourcedNameSource::Past(..))
533            }
534        }
535
536        struct SourcedName {
537            name: Atom,
538            element: DomRoot<Element>,
539            source: SourcedNameSource,
540        }
541
542        let mut sourced_names_vec: Vec<SourcedName> = Vec::new();
543
544        // Step 2
545        for child in self.controls.borrow().iter() {
546            if child
547                .downcast::<HTMLElement>()
548                .is_some_and(|c| c.is_listed_element())
549            {
550                if let Some(id_atom) = child.get_id() {
551                    let entry = SourcedName {
552                        name: id_atom,
553                        element: DomRoot::from_ref(child),
554                        source: SourcedNameSource::Id,
555                    };
556                    sourced_names_vec.push(entry);
557                }
558                if let Some(name_atom) = child.get_name() {
559                    let entry = SourcedName {
560                        name: name_atom,
561                        element: DomRoot::from_ref(child),
562                        source: SourcedNameSource::Name,
563                    };
564                    sourced_names_vec.push(entry);
565                }
566            }
567        }
568
569        // Step 3
570        for child in self.controls.borrow().iter() {
571            if child.is::<HTMLImageElement>() {
572                if let Some(id_atom) = child.get_id() {
573                    let entry = SourcedName {
574                        name: id_atom,
575                        element: DomRoot::from_ref(child),
576                        source: SourcedNameSource::Id,
577                    };
578                    sourced_names_vec.push(entry);
579                }
580                if let Some(name_atom) = child.get_name() {
581                    let entry = SourcedName {
582                        name: name_atom,
583                        element: DomRoot::from_ref(child),
584                        source: SourcedNameSource::Name,
585                    };
586                    sourced_names_vec.push(entry);
587                }
588            }
589        }
590
591        // Step 4
592        let past_names_map = self.past_names_map.borrow();
593        for (key, val) in past_names_map.iter() {
594            let entry = SourcedName {
595                name: key.clone(),
596                element: DomRoot::from_ref(&*val.0),
597                source: SourcedNameSource::Past(self.current_name_generation.get() - val.1.0),
598            };
599            sourced_names_vec.push(entry);
600        }
601
602        // Step 5
603        // TODO need to sort as per spec.
604        // if a.CompareDocumentPosition(b) returns 0 that means a=b in which case
605        // the remaining part where sorting is to be done by putting entries whose source is id first,
606        // then entries whose source is name, and finally entries whose source is past,
607        // and sorting entries with the same element and source by their age, oldest first.
608
609        // if a.CompareDocumentPosition(b) has set NodeConstants::DOCUMENT_POSITION_FOLLOWING
610        // (this can be checked by bitwise operations) then b would follow a in tree order and
611        // Ordering::Less should be returned in the closure else Ordering::Greater
612
613        sourced_names_vec.sort_by(|a, b| {
614            if a.element
615                .upcast::<Node>()
616                .CompareDocumentPosition(b.element.upcast::<Node>()) ==
617                0
618            {
619                if a.source.is_past() && b.source.is_past() {
620                    b.source.cmp(&a.source)
621                } else {
622                    a.source.cmp(&b.source)
623                }
624            } else if a
625                .element
626                .upcast::<Node>()
627                .CompareDocumentPosition(b.element.upcast::<Node>()) &
628                NodeConstants::DOCUMENT_POSITION_FOLLOWING ==
629                NodeConstants::DOCUMENT_POSITION_FOLLOWING
630            {
631                std::cmp::Ordering::Less
632            } else {
633                std::cmp::Ordering::Greater
634            }
635        });
636
637        // Step 6
638        sourced_names_vec.retain(|sn| !sn.name.to_string().is_empty());
639
640        // Step 7-8
641        let mut names_vec: Vec<DOMString> = Vec::new();
642        for elem in sourced_names_vec.iter() {
643            if !names_vec.iter().any(|name| *name == *elem.name) {
644                names_vec.push(DOMString::from(&*elem.name));
645            }
646        }
647
648        names_vec
649    }
650
651    /// <https://html.spec.whatwg.org/multipage/#dom-form-checkvalidity>
652    fn CheckValidity(&self, can_gc: CanGc) -> bool {
653        self.static_validation(can_gc).is_ok()
654    }
655
656    /// <https://html.spec.whatwg.org/multipage/#dom-form-reportvalidity>
657    fn ReportValidity(&self, can_gc: CanGc) -> bool {
658        self.interactive_validation(can_gc).is_ok()
659    }
660}
661
662#[derive(Clone, Copy, MallocSizeOf, PartialEq)]
663pub(crate) enum SubmittedFrom {
664    FromForm,
665    NotFromForm,
666}
667
668#[derive(Clone, Copy, MallocSizeOf)]
669pub(crate) enum ResetFrom {
670    FromForm,
671    NotFromForm,
672}
673
674impl HTMLFormElement {
675    /// <https://html.spec.whatwg.org/multipage/#picking-an-encoding-for-the-form>
676    fn pick_encoding(&self) -> &'static Encoding {
677        // Step 2
678        if self
679            .upcast::<Element>()
680            .has_attribute(&local_name!("accept-charset"))
681        {
682            // Substep 1
683            let input = self
684                .upcast::<Element>()
685                .get_string_attribute(&local_name!("accept-charset"));
686
687            // Substep 2, 3, 4
688            let input = input.str();
689            let mut candidate_encodings =
690                split_html_space_chars(&input).filter_map(|c| Encoding::for_label(c.as_bytes()));
691
692            // Substep 5, 6
693            return candidate_encodings.next().unwrap_or(UTF_8);
694        }
695
696        // Step 1, 3
697        self.owner_document().encoding()
698    }
699
700    /// <https://html.spec.whatwg.org/multipage/#text/plain-encoding-algorithm>
701    fn encode_plaintext(&self, form_data: &mut [FormDatum]) -> String {
702        // Step 1
703        let mut result = String::new();
704
705        // Step 2
706        for entry in form_data.iter() {
707            let value = match &entry.value {
708                FormDatumValue::File(f) => f.name(),
709                FormDatumValue::String(s) => s,
710            };
711            result.push_str(&format!("{}={}\r\n", entry.name, value));
712        }
713
714        // Step 3
715        result
716    }
717
718    pub(crate) fn update_validity(&self, can_gc: CanGc) {
719        let is_any_invalid = self
720            .controls
721            .borrow()
722            .iter()
723            .any(|control| control.is_invalid(false, can_gc));
724
725        self.upcast::<Element>()
726            .set_state(ElementState::VALID, !is_any_invalid);
727        self.upcast::<Element>()
728            .set_state(ElementState::INVALID, is_any_invalid);
729    }
730
731    /// [Form submission](https://html.spec.whatwg.org/multipage/#concept-form-submit)
732    pub(crate) fn submit(
733        &self,
734        submit_method_flag: SubmittedFrom,
735        submitter: FormSubmitterElement,
736        can_gc: CanGc,
737    ) {
738        // Step 1
739        if self.upcast::<Element>().cannot_navigate() {
740            return;
741        }
742
743        // Step 2
744        if self.constructing_entry_list.get() {
745            return;
746        }
747        // Step 3. Let form document be form's node document.
748        let doc = self.owner_document();
749
750        // Step 4. If form document's active sandboxing flag set has its sandboxed forms browsing
751        // context flag set, then return.
752        if doc.has_active_sandboxing_flag(SandboxingFlagSet::SANDBOXED_FORMS_BROWSING_CONTEXT_FLAG)
753        {
754            return;
755        }
756
757        let base = doc.base_url();
758        // TODO: Handle browsing contexts (Step 5)
759        // Step 6
760        if submit_method_flag == SubmittedFrom::NotFromForm {
761            // Step 6.1
762            if self.firing_submission_events.get() {
763                return;
764            }
765            // Step 6.2
766            self.firing_submission_events.set(true);
767            // Step 6.3
768            if !submitter.no_validate(self) && self.interactive_validation(can_gc).is_err() {
769                self.firing_submission_events.set(false);
770                return;
771            }
772            // Step 6.4
773            // spec calls this "submitterButton" but it doesn't have to be a button,
774            // just not be the form itself
775            let submitter_button = match submitter {
776                FormSubmitterElement::Form(f) => {
777                    if f == self {
778                        None
779                    } else {
780                        Some(f.upcast::<HTMLElement>())
781                    }
782                },
783                FormSubmitterElement::Input(i) => Some(i.upcast::<HTMLElement>()),
784                FormSubmitterElement::Button(b) => Some(b.upcast::<HTMLElement>()),
785            };
786
787            // Step 6.5
788            let event = SubmitEvent::new(
789                self.global().as_window(),
790                atom!("submit"),
791                true,
792                true,
793                submitter_button.map(DomRoot::from_ref),
794                can_gc,
795            );
796            let event = event.upcast::<Event>();
797            event.fire(self.upcast::<EventTarget>(), can_gc);
798
799            // Step 6.6
800            self.firing_submission_events.set(false);
801            // Step 6.7
802            if event.DefaultPrevented() {
803                return;
804            }
805            // Step 6.8
806            if self.upcast::<Element>().cannot_navigate() {
807                return;
808            }
809        }
810
811        // Step 7
812        let encoding = self.pick_encoding();
813
814        // Step 8
815        let mut form_data = match self.get_form_dataset(Some(submitter), Some(encoding), can_gc) {
816            Some(form_data) => form_data,
817            None => return,
818        };
819
820        // Step 9. If form cannot navigate, then return.
821        if self.upcast::<Element>().cannot_navigate() {
822            return;
823        }
824
825        // Step 10. Let method be the submitter element's method.
826        let method = submitter.method();
827        // Step 11. If method is dialog, then:
828        // TODO
829
830        // Step 12. Let action be the submitter element's action.
831        let mut action = submitter.action();
832
833        // Step 13. If action is the empty string, let action be the URL of the form document.
834        if action.is_empty() {
835            action = DOMString::from(base.as_str());
836        }
837        // Step 14. Let parsed action be the result of encoding-parsing a URL given action, relative to submitter's node document.
838        let action_components = match base.join(&action.str()) {
839            Ok(url) => url,
840            // Step 15. If parsed action is failure, then return.
841            Err(_) => return,
842        };
843        // Step 16. Let scheme be the scheme of parsed action.
844        let scheme = action_components.scheme().to_owned();
845        // Step 17. Let enctype be the submitter element's enctype.
846        let enctype = submitter.enctype();
847
848        // Step 19. If the submitter element is a submit button and it has a formtarget attribute,
849        // then set formTarget to the formtarget attribute value.
850        let form_target_attribute = submitter.target();
851        let form_target = if submitter.is_submit_button() &&
852            valid_navigable_target_name_or_keyword(&form_target_attribute)
853        {
854            Some(form_target_attribute)
855        } else {
856            // Step 18. Let formTarget be null.
857            None
858        };
859        // Step 20. Let target be the result of getting an element's target given submitter's form owner and formTarget.
860        let form_owner = submitter.form_owner();
861        let form = form_owner.as_deref().unwrap_or(self);
862        let target = get_element_target(form.upcast::<Element>(), form_target);
863
864        // Step 21. Let noopener be the result of getting an element's noopener with form, parsed action, and target.
865        let noopener = self.relations.get().get_element_noopener(target.as_ref());
866
867        // Step 22. Let targetNavigable be the first return value of applying the rules for choosing a navigable given target,
868        // form's node navigable, and noopener.
869        let source = doc.browsing_context().unwrap();
870        let (maybe_chosen, _new) =
871            source.choose_browsing_context(target.unwrap_or_default(), noopener);
872
873        let Some(chosen) = maybe_chosen else {
874            // Step 23. If targetNavigable is null, then return.
875            return;
876        };
877        // Step 24. Let historyHandling be "auto".
878        // TODO
879        // Step 25. If form document equals targetNavigable's active document, and form document has not yet completely loaded,
880        // then set historyHandling to "replace".
881        // TODO
882
883        let target_document = match chosen.document() {
884            Some(doc) => doc,
885            None => return,
886        };
887        let target_window = target_document.window();
888        let mut load_data = LoadData::new(
889            LoadOrigin::Script(doc.origin().snapshot()),
890            action_components,
891            target_document.about_base_url(),
892            None,
893            target_window.as_global_scope().get_referrer(),
894            target_document.get_referrer_policy(),
895            Some(target_window.as_global_scope().is_secure_context()),
896            Some(target_document.insecure_requests_policy()),
897            target_document.has_trustworthy_ancestor_origin(),
898            target_document.creation_sandboxing_flag_set_considering_parent_iframe(),
899        );
900
901        // Step 26. Select the appropriate row in the table below based on scheme as given by the first cell of each row.
902        // Then, select the appropriate cell on that row based on method as given in the first cell of each column.
903        // Then, jump to the steps named in that cell and defined below the table.
904        match (&*scheme, method) {
905            (_, FormMethod::Dialog) => {
906                // TODO: Submit dialog
907                // https://html.spec.whatwg.org/multipage/#submit-dialog
908            },
909            // https://html.spec.whatwg.org/multipage/#submit-mutate-action
910            ("http", FormMethod::Get) | ("https", FormMethod::Get) | ("data", FormMethod::Get) => {
911                load_data
912                    .headers
913                    .typed_insert(ContentType::from(mime::APPLICATION_WWW_FORM_URLENCODED));
914                self.mutate_action_url(&mut form_data, load_data, encoding, target_window);
915            },
916            // https://html.spec.whatwg.org/multipage/#submit-body
917            ("http", FormMethod::Post) | ("https", FormMethod::Post) => {
918                load_data.method = Method::POST;
919                self.submit_entity_body(
920                    &mut form_data,
921                    load_data,
922                    enctype,
923                    encoding,
924                    target_window,
925                    can_gc,
926                );
927            },
928            // https://html.spec.whatwg.org/multipage/#submit-get-action
929            ("file", _) |
930            ("about", _) |
931            ("data", FormMethod::Post) |
932            ("ftp", _) |
933            ("javascript", _) => {
934                self.plan_to_navigate(load_data, target_window);
935            },
936            ("mailto", FormMethod::Post) => {
937                // TODO: Mail as body
938                // https://html.spec.whatwg.org/multipage/#submit-mailto-body
939            },
940            ("mailto", FormMethod::Get) => {
941                // TODO: Mail with headers
942                // https://html.spec.whatwg.org/multipage/#submit-mailto-headers
943            },
944            _ => (),
945        }
946    }
947
948    /// <https://html.spec.whatwg.org/multipage/#submit-mutate-action>
949    fn mutate_action_url(
950        &self,
951        form_data: &mut [FormDatum],
952        mut load_data: LoadData,
953        encoding: &'static Encoding,
954        target: &Window,
955    ) {
956        let charset = encoding.name();
957
958        self.set_url_query_pairs(
959            &mut load_data.url,
960            form_data
961                .iter()
962                .map(|field| (field.name.str(), field.replace_value(charset))),
963        );
964
965        self.plan_to_navigate(load_data, target);
966    }
967
968    /// <https://html.spec.whatwg.org/multipage/#submit-body>
969    fn submit_entity_body(
970        &self,
971        form_data: &mut [FormDatum],
972        mut load_data: LoadData,
973        enctype: FormEncType,
974        encoding: &'static Encoding,
975        target: &Window,
976        can_gc: CanGc,
977    ) {
978        let boundary = generate_boundary();
979        let bytes = match enctype {
980            FormEncType::UrlEncoded => {
981                let charset = encoding.name();
982                load_data
983                    .headers
984                    .typed_insert(ContentType::from(mime::APPLICATION_WWW_FORM_URLENCODED));
985
986                let mut url = load_data.url.clone();
987                self.set_url_query_pairs(
988                    &mut url,
989                    form_data
990                        .iter()
991                        .map(|field| (field.name.str(), field.replace_value(charset))),
992                );
993
994                url.query().unwrap_or("").to_string().into_bytes()
995            },
996            FormEncType::MultipartFormData => {
997                let mime: Mime = format!("multipart/form-data; boundary={}", boundary)
998                    .parse()
999                    .unwrap();
1000                load_data.headers.typed_insert(ContentType::from(mime));
1001                encode_multipart_form_data(form_data, boundary, encoding)
1002            },
1003            FormEncType::TextPlain => {
1004                load_data
1005                    .headers
1006                    .typed_insert(ContentType::from(mime::TEXT_PLAIN));
1007                self.encode_plaintext(form_data).into_bytes()
1008            },
1009        };
1010
1011        let global = self.global();
1012
1013        let request_body = bytes
1014            .extract(&global, false, can_gc)
1015            .expect("Couldn't extract body.")
1016            .into_net_request_body()
1017            .0;
1018        load_data.data = Some(request_body);
1019
1020        self.plan_to_navigate(load_data, target);
1021    }
1022
1023    fn set_url_query_pairs<T>(
1024        &self,
1025        url: &mut servo_url::ServoUrl,
1026        pairs: impl Iterator<Item = (T, String)>,
1027    ) where
1028        T: AsRef<str>,
1029    {
1030        let encoding = self.pick_encoding();
1031        url.as_mut_url()
1032            .query_pairs_mut()
1033            .encoding_override(Some(&|s| encoding.encode(s).0))
1034            .clear()
1035            .extend_pairs(pairs);
1036    }
1037
1038    /// [Planned navigation](https://html.spec.whatwg.org/multipage/#planned-navigation)
1039    fn plan_to_navigate(&self, mut load_data: LoadData, target: &Window) {
1040        // 1. Let referrerPolicy be the empty string.
1041        // 2. If the form element's link types include the noreferrer keyword,
1042        //    then set referrerPolicy to "no-referrer".
1043        // Note: both steps done below.
1044        let elem = self.upcast::<Element>();
1045        let referrer = match elem.get_attribute(&ns!(), &local_name!("rel")) {
1046            Some(ref link_types) if link_types.Value().contains("noreferrer") => {
1047                Referrer::NoReferrer
1048            },
1049            _ => target.as_global_scope().get_referrer(),
1050        };
1051
1052        // 3. If the form has a non-null planned navigation, remove it from its task queue.
1053        // Note: done by incrementing `planned_navigation`.
1054        self.planned_navigation
1055            .set(self.planned_navigation.get().wrapping_add(1));
1056        let planned_navigation = self.planned_navigation.get();
1057
1058        // Note: we start to use
1059        // the beginnings of an `ongoing_navigation` concept,
1060        // to cancel planned navigations as part of
1061        // <https://html.spec.whatwg.org/multipage/#nav-stop>
1062        //
1063        // The concept of ongoing navigation must be separated from the form's
1064        // planned navigation concept, because each planned navigation cancels the previous one
1065        // for a given form, whereas an ongoing navigation is a per navigable (read: window for now)
1066        // concept.
1067        //
1068        // Setting the ongoing navigation now means the navigation could be cancelled
1069        // even if the below task has not run yet. This is not how the spec is written: it
1070        // seems instead to imply that a `window.stop` should only cancel the navigation
1071        // that has already started (here the task is queued, but the navigation starts only
1072        // in the task). See <https://github.com/whatwg/html/issues/11562>.
1073        let ongoing_navigation = target.set_ongoing_navigation();
1074
1075        let referrer_policy = target.Document().get_referrer_policy();
1076        load_data.creator_pipeline_id = Some(target.pipeline_id());
1077        load_data.referrer = referrer;
1078        load_data.referrer_policy = referrer_policy;
1079
1080        // Note the pending form navigation if this is an iframe;
1081        // necessary for deciding whether to run the iframe load event steps.
1082        if let Some(window_proxy) = target.undiscarded_window_proxy() {
1083            if let Some(frame) = window_proxy
1084                .frame_element()
1085                .and_then(|e| e.downcast::<HTMLIFrameElement>())
1086            {
1087                frame.note_pending_navigation()
1088            }
1089        }
1090
1091        // 4. Queue an element task on the DOM manipulation task source
1092        // given the form element and the following steps:
1093        let form = Trusted::new(self);
1094        let window = Trusted::new(target);
1095        let task = task!(navigate_to_form_planned_navigation: move || {
1096            // 4.1 Set the form's planned navigation to null.
1097            // Note: we implement the equivalent by incrementing the counter above,
1098            // and checking it here.
1099            if planned_navigation != form.root().planned_navigation.get() {
1100                return;
1101            }
1102
1103            // Note: we also check if the navigation has been cancelled,
1104            // see https://github.com/whatwg/html/issues/11562
1105            if ongoing_navigation != window.root().ongoing_navigation() {
1106                return;
1107            }
1108
1109            // 4.2 Navigate targetNavigable to url
1110            window
1111                .root()
1112                .load_url(
1113                    NavigationHistoryBehavior::Push,
1114                    false,
1115                    load_data,
1116                    CanGc::note(),
1117                );
1118        });
1119
1120        // 5. Set the form's planned navigation to the just-queued task.
1121        // Done above as part of incrementing the planned navigation counter.
1122
1123        // Note: task queued here.
1124        target
1125            .global()
1126            .task_manager()
1127            .dom_manipulation_task_source()
1128            .queue(task)
1129    }
1130
1131    /// Interactively validate the constraints of form elements
1132    /// <https://html.spec.whatwg.org/multipage/#interactively-validate-the-constraints>
1133    fn interactive_validation(&self, can_gc: CanGc) -> Result<(), ()> {
1134        // Step 1 - 2: Statically validate the constraints of form,
1135        // and let `unhandled invalid controls` be the list of elements
1136        // returned if the result was negative.
1137        // If the result was positive, then return that result.
1138        let unhandled_invalid_controls = match self.static_validation(can_gc) {
1139            Ok(()) => return Ok(()),
1140            Err(err) => err,
1141        };
1142
1143        // Step 3: Report the problems with the constraints of at least one of the elements
1144        // given in unhandled invalid controls to the user.
1145        let mut first = true;
1146
1147        for elem in unhandled_invalid_controls {
1148            if let Some(validatable) = elem.as_maybe_validatable() {
1149                error!("Validation error: {}", validatable.validation_message());
1150            }
1151            if first {
1152                if let Some(html_elem) = elem.downcast::<HTMLElement>() {
1153                    // Step 3.1: User agents may focus one of those elements in the process,
1154                    // by running the focusing steps for that element,
1155                    // and may change the scrolling position of the document, or perform
1156                    // some other action that brings the element to the user's attention.
1157
1158                    // Here we run focusing steps and scroll element into view.
1159                    html_elem.Focus(&FocusOptions::default(), can_gc);
1160                    first = false;
1161                }
1162            }
1163        }
1164
1165        // If it's form-associated and has a validation anchor, point the
1166        //  user there instead of the element itself.
1167        // Step 4
1168        Err(())
1169    }
1170
1171    /// Statitically validate the constraints of form elements
1172    /// <https://html.spec.whatwg.org/multipage/#statically-validate-the-constraints>
1173    fn static_validation(&self, can_gc: CanGc) -> Result<(), Vec<DomRoot<Element>>> {
1174        // Step 1-3
1175        let invalid_controls = self
1176            .controls
1177            .borrow()
1178            .iter()
1179            .filter_map(|field| {
1180                if let Some(element) = field.downcast::<Element>() {
1181                    if element.is_invalid(true, can_gc) {
1182                        Some(DomRoot::from_ref(element))
1183                    } else {
1184                        None
1185                    }
1186                } else {
1187                    None
1188                }
1189            })
1190            .collect::<Vec<DomRoot<Element>>>();
1191        // Step 4: If invalid controls is empty, then return a positive result.
1192        if invalid_controls.is_empty() {
1193            return Ok(());
1194        }
1195        // Step 5-6
1196        let unhandled_invalid_controls = invalid_controls
1197            .into_iter()
1198            .filter_map(|field| {
1199                // Step 6.1: Let notCanceled be the result of firing an event named invalid at
1200                // field, with the cancelable attribute initialized to true.
1201                let not_canceled = field
1202                    .upcast::<EventTarget>()
1203                    .fire_cancelable_event(atom!("invalid"), can_gc);
1204                // Step 6.2: If notCanceled is true, then add field to unhandled invalid controls.
1205                if not_canceled {
1206                    return Some(field);
1207                }
1208                None
1209            })
1210            .collect::<Vec<DomRoot<Element>>>();
1211        // Step 7
1212        Err(unhandled_invalid_controls)
1213    }
1214
1215    /// <https://html.spec.whatwg.org/multipage/#constructing-the-form-data-set>
1216    /// terminology note:  "form data set" = "entry list"
1217    /// Steps range from 3 to 5
1218    /// 5.x substeps are mostly handled inside element-specific methods
1219    fn get_unclean_dataset(
1220        &self,
1221        submitter: Option<FormSubmitterElement>,
1222        encoding: Option<&'static Encoding>,
1223        can_gc: CanGc,
1224    ) -> Vec<FormDatum> {
1225        let mut data_set = Vec::new();
1226        for child in self.controls.borrow().iter() {
1227            // Step 5.1: The field element is disabled.
1228            if child.disabled_state() {
1229                continue;
1230            }
1231            let child = child.upcast::<Node>();
1232
1233            // Step 5.1: The field element has a datalist element ancestor.
1234            if child.ancestors().any(|a| a.is::<HTMLDataListElement>()) {
1235                continue;
1236            }
1237            if let NodeTypeId::Element(ElementTypeId::HTMLElement(element)) = child.type_id() {
1238                match element {
1239                    HTMLElementTypeId::HTMLInputElement => {
1240                        let input = child.downcast::<HTMLInputElement>().unwrap();
1241                        data_set.append(&mut input.form_datums(submitter, encoding));
1242                    },
1243                    HTMLElementTypeId::HTMLButtonElement => {
1244                        let button = child.downcast::<HTMLButtonElement>().unwrap();
1245                        if let Some(datum) = button.form_datum(submitter) {
1246                            data_set.push(datum);
1247                        }
1248                    },
1249                    HTMLElementTypeId::HTMLObjectElement => {
1250                        // Unimplemented
1251                    },
1252                    HTMLElementTypeId::HTMLSelectElement => {
1253                        let select = child.downcast::<HTMLSelectElement>().unwrap();
1254                        select.push_form_data(&mut data_set);
1255                    },
1256                    HTMLElementTypeId::HTMLTextAreaElement => {
1257                        let textarea = child.downcast::<HTMLTextAreaElement>().unwrap();
1258                        let name = textarea.Name();
1259                        if !name.is_empty() {
1260                            data_set.push(FormDatum {
1261                                ty: textarea.Type(),
1262                                name,
1263                                value: FormDatumValue::String(textarea.Value()),
1264                            });
1265                        }
1266                    },
1267                    HTMLElementTypeId::HTMLElement => {
1268                        let custom = child.downcast::<HTMLElement>().unwrap();
1269                        if custom.is_form_associated_custom_element() {
1270                            // https://html.spec.whatwg.org/multipage/#face-entry-construction
1271                            let internals =
1272                                custom.upcast::<Element>().ensure_element_internals(can_gc);
1273                            internals.perform_entry_construction(&mut data_set);
1274                            // Otherwise no form value has been set so there is nothing to do.
1275                        }
1276                    },
1277                    _ => (),
1278                }
1279            }
1280
1281            // Step: 5.13. Add an entry if element has dirname attribute
1282            // An element can only have a dirname attribute if it is a textarea element
1283            // or an input element whose type attribute is in either the Text state or the Search state
1284            let child_element = child.downcast::<Element>().unwrap();
1285            let input_matches = child_element
1286                .downcast::<HTMLInputElement>()
1287                .is_some_and(|input| {
1288                    matches!(input.input_type(), InputType::Text | InputType::Search)
1289                });
1290            let textarea_matches = child_element.is::<HTMLTextAreaElement>();
1291            let dirname = child_element.get_string_attribute(&local_name!("dirname"));
1292            if (input_matches || textarea_matches) && !dirname.is_empty() {
1293                let dir = DOMString::from(child_element.directionality());
1294                data_set.push(FormDatum {
1295                    ty: DOMString::from("string"),
1296                    name: dirname,
1297                    value: FormDatumValue::String(dir),
1298                });
1299            }
1300        }
1301        data_set
1302    }
1303
1304    /// <https://html.spec.whatwg.org/multipage/#constructing-the-form-data-set>
1305    pub(crate) fn get_form_dataset(
1306        &self,
1307        submitter: Option<FormSubmitterElement>,
1308        encoding: Option<&'static Encoding>,
1309        can_gc: CanGc,
1310    ) -> Option<Vec<FormDatum>> {
1311        // Step 1
1312        if self.constructing_entry_list.get() {
1313            return None;
1314        }
1315
1316        // Step 2
1317        self.constructing_entry_list.set(true);
1318
1319        // Step 3-6
1320        let ret = self.get_unclean_dataset(submitter, encoding, can_gc);
1321
1322        let window = self.owner_window();
1323
1324        // Step 6
1325        let form_data = FormData::new(Some(ret), &window.global(), can_gc);
1326
1327        // Step 7
1328        let event = FormDataEvent::new(
1329            &window,
1330            atom!("formdata"),
1331            EventBubbles::Bubbles,
1332            EventCancelable::NotCancelable,
1333            &form_data,
1334            can_gc,
1335        );
1336
1337        event
1338            .upcast::<Event>()
1339            .fire(self.upcast::<EventTarget>(), can_gc);
1340
1341        // Step 8
1342        self.constructing_entry_list.set(false);
1343
1344        // Step 9
1345        Some(form_data.datums())
1346    }
1347
1348    /// <https://html.spec.whatwg.org/multipage/#dom-form-reset>
1349    pub(crate) fn reset(&self, _reset_method_flag: ResetFrom, can_gc: CanGc) {
1350        // https://html.spec.whatwg.org/multipage/#locked-for-reset
1351        if self.marked_for_reset.get() {
1352            return;
1353        } else {
1354            self.marked_for_reset.set(true);
1355        }
1356
1357        // https://html.spec.whatwg.org/multipage/#concept-form-reset
1358        // Let reset be the result of firing an event named reset at form,
1359        // with the bubbles and cancelable attributes initialized to true.
1360        let reset = self
1361            .upcast::<EventTarget>()
1362            .fire_bubbling_cancelable_event(atom!("reset"), can_gc);
1363        if !reset {
1364            return;
1365        }
1366
1367        let controls: Vec<_> = self
1368            .controls
1369            .borrow()
1370            .iter()
1371            .map(|c| c.as_rooted())
1372            .collect();
1373
1374        for child in controls {
1375            child.reset(can_gc);
1376        }
1377        self.marked_for_reset.set(false);
1378    }
1379
1380    fn add_control<T: ?Sized + FormControl>(&self, control: &T, can_gc: CanGc) {
1381        {
1382            let root = self.upcast::<Element>().root_element();
1383            let root = root.upcast::<Node>();
1384            let mut controls = self.controls.borrow_mut();
1385            controls.insert_pre_order(control.to_element(), root);
1386        }
1387        self.update_validity(can_gc);
1388    }
1389
1390    fn remove_control<T: ?Sized + FormControl>(&self, control: &T, can_gc: CanGc) {
1391        {
1392            let control = control.to_element();
1393            let mut controls = self.controls.borrow_mut();
1394            controls
1395                .iter()
1396                .position(|c| &**c == control)
1397                .map(|idx| controls.remove(idx));
1398
1399            // https://html.spec.whatwg.org/multipage#forms.html#the-form-element:past-names-map-5
1400            // "If an element listed in a form element's past names map
1401            // changes form owner, then its entries must be removed
1402            // from that map."
1403            let mut past_names_map = self.past_names_map.borrow_mut();
1404            past_names_map.0.retain(|_k, v| v.0 != control);
1405        }
1406        self.update_validity(can_gc);
1407    }
1408}
1409
1410impl Element {
1411    pub(crate) fn is_resettable(&self) -> bool {
1412        let NodeTypeId::Element(ElementTypeId::HTMLElement(element_type)) =
1413            self.upcast::<Node>().type_id()
1414        else {
1415            return false;
1416        };
1417        matches!(
1418            element_type,
1419            HTMLElementTypeId::HTMLInputElement |
1420                HTMLElementTypeId::HTMLSelectElement |
1421                HTMLElementTypeId::HTMLTextAreaElement |
1422                HTMLElementTypeId::HTMLOutputElement |
1423                HTMLElementTypeId::HTMLElement
1424        )
1425    }
1426
1427    pub(crate) fn reset(&self, can_gc: CanGc) {
1428        if !self.is_resettable() {
1429            return;
1430        }
1431
1432        if let Some(input_element) = self.downcast::<HTMLInputElement>() {
1433            input_element.reset(can_gc);
1434        } else if let Some(select_element) = self.downcast::<HTMLSelectElement>() {
1435            select_element.reset();
1436        } else if let Some(textarea_element) = self.downcast::<HTMLTextAreaElement>() {
1437            textarea_element.reset(can_gc);
1438        } else if let Some(output_element) = self.downcast::<HTMLOutputElement>() {
1439            output_element.reset(can_gc);
1440        } else if let Some(html_element) = self.downcast::<HTMLElement>() {
1441            if html_element.is_form_associated_custom_element() {
1442                ScriptThread::enqueue_callback_reaction(
1443                    html_element.upcast::<Element>(),
1444                    CallbackReaction::FormReset,
1445                    None,
1446                )
1447            }
1448        }
1449    }
1450}
1451
1452#[derive(Clone, JSTraceable, MallocSizeOf)]
1453pub(crate) enum FormDatumValue {
1454    File(DomRoot<File>),
1455    String(DOMString),
1456}
1457
1458#[derive(Clone, JSTraceable, MallocSizeOf)]
1459pub(crate) struct FormDatum {
1460    pub(crate) ty: DOMString,
1461    pub(crate) name: DOMString,
1462    pub(crate) value: FormDatumValue,
1463}
1464
1465impl FormDatum {
1466    pub(crate) fn replace_value(&self, charset: &str) -> String {
1467        if self.name.to_ascii_lowercase() == "_charset_" && self.ty == "hidden" {
1468            return charset.to_string();
1469        }
1470
1471        match self.value {
1472            FormDatumValue::File(ref f) => String::from(f.name().clone()),
1473            FormDatumValue::String(ref s) => String::from(s.clone()),
1474        }
1475    }
1476}
1477
1478#[derive(Clone, Copy, MallocSizeOf)]
1479pub(crate) enum FormEncType {
1480    TextPlain,
1481    UrlEncoded,
1482    MultipartFormData,
1483}
1484
1485#[derive(Clone, Copy, MallocSizeOf)]
1486pub(crate) enum FormMethod {
1487    Get,
1488    Post,
1489    Dialog,
1490}
1491
1492/// <https://html.spec.whatwg.org/multipage/#form-associated-element>
1493#[derive(Clone, Copy, MallocSizeOf)]
1494pub(crate) enum FormSubmitterElement<'a> {
1495    Form(&'a HTMLFormElement),
1496    Input(&'a HTMLInputElement),
1497    Button(&'a HTMLButtonElement),
1498    // TODO: implement other types of form associated elements
1499    // (including custom elements) that can be passed as submitter.
1500}
1501
1502impl FormSubmitterElement<'_> {
1503    fn action(&self) -> DOMString {
1504        match *self {
1505            FormSubmitterElement::Form(form) => form.Action(),
1506            FormSubmitterElement::Input(input_element) => input_element.get_form_attribute(
1507                &local_name!("formaction"),
1508                |i| i.FormAction(),
1509                |f| f.Action(),
1510            ),
1511            FormSubmitterElement::Button(button_element) => button_element.get_form_attribute(
1512                &local_name!("formaction"),
1513                |i| i.FormAction(),
1514                |f| f.Action(),
1515            ),
1516        }
1517    }
1518
1519    fn enctype(&self) -> FormEncType {
1520        let attr = match *self {
1521            FormSubmitterElement::Form(form) => form.Enctype(),
1522            FormSubmitterElement::Input(input_element) => input_element.get_form_attribute(
1523                &local_name!("formenctype"),
1524                |i| i.FormEnctype(),
1525                |f| f.Enctype(),
1526            ),
1527            FormSubmitterElement::Button(button_element) => button_element.get_form_attribute(
1528                &local_name!("formenctype"),
1529                |i| i.FormEnctype(),
1530                |f| f.Enctype(),
1531            ),
1532        };
1533        // https://html.spec.whatwg.org/multipage/#attr-fs-enctype
1534        // urlencoded is the default
1535        match_domstring_ascii!(attr,
1536            "multipart/form-data" => FormEncType::MultipartFormData,
1537            "text/plain" => FormEncType::TextPlain,
1538            _ => FormEncType::UrlEncoded,
1539        )
1540    }
1541
1542    fn method(&self) -> FormMethod {
1543        let attr = match *self {
1544            FormSubmitterElement::Form(form) => form.Method(),
1545            FormSubmitterElement::Input(input_element) => input_element.get_form_attribute(
1546                &local_name!("formmethod"),
1547                |i| i.FormMethod(),
1548                |f| f.Method(),
1549            ),
1550            FormSubmitterElement::Button(button_element) => button_element.get_form_attribute(
1551                &local_name!("formmethod"),
1552                |i| i.FormMethod(),
1553                |f| f.Method(),
1554            ),
1555        };
1556        match_domstring_ascii!(attr,
1557            "dialog" => FormMethod::Dialog,
1558            "post" => FormMethod::Post,
1559            _ => FormMethod::Get,
1560        )
1561    }
1562
1563    fn target(&self) -> DOMString {
1564        match *self {
1565            FormSubmitterElement::Form(form) => form.Target(),
1566            FormSubmitterElement::Input(input_element) => input_element.get_form_attribute(
1567                &local_name!("formtarget"),
1568                |i| i.FormTarget(),
1569                |f| f.Target(),
1570            ),
1571            FormSubmitterElement::Button(button_element) => button_element.get_form_attribute(
1572                &local_name!("formtarget"),
1573                |i| i.FormTarget(),
1574                |f| f.Target(),
1575            ),
1576        }
1577    }
1578
1579    fn no_validate(&self, _form_owner: &HTMLFormElement) -> bool {
1580        match *self {
1581            FormSubmitterElement::Form(form) => form.NoValidate(),
1582            FormSubmitterElement::Input(input_element) => input_element.get_form_boolean_attribute(
1583                &local_name!("formnovalidate"),
1584                |i| i.FormNoValidate(),
1585                |f| f.NoValidate(),
1586            ),
1587            FormSubmitterElement::Button(button_element) => button_element
1588                .get_form_boolean_attribute(
1589                    &local_name!("formnovalidate"),
1590                    |i| i.FormNoValidate(),
1591                    |f| f.NoValidate(),
1592                ),
1593        }
1594    }
1595
1596    // https://html.spec.whatwg.org/multipage/#concept-submit-button
1597    pub(crate) fn is_submit_button(&self) -> bool {
1598        match *self {
1599            // https://html.spec.whatwg.org/multipage/#image-button-state-(type=image)
1600            // https://html.spec.whatwg.org/multipage/#submit-button-state-(type=submit)
1601            FormSubmitterElement::Input(input_element) => input_element.is_submit_button(),
1602            // https://html.spec.whatwg.org/multipage/#attr-button-type-submit-state
1603            FormSubmitterElement::Button(button_element) => button_element.is_submit_button(),
1604            _ => false,
1605        }
1606    }
1607
1608    // https://html.spec.whatwg.org/multipage/#form-owner
1609    pub(crate) fn form_owner(&self) -> Option<DomRoot<HTMLFormElement>> {
1610        match *self {
1611            FormSubmitterElement::Button(button_el) => button_el.form_owner(),
1612            FormSubmitterElement::Input(input_el) => input_el.form_owner(),
1613            _ => None,
1614        }
1615    }
1616}
1617
1618pub(crate) trait FormControl: DomObject<ReflectorType = ()> {
1619    fn form_owner(&self) -> Option<DomRoot<HTMLFormElement>>;
1620
1621    fn set_form_owner(&self, form: Option<&HTMLFormElement>);
1622
1623    fn to_element(&self) -> &Element;
1624
1625    fn is_listed(&self) -> bool {
1626        true
1627    }
1628
1629    // https://html.spec.whatwg.org/multipage/#create-an-element-for-the-token
1630    // Part of step 12.
1631    // '..suppress the running of the reset the form owner algorithm
1632    // when the parser subsequently attempts to insert the element..'
1633    fn set_form_owner_from_parser(&self, form: &HTMLFormElement, can_gc: CanGc) {
1634        let elem = self.to_element();
1635        let node = elem.upcast::<Node>();
1636        node.set_flag(NodeFlags::PARSER_ASSOCIATED_FORM_OWNER, true);
1637        form.add_control(self, can_gc);
1638        self.set_form_owner(Some(form));
1639    }
1640
1641    /// <https://html.spec.whatwg.org/multipage/#reset-the-form-owner>
1642    fn reset_form_owner(&self, can_gc: CanGc) {
1643        let elem = self.to_element();
1644        let node = elem.upcast::<Node>();
1645        let old_owner = self.form_owner();
1646        let has_form_id = elem.has_attribute(&local_name!("form"));
1647        let nearest_form_ancestor = node
1648            .ancestors()
1649            .find_map(DomRoot::downcast::<HTMLFormElement>);
1650
1651        // Step 1
1652        if old_owner.is_some() &&
1653            !(self.is_listed() && has_form_id) &&
1654            nearest_form_ancestor == old_owner
1655        {
1656            return;
1657        }
1658
1659        let new_owner = if self.is_listed() && has_form_id && elem.is_connected() {
1660            // Step 3
1661            let doc = node.owner_document();
1662            let form_id = elem.get_string_attribute(&local_name!("form"));
1663            doc.GetElementById(form_id)
1664                .and_then(DomRoot::downcast::<HTMLFormElement>)
1665        } else {
1666            // Step 4
1667            nearest_form_ancestor
1668        };
1669
1670        if old_owner != new_owner {
1671            if let Some(o) = old_owner {
1672                o.remove_control(self, can_gc);
1673            }
1674            if let Some(ref new_owner) = new_owner {
1675                new_owner.add_control(self, can_gc);
1676            }
1677            // https://html.spec.whatwg.org/multipage/#custom-element-reactions:reset-the-form-owner
1678            if let Some(html_elem) = elem.downcast::<HTMLElement>() {
1679                if html_elem.is_form_associated_custom_element() {
1680                    ScriptThread::enqueue_callback_reaction(
1681                        elem,
1682                        CallbackReaction::FormAssociated(
1683                            new_owner.as_ref().map(|form| DomRoot::from_ref(&**form)),
1684                        ),
1685                        None,
1686                    )
1687                }
1688            }
1689            self.set_form_owner(new_owner.as_deref());
1690        }
1691    }
1692
1693    /// <https://html.spec.whatwg.org/multipage/#association-of-controls-and-forms>
1694    fn form_attribute_mutated(&self, mutation: AttributeMutation, can_gc: CanGc) {
1695        match mutation {
1696            AttributeMutation::Set(..) => {
1697                self.register_if_necessary();
1698            },
1699            AttributeMutation::Removed => {
1700                self.unregister_if_necessary();
1701            },
1702        }
1703
1704        self.reset_form_owner(can_gc);
1705    }
1706
1707    /// <https://html.spec.whatwg.org/multipage/#association-of-controls-and-forms>
1708    fn register_if_necessary(&self) {
1709        let elem = self.to_element();
1710        let form_id = elem.get_string_attribute(&local_name!("form"));
1711        let node = elem.upcast::<Node>();
1712
1713        if self.is_listed() && !form_id.is_empty() && node.is_connected() {
1714            node.owner_document()
1715                .register_form_id_listener(form_id, self);
1716        }
1717    }
1718
1719    fn unregister_if_necessary(&self) {
1720        let elem = self.to_element();
1721        let form_id = elem.get_string_attribute(&local_name!("form"));
1722
1723        if self.is_listed() && !form_id.is_empty() {
1724            elem.owner_document()
1725                .unregister_form_id_listener(form_id, self);
1726        }
1727    }
1728
1729    /// <https://html.spec.whatwg.org/multipage/#association-of-controls-and-forms>
1730    fn bind_form_control_to_tree(&self, can_gc: CanGc) {
1731        let elem = self.to_element();
1732        let node = elem.upcast::<Node>();
1733
1734        // https://html.spec.whatwg.org/multipage/#create-an-element-for-the-token
1735        // Part of step 12.
1736        // '..suppress the running of the reset the form owner algorithm
1737        // when the parser subsequently attempts to insert the element..'
1738        let must_skip_reset = node.get_flag(NodeFlags::PARSER_ASSOCIATED_FORM_OWNER);
1739        node.set_flag(NodeFlags::PARSER_ASSOCIATED_FORM_OWNER, false);
1740
1741        if !must_skip_reset {
1742            self.form_attribute_mutated(
1743                AttributeMutation::Set(None, AttributeMutationReason::Directly),
1744                can_gc,
1745            );
1746        }
1747    }
1748
1749    /// <https://html.spec.whatwg.org/multipage/#association-of-controls-and-forms>
1750    fn unbind_form_control_from_tree(&self, can_gc: CanGc) {
1751        let elem = self.to_element();
1752        let has_form_attr = elem.has_attribute(&local_name!("form"));
1753        let same_subtree = self
1754            .form_owner()
1755            .is_none_or(|form| elem.is_in_same_home_subtree(&*form));
1756
1757        self.unregister_if_necessary();
1758
1759        // Since this control has been unregistered from the id->listener map
1760        // in the previous step, reset_form_owner will not be invoked on it
1761        // when the form owner element is unbound (i.e it is in the same
1762        // subtree) if it appears later in the tree order. Hence invoke
1763        // reset from here if this control has the form attribute set.
1764        if !same_subtree || (self.is_listed() && has_form_attr) {
1765            self.reset_form_owner(can_gc);
1766        }
1767    }
1768
1769    fn get_form_attribute<InputFn, OwnerFn>(
1770        &self,
1771        attr: &LocalName,
1772        input: InputFn,
1773        owner: OwnerFn,
1774    ) -> DOMString
1775    where
1776        InputFn: Fn(&Self) -> DOMString,
1777        OwnerFn: Fn(&HTMLFormElement) -> DOMString,
1778        Self: Sized,
1779    {
1780        if self.to_element().has_attribute(attr) {
1781            input(self)
1782        } else {
1783            self.form_owner().map_or(DOMString::new(), |t| owner(&t))
1784        }
1785    }
1786
1787    fn get_form_boolean_attribute<InputFn, OwnerFn>(
1788        &self,
1789        attr: &LocalName,
1790        input: InputFn,
1791        owner: OwnerFn,
1792    ) -> bool
1793    where
1794        InputFn: Fn(&Self) -> bool,
1795        OwnerFn: Fn(&HTMLFormElement) -> bool,
1796        Self: Sized,
1797    {
1798        if self.to_element().has_attribute(attr) {
1799            input(self)
1800        } else {
1801            self.form_owner().is_some_and(|t| owner(&t))
1802        }
1803    }
1804
1805    /// <https://html.spec.whatwg.org/multipage/#candidate-for-constraint-validation>
1806    fn is_candidate_for_constraint_validation(&self) -> bool {
1807        let element = self.to_element();
1808        let html_element = element.downcast::<HTMLElement>();
1809        if let Some(html_element) = html_element {
1810            html_element.is_submittable_element() || element.is_instance_validatable()
1811        } else {
1812            false
1813        }
1814    }
1815
1816    // XXXKiChjang: Implement these on inheritors
1817    // fn satisfies_constraints(&self) -> bool;
1818}
1819
1820impl VirtualMethods for HTMLFormElement {
1821    fn super_type(&self) -> Option<&dyn VirtualMethods> {
1822        Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods)
1823    }
1824
1825    fn unbind_from_tree(&self, context: &UnbindContext, can_gc: CanGc) {
1826        self.super_type().unwrap().unbind_from_tree(context, can_gc);
1827
1828        // Collect the controls to reset because reset_form_owner
1829        // will mutably borrow self.controls
1830        rooted_vec!(let mut to_reset);
1831        to_reset.extend(
1832            self.controls
1833                .borrow()
1834                .iter()
1835                .filter(|c| !c.is_in_same_home_subtree(self))
1836                .cloned(),
1837        );
1838
1839        for control in to_reset.iter() {
1840            control
1841                .as_maybe_form_control()
1842                .expect("Element must be a form control")
1843                .reset_form_owner(can_gc);
1844        }
1845    }
1846
1847    fn parse_plain_attribute(&self, name: &LocalName, value: DOMString) -> AttrValue {
1848        match name {
1849            &local_name!("rel") => AttrValue::from_serialized_tokenlist(value.into()),
1850            _ => self
1851                .super_type()
1852                .unwrap()
1853                .parse_plain_attribute(name, value),
1854        }
1855    }
1856
1857    fn attribute_mutated(&self, attr: &Attr, mutation: AttributeMutation, can_gc: CanGc) {
1858        self.super_type()
1859            .unwrap()
1860            .attribute_mutated(attr, mutation, can_gc);
1861
1862        match *attr.local_name() {
1863            local_name!("rel") | local_name!("rev") => {
1864                self.relations
1865                    .set(LinkRelations::for_element(self.upcast()));
1866            },
1867            _ => {},
1868        }
1869    }
1870
1871    fn bind_to_tree(&self, context: &BindContext, can_gc: CanGc) {
1872        if let Some(s) = self.super_type() {
1873            s.bind_to_tree(context, can_gc);
1874        }
1875
1876        self.relations
1877            .set(LinkRelations::for_element(self.upcast()));
1878    }
1879}
1880
1881pub(crate) trait FormControlElementHelpers {
1882    fn as_maybe_form_control(&self) -> Option<&dyn FormControl>;
1883}
1884
1885impl FormControlElementHelpers for Element {
1886    fn as_maybe_form_control(&self) -> Option<&dyn FormControl> {
1887        let node = self.upcast::<Node>();
1888
1889        match node.type_id() {
1890            NodeTypeId::Element(ElementTypeId::HTMLElement(
1891                HTMLElementTypeId::HTMLButtonElement,
1892            )) => Some(self.downcast::<HTMLButtonElement>().unwrap() as &dyn FormControl),
1893            NodeTypeId::Element(ElementTypeId::HTMLElement(
1894                HTMLElementTypeId::HTMLFieldSetElement,
1895            )) => Some(self.downcast::<HTMLFieldSetElement>().unwrap() as &dyn FormControl),
1896            NodeTypeId::Element(ElementTypeId::HTMLElement(
1897                HTMLElementTypeId::HTMLImageElement,
1898            )) => Some(self.downcast::<HTMLImageElement>().unwrap() as &dyn FormControl),
1899            NodeTypeId::Element(ElementTypeId::HTMLElement(
1900                HTMLElementTypeId::HTMLInputElement,
1901            )) => Some(self.downcast::<HTMLInputElement>().unwrap() as &dyn FormControl),
1902            NodeTypeId::Element(ElementTypeId::HTMLElement(
1903                HTMLElementTypeId::HTMLLabelElement,
1904            )) => Some(self.downcast::<HTMLLabelElement>().unwrap() as &dyn FormControl),
1905            NodeTypeId::Element(ElementTypeId::HTMLElement(
1906                HTMLElementTypeId::HTMLLegendElement,
1907            )) => Some(self.downcast::<HTMLLegendElement>().unwrap() as &dyn FormControl),
1908            NodeTypeId::Element(ElementTypeId::HTMLElement(
1909                HTMLElementTypeId::HTMLObjectElement,
1910            )) => Some(self.downcast::<HTMLObjectElement>().unwrap() as &dyn FormControl),
1911            NodeTypeId::Element(ElementTypeId::HTMLElement(
1912                HTMLElementTypeId::HTMLOutputElement,
1913            )) => Some(self.downcast::<HTMLOutputElement>().unwrap() as &dyn FormControl),
1914            NodeTypeId::Element(ElementTypeId::HTMLElement(
1915                HTMLElementTypeId::HTMLSelectElement,
1916            )) => Some(self.downcast::<HTMLSelectElement>().unwrap() as &dyn FormControl),
1917            NodeTypeId::Element(ElementTypeId::HTMLElement(
1918                HTMLElementTypeId::HTMLTextAreaElement,
1919            )) => Some(self.downcast::<HTMLTextAreaElement>().unwrap() as &dyn FormControl),
1920            _ => self.downcast::<HTMLElement>().and_then(|elem| {
1921                if elem.is_form_associated_custom_element() {
1922                    Some(elem as &dyn FormControl)
1923                } else {
1924                    None
1925                }
1926            }),
1927        }
1928    }
1929}
1930
1931/// <https://html.spec.whatwg.org/multipage/#multipart/form-data-encoding-algorithm>
1932pub(crate) fn encode_multipart_form_data(
1933    form_data: &mut [FormDatum],
1934    boundary: String,
1935    encoding: &'static Encoding,
1936) -> Vec<u8> {
1937    let mut result = vec![];
1938
1939    // Newline replacement routine as described in Step 1
1940    fn clean_crlf(s: &DOMString) -> DOMString {
1941        let mut buf = "".to_owned();
1942        let mut prev = ' ';
1943        for ch in s.str().chars() {
1944            match ch {
1945                '\n' if prev != '\r' => {
1946                    buf.push('\r');
1947                    buf.push('\n');
1948                },
1949                '\n' => {
1950                    buf.push('\n');
1951                },
1952                // This character isn't LF but is
1953                // preceded by CR
1954                _ if prev == '\r' => {
1955                    buf.push('\r');
1956                    buf.push('\n');
1957                    buf.push(ch);
1958                },
1959                _ => buf.push(ch),
1960            };
1961            prev = ch;
1962        }
1963        // In case the last character was CR
1964        if prev == '\r' {
1965            buf.push('\n');
1966        }
1967        DOMString::from(buf)
1968    }
1969
1970    for entry in form_data.iter_mut() {
1971        // Step 1.1: Perform newline replacement on entry's name
1972        entry.name = clean_crlf(&entry.name);
1973
1974        // Step 1.2: If entry's value is not a File object, perform newline replacement on entry's
1975        // value
1976        if let FormDatumValue::String(ref s) = entry.value {
1977            entry.value = FormDatumValue::String(clean_crlf(s));
1978        }
1979
1980        // Step 2: Return the byte sequence resulting from encoding the entry list.
1981        // https://tools.ietf.org/html/rfc7578#section-4
1982        // NOTE(izgzhen): The encoding here expected by most servers seems different from
1983        // what spec says (that it should start with a '\r\n').
1984        let mut boundary_bytes = format!("--{}\r\n", boundary).into_bytes();
1985        result.append(&mut boundary_bytes);
1986
1987        // TODO(eijebong): Everthing related to content-disposition it to redo once typed headers
1988        // are capable of it.
1989        match entry.value {
1990            FormDatumValue::String(ref s) => {
1991                let content_disposition = format!("form-data; name=\"{}\"", entry.name);
1992                let mut bytes =
1993                    format!("Content-Disposition: {content_disposition}\r\n\r\n{s}\r\n",)
1994                        .into_bytes();
1995                result.append(&mut bytes);
1996            },
1997            FormDatumValue::File(ref f) => {
1998                let charset = encoding.name();
1999                let extra = if charset.to_lowercase() == "utf-8" {
2000                    format!("filename=\"{}\"", String::from(f.name().str()))
2001                } else {
2002                    format!(
2003                        "filename*=\"{}\"''{}",
2004                        charset,
2005                        http_percent_encode(&f.name().as_bytes())
2006                    )
2007                };
2008
2009                let content_disposition = format!("form-data; name=\"{}\"; {}", entry.name, extra);
2010                // https://tools.ietf.org/html/rfc7578#section-4.4
2011                let content_type: Mime = f
2012                    .upcast::<Blob>()
2013                    .Type()
2014                    .parse()
2015                    .unwrap_or(mime::TEXT_PLAIN);
2016                let mut type_bytes = format!(
2017                    "Content-Disposition: {}\r\nContent-Type: {}\r\n\r\n",
2018                    content_disposition, content_type
2019                )
2020                .into_bytes();
2021                result.append(&mut type_bytes);
2022
2023                let mut bytes = f.upcast::<Blob>().get_bytes().unwrap_or(vec![]);
2024
2025                result.append(&mut bytes);
2026                result.extend(b"\r\n");
2027            },
2028        }
2029    }
2030
2031    let mut boundary_bytes = format!("--{boundary}--\r\n").into_bytes();
2032    result.append(&mut boundary_bytes);
2033
2034    result
2035}
2036
2037// https://tools.ietf.org/html/rfc7578#section-4.1
2038pub(crate) fn generate_boundary() -> String {
2039    let i1 = random::<u32>();
2040    let i2 = random::<u32>();
2041
2042    format!("---------------------------{0}{1}", i1, i2)
2043}