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