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