Skip to main content

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