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