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::{Cow, 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, NoGC};
15use js::rust::HandleObject;
16use mime::{self, Mime};
17use net_traits::request::Referrer;
18use rand::random;
19use rustc_hash::FxBuildHasher;
20use script_bindings::cell::DomRefCell;
21use script_bindings::codegen::GenericBindings::DocumentFragmentBinding::DocumentFragmentMethods;
22use script_bindings::match_domstring_ascii;
23use script_bindings::reflector::DomObject;
24use servo_constellation_traits::{LoadData, LoadOrigin, NavigationHistoryBehavior};
25use style::attr::AttrValue;
26use style::str::split_html_space_chars;
27use stylo_atoms::Atom;
28use stylo_dom::ElementState;
29
30use crate::body::Extractable;
31use crate::dom::bindings::codegen::Bindings::BlobBinding::BlobMethods;
32use crate::dom::bindings::codegen::Bindings::DocumentBinding::DocumentMethods;
33use crate::dom::bindings::codegen::Bindings::EventBinding::EventMethods;
34use crate::dom::bindings::codegen::Bindings::HTMLButtonElementBinding::HTMLButtonElementMethods;
35use crate::dom::bindings::codegen::Bindings::HTMLElementBinding::HTMLElementMethods;
36use crate::dom::bindings::codegen::Bindings::HTMLFormControlsCollectionBinding::HTMLFormControlsCollectionMethods;
37use crate::dom::bindings::codegen::Bindings::HTMLFormElementBinding::HTMLFormElementMethods;
38use crate::dom::bindings::codegen::Bindings::HTMLInputElementBinding::HTMLInputElementMethods;
39use crate::dom::bindings::codegen::Bindings::HTMLOrSVGElementBinding::FocusOptions;
40use crate::dom::bindings::codegen::Bindings::HTMLTextAreaElementBinding::HTMLTextAreaElementMethods;
41use crate::dom::bindings::codegen::Bindings::NodeBinding::{NodeConstants, NodeMethods};
42use crate::dom::bindings::codegen::Bindings::NodeListBinding::NodeListMethods;
43use crate::dom::bindings::codegen::Bindings::RadioNodeListBinding::RadioNodeListMethods;
44use crate::dom::bindings::codegen::Bindings::WindowBinding::Window_Binding::WindowMethods;
45use crate::dom::bindings::codegen::UnionTypes::RadioNodeListOrElement;
46use crate::dom::bindings::error::{Error, Fallible};
47use crate::dom::bindings::inheritance::{Castable, ElementTypeId, HTMLElementTypeId, NodeTypeId};
48use crate::dom::bindings::refcounted::Trusted;
49use crate::dom::bindings::reflector::DomGlobal;
50use crate::dom::bindings::root::{Dom, DomOnceCell, DomRoot, MutNullableDom};
51use crate::dom::bindings::str::DOMString;
52use crate::dom::bindings::trace::{HashMapTracedValues, NoTrace};
53use crate::dom::blob::Blob;
54use crate::dom::customelementregistry::CallbackReaction;
55use crate::dom::document::Document;
56use crate::dom::domtokenlist::DOMTokenList;
57use crate::dom::element::attributes::storage::AttrRef;
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::{Node, NodeFlags, NodeTraits, UnbindContext, VecPreOrderInsertionHelper};
80use crate::dom::nodelist::{NodeList, RadioListMode};
81use crate::dom::radionodelist::RadioNodeList;
82use crate::dom::submitevent::SubmitEvent;
83use crate::dom::types::{DocumentFragment, HTMLIFrameElement};
84use crate::dom::virtualmethods::VirtualMethods;
85use crate::dom::window::Window;
86use crate::links::{LinkRelations, get_element_target, valid_navigable_target_name_or_keyword};
87use crate::navigation::navigate;
88use crate::script_runtime::CanGc;
89use crate::script_thread::ScriptThread;
90
91/// <https://html.spec.whatwg.org/multipage/#the-form-element>
92#[dom_struct]
93pub(crate) struct HTMLFormElement {
94    htmlelement: HTMLElement,
95    marked_for_reset: Cell<bool>,
96    /// <https://html.spec.whatwg.org/multipage/#constructing-entry-list>
97    constructing_entry_list: Cell<bool>,
98    elements: DomOnceCell<HTMLFormControlsCollection>,
99    controls: DomRefCell<Vec<Dom<Element>>>,
100
101    /// It is safe to use FxBuildHasher here as `Atom` is in the string_cache.
102    #[expect(clippy::type_complexity)]
103    past_names_map:
104        DomRefCell<HashMapTracedValues<Atom, (Dom<Element>, NoTrace<usize>), FxBuildHasher>>,
105
106    /// The current generation of past names, i.e., the number of name changes to the name.
107    current_name_generation: Cell<usize>,
108
109    firing_submission_events: Cell<bool>,
110    rel_list: MutNullableDom<DOMTokenList>,
111
112    /// <https://html.spec.whatwg.org/multipage/#planned-navigation>
113    planned_navigation: Cell<usize>,
114
115    /// <https://html.spec.whatwg.org/multipage/#attr-form-rel>
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 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, cx: &mut JSContext, value: DOMString) {
258        self.SetEnctype(cx, 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, cx: &mut JSContext) {
296        self.submit(
297            cx,
298            SubmittedFrom::FromForm,
299            FormSubmitterElement::Form(self),
300        );
301    }
302
303    /// <https://html.spec.whatwg.org/multipage/#dom-form-requestsubmit>
304    fn RequestSubmit(&self, cx: &mut JSContext, submitter: Option<&HTMLElement>) -> 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(cx, SubmittedFrom::NotFromForm, submitter);
361        Ok(())
362    }
363
364    /// <https://html.spec.whatwg.org/multipage/#dom-form-reset>
365    fn Reset(&self, cx: &mut JSContext) {
366        self.reset(cx, ResetFrom::FromForm);
367    }
368
369    /// <https://html.spec.whatwg.org/multipage/#dom-form-elements>
370    fn Elements(&self, cx: &mut JSContext) -> 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(cx, &window, self, filter)
434        }))
435    }
436
437    /// <https://html.spec.whatwg.org/multipage/#dom-form-length>
438    fn Length(&self, cx: &mut JSContext) -> u32 {
439        self.Elements(cx).Length()
440    }
441
442    /// <https://html.spec.whatwg.org/multipage/#dom-form-item>
443    fn IndexedGetter(&self, cx: &mut JSContext, index: u32) -> Option<DomRoot<Element>> {
444        let elements = self.Elements(cx);
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, cx: &mut JSContext, name: DOMString) -> 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(cx, &window, self, &name);
457        let mut candidates_length = candidates.Length();
458
459        // Step 2
460        if candidates_length == 0 {
461            candidates = RadioNodeList::new_images(cx, &window, self, &name);
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, cx: &mut JSContext, rel: DOMString) {
503        self.upcast::<Element>()
504            .set_tokenlist_attribute(cx, &local_name!("rel"), rel);
505    }
506
507    /// <https://html.spec.whatwg.org/multipage/#dom-a-rellist>
508    fn RelList(&self, cx: &mut JSContext) -> DomRoot<DOMTokenList> {
509        self.rel_list.or_init(|| {
510            DOMTokenList::new(
511                cx,
512                self.upcast(),
513                &local_name!("rel"),
514                Some(vec![
515                    Atom::from("noopener"),
516                    Atom::from("noreferrer"),
517                    Atom::from("opener"),
518                ]),
519            )
520        })
521    }
522
523    // https://html.spec.whatwg.org/multipage/#the-form-element:supported-property-names
524    fn SupportedPropertyNames(&self, _: &NoGC) -> 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(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(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    pub(crate) fn update_validity(&self, cx: &mut JSContext) {
704        let is_any_invalid = self
705            .controls
706            .borrow()
707            .iter()
708            .any(|control| control.is_invalid(cx, false));
709
710        self.upcast::<Element>()
711            .set_state(ElementState::VALID, !is_any_invalid);
712        self.upcast::<Element>()
713            .set_state(ElementState::INVALID, is_any_invalid);
714    }
715
716    /// [Form submission](https://html.spec.whatwg.org/multipage/#concept-form-submit)
717    pub(crate) fn submit(
718        &self,
719        cx: &mut JSContext,
720        submit_method_flag: SubmittedFrom,
721        submitter: FormSubmitterElement,
722    ) {
723        // Step 1
724        if self.upcast::<Element>().cannot_navigate() {
725            return;
726        }
727
728        // Step 2
729        if self.constructing_entry_list.get() {
730            return;
731        }
732        // Step 3. Let form document be form's node document.
733        let doc = self.owner_document();
734
735        // Step 4. If form document's active sandboxing flag set has its sandboxed forms browsing
736        // context flag set, then return.
737        if doc.has_active_sandboxing_flag(SandboxingFlagSet::SANDBOXED_FORMS_BROWSING_CONTEXT_FLAG)
738        {
739            return;
740        }
741
742        let base = doc.base_url();
743        // TODO: Handle browsing contexts (Step 5)
744        // Step 6
745        if submit_method_flag == SubmittedFrom::NotFromForm {
746            // Step 6.1
747            if self.firing_submission_events.get() {
748                return;
749            }
750            // Step 6.2
751            self.firing_submission_events.set(true);
752            // Step 6.3
753            if !submitter.no_validate(self) && self.interactive_validation(cx).is_err() {
754                self.firing_submission_events.set(false);
755                return;
756            }
757            // Step 6.4
758            // spec calls this "submitterButton" but it doesn't have to be a button,
759            // just not be the form itself
760            let submitter_button = match submitter {
761                FormSubmitterElement::Form(f) => {
762                    if f == self {
763                        None
764                    } else {
765                        Some(f.upcast::<HTMLElement>())
766                    }
767                },
768                FormSubmitterElement::Input(i) => Some(i.upcast::<HTMLElement>()),
769                FormSubmitterElement::Button(b) => Some(b.upcast::<HTMLElement>()),
770            };
771
772            // Step 6.5
773            let event = SubmitEvent::new(
774                self.global().as_window(),
775                atom!("submit"),
776                true,
777                true,
778                submitter_button.map(DomRoot::from_ref),
779                CanGc::from_cx(cx),
780            );
781            let event = event.upcast::<Event>();
782            event.fire(cx, self.upcast::<EventTarget>());
783
784            // Step 6.6
785            self.firing_submission_events.set(false);
786            // Step 6.7
787            if event.DefaultPrevented() {
788                return;
789            }
790            // Step 6.8
791            if self.upcast::<Element>().cannot_navigate() {
792                return;
793            }
794        }
795
796        // Step 7
797        let encoding = self.pick_encoding();
798
799        // Step 8
800        let mut form_data = match self.get_form_dataset(cx, Some(submitter), Some(encoding)) {
801            Some(form_data) => form_data,
802            None => return,
803        };
804
805        // Step 9. If form cannot navigate, then return.
806        if self.upcast::<Element>().cannot_navigate() {
807            return;
808        }
809
810        // Step 10. Let method be the submitter element's method.
811        let method = submitter.method();
812        // Step 11. If method is dialog, then:
813        // TODO
814
815        // Step 12. Let action be the submitter element's action.
816        let mut action = submitter.action();
817
818        // Step 13. If action is the empty string, let action be the URL of the form document.
819        if action.is_empty() {
820            action = DOMString::from(base.as_str());
821        }
822        // Step 14. Let parsed action be the result of encoding-parsing a URL given action, relative to submitter's node document.
823        let action_components = match doc.encoding_parse_a_url(&action.str()) {
824            Ok(url) => url,
825            // Step 15. If parsed action is failure, then return.
826            Err(_) => return,
827        };
828        // Step 16. Let scheme be the scheme of parsed action.
829        let scheme = action_components.scheme().to_owned();
830        // Step 17. Let enctype be the submitter element's enctype.
831        let enctype = submitter.enctype();
832
833        // Step 19. If the submitter element is a submit button and it has a formtarget attribute,
834        // then set formTarget to the formtarget attribute value.
835        let form_target_attribute = submitter.target();
836        let form_target = if submitter.is_submit_button() &&
837            valid_navigable_target_name_or_keyword(&form_target_attribute)
838        {
839            Some(form_target_attribute)
840        } else {
841            // Step 18. Let formTarget be null.
842            None
843        };
844        // Step 20. Let target be the result of getting an element's target given submitter's form owner and formTarget.
845        let form_owner = submitter.form_owner();
846        let form = form_owner.as_deref().unwrap_or(self);
847        let target = get_element_target(form.upcast::<Element>(), form_target);
848
849        // Step 21. Let noopener be the result of getting an element's noopener with form, parsed action, and target.
850        let noopener = self.relations.get().get_element_noopener(target.as_ref());
851
852        // Step 22. Let targetNavigable be the first return value of applying the rules for choosing a navigable given target,
853        // form's node navigable, and noopener.
854        let source = doc.browsing_context().unwrap();
855        let (maybe_chosen, _new) =
856            source.choose_browsing_context(cx, target.unwrap_or_default(), noopener);
857
858        let Some(chosen) = maybe_chosen else {
859            // Step 23. If targetNavigable is null, then return.
860            return;
861        };
862        let target_document = match chosen.document() {
863            Some(doc) => doc,
864            None => return,
865        };
866
867        // Step 24. Let historyHandling be "auto".
868        // Step 25. If form document equals targetNavigable's active document, and form document has not yet completely loaded,
869        // then set historyHandling to "replace".
870        let history_handling = if doc == target_document && !doc.completely_loaded() {
871            NavigationHistoryBehavior::Replace
872        } else {
873            NavigationHistoryBehavior::Auto
874        };
875
876        let target_window = target_document.window();
877        let mut load_data = LoadData::new(
878            LoadOrigin::Script(doc.origin().snapshot()),
879            action_components,
880            target_document.about_base_url(),
881            None,
882            target_window.as_global_scope().get_referrer(),
883            target_document.get_referrer_policy(),
884            Some(target_window.as_global_scope().is_secure_context()),
885            Some(target_document.insecure_requests_policy()),
886            target_document.has_trustworthy_ancestor_origin(),
887            target_document.creation_sandboxing_flag_set_considering_parent_iframe(),
888        );
889
890        // Step 26. Select the appropriate row in the table below based on scheme as given by the first cell of each row.
891        // Then, select the appropriate cell on that row based on method as given in the first cell of each column.
892        // Then, jump to the steps named in that cell and defined below the table.
893        match (&*scheme, method) {
894            (_, FormMethod::Dialog) => {
895                // TODO: Submit dialog
896                // https://html.spec.whatwg.org/multipage/#submit-dialog
897            },
898            // https://html.spec.whatwg.org/multipage/#submit-mutate-action
899            ("http", FormMethod::Get) | ("https", FormMethod::Get) | ("data", FormMethod::Get) => {
900                load_data
901                    .headers
902                    .typed_insert(ContentType::from(mime::APPLICATION_WWW_FORM_URLENCODED));
903                self.mutate_action_url(&mut form_data, load_data, target_window, history_handling);
904            },
905            // https://html.spec.whatwg.org/multipage/#submit-body
906            ("http", FormMethod::Post) | ("https", FormMethod::Post) => {
907                load_data.method = Method::POST;
908                self.submit_entity_body(
909                    cx,
910                    &mut form_data,
911                    load_data,
912                    enctype,
913                    encoding,
914                    target_window,
915                    history_handling,
916                );
917            },
918            // https://html.spec.whatwg.org/multipage/#submit-get-action
919            ("file", _) |
920            ("about", _) |
921            ("data", FormMethod::Post) |
922            ("ftp", _) |
923            ("javascript", _) => {
924                self.plan_to_navigate(load_data, target_window, history_handling);
925            },
926            ("mailto", FormMethod::Post) => {
927                // TODO: Mail as body
928                // https://html.spec.whatwg.org/multipage/#submit-mailto-body
929            },
930            ("mailto", FormMethod::Get) => {
931                // TODO: Mail with headers
932                // https://html.spec.whatwg.org/multipage/#submit-mailto-headers
933            },
934            _ => (),
935        }
936    }
937
938    /// <https://html.spec.whatwg.org/multipage/#submit-mutate-action>
939    fn mutate_action_url(
940        &self,
941        form_data: &mut [FormDatum],
942        mut load_data: LoadData,
943        target: &Window,
944        history_handling: NavigationHistoryBehavior,
945    ) {
946        self.set_url_query_pairs(
947            &mut load_data.url,
948            form_data.iter().map(|field| {
949                (
950                    field.name.normalize_crlf(),
951                    field.replace_value().normalize_crlf(),
952                )
953            }),
954        );
955
956        self.plan_to_navigate(load_data, target, history_handling);
957    }
958
959    /// <https://html.spec.whatwg.org/multipage/#submit-body>
960    #[allow(clippy::too_many_arguments)]
961    fn submit_entity_body(
962        &self,
963        cx: &mut JSContext,
964        form_data: &mut [FormDatum],
965        mut load_data: LoadData,
966        enctype: FormEncType,
967        encoding: &'static Encoding,
968        target: &Window,
969        history_handling: NavigationHistoryBehavior,
970    ) {
971        let boundary = generate_boundary();
972        let bytes = match enctype {
973            FormEncType::UrlEncoded => {
974                load_data
975                    .headers
976                    .typed_insert(ContentType::from(mime::APPLICATION_WWW_FORM_URLENCODED));
977
978                let mut url = load_data.url.clone();
979                self.set_url_query_pairs(
980                    &mut url,
981                    // Let pairs be the result of
982                    // converting to a list of name-value pairs with entry list.
983                    // <https://html.spec.whatwg.org/multipage/#convert-to-a-list-of-name-value-pairs>
984                    form_data.iter().map(|field| {
985                        (
986                            field.name.normalize_crlf(),
987                            field.replace_value().normalize_crlf(),
988                        )
989                    }),
990                );
991
992                url.query().unwrap_or("").to_string().into_bytes()
993            },
994            FormEncType::MultipartFormData => {
995                let mime: Mime = format!("multipart/form-data; boundary={}", boundary)
996                    .parse()
997                    .unwrap();
998                load_data.headers.typed_insert(ContentType::from(mime));
999                encode_multipart_form_data(form_data, boundary, encoding)
1000            },
1001            FormEncType::TextPlain => {
1002                load_data
1003                    .headers
1004                    .typed_insert(ContentType::from(mime::TEXT_PLAIN));
1005                // Step 1: Let pairs be the result of converting to a list
1006                // of name-value pairs with entry list.
1007                // <https://html.spec.whatwg.org/multipage/#convert-to-a-list-of-name-value-pairs>
1008                let pairs = form_data.iter().map(|field| {
1009                    (
1010                        field.name.normalize_crlf(),
1011                        field.replace_value().normalize_crlf(),
1012                    )
1013                });
1014                // Step 2: Let body be the result of running the text/plain
1015                // encoding algorithm with pairs.
1016                let body = encode_plaintext(pairs);
1017                // Step 3: Set body to the result of encoding body using encoding.
1018                encode_with_html_fallback(&body, encoding).into_owned()
1019            },
1020        };
1021
1022        let global = self.global();
1023
1024        let request_body = bytes
1025            .extract(cx, &global, false)
1026            .expect("Couldn't extract body.")
1027            .into_net_request_body()
1028            .0;
1029        load_data.data = Some(request_body);
1030
1031        self.plan_to_navigate(load_data, target, history_handling);
1032    }
1033
1034    fn set_url_query_pairs<T>(
1035        &self,
1036        url: &mut servo_url::ServoUrl,
1037        pairs: impl Iterator<Item = (T, String)>,
1038    ) where
1039        T: AsRef<str>,
1040    {
1041        let encoding = self.pick_encoding();
1042        url.as_mut_url()
1043            .query_pairs_mut()
1044            .encoding_override(Some(&|s| encode_with_html_fallback(s, encoding)))
1045            .clear()
1046            .extend_pairs(pairs);
1047    }
1048
1049    /// [Planned navigation](https://html.spec.whatwg.org/multipage/#planned-navigation)
1050    fn plan_to_navigate(
1051        &self,
1052        mut load_data: LoadData,
1053        target: &Window,
1054        history_handling: NavigationHistoryBehavior,
1055    ) {
1056        // 1. Let referrerPolicy be the empty string.
1057        // 2. If the form element's link types include the noreferrer keyword,
1058        //    then set referrerPolicy to "no-referrer".
1059        // Note: both steps done below.
1060        let elem = self.upcast::<Element>();
1061        let referrer = match elem.get_attribute_string_value(&local_name!("rel")) {
1062            Some(link_types) if link_types.contains("noreferrer") => Referrer::NoReferrer,
1063            _ => target.as_global_scope().get_referrer(),
1064        };
1065
1066        // 3. If the form has a non-null planned navigation, remove it from its task queue.
1067        // Note: done by incrementing `planned_navigation`.
1068        self.planned_navigation
1069            .set(self.planned_navigation.get().wrapping_add(1));
1070        let planned_navigation = self.planned_navigation.get();
1071
1072        // Note: we start to use
1073        // the beginnings of an `ongoing_navigation` concept,
1074        // to cancel planned navigations as part of
1075        // <https://html.spec.whatwg.org/multipage/#nav-stop>
1076        //
1077        // The concept of ongoing navigation must be separated from the form's
1078        // planned navigation concept, because each planned navigation cancels the previous one
1079        // for a given form, whereas an ongoing navigation is a per navigable (read: window for now)
1080        // concept.
1081        //
1082        // Setting the ongoing navigation now means the navigation could be cancelled
1083        // even if the below task has not run yet. This is not how the spec is written: it
1084        // seems instead to imply that a `window.stop` should only cancel the navigation
1085        // that has already started (here the task is queued, but the navigation starts only
1086        // in the task). See <https://github.com/whatwg/html/issues/11562>.
1087        let ongoing_navigation = target.set_ongoing_navigation();
1088
1089        let referrer_policy = target.Document().get_referrer_policy();
1090        load_data.creator_pipeline_id = Some(target.pipeline_id());
1091        load_data.referrer = referrer;
1092        load_data.referrer_policy = referrer_policy;
1093
1094        // Note the pending form navigation if this is an iframe;
1095        // necessary for deciding whether to run the iframe load event steps.
1096        if let Some(window_proxy) = target.undiscarded_window_proxy() &&
1097            let Some(frame) = window_proxy
1098                .frame_element()
1099                .and_then(|e| e.downcast::<HTMLIFrameElement>())
1100        {
1101            frame.note_pending_navigation()
1102        }
1103
1104        // 4. Queue an element task on the DOM manipulation task source
1105        // given the form element and the following steps:
1106        let form = Trusted::new(self);
1107        let window = Trusted::new(target);
1108        let task = task!(navigate_to_form_planned_navigation: move |cx| {
1109            // 4.1 Set the form's planned navigation to null.
1110            // Note: we implement the equivalent by incrementing the counter above,
1111            // and checking it here.
1112            if planned_navigation != form.root().planned_navigation.get() {
1113                return;
1114            }
1115
1116            // Note: we also check if the navigation has been cancelled,
1117            // see https://github.com/whatwg/html/issues/11562
1118            if ongoing_navigation != window.root().ongoing_navigation() {
1119                return;
1120            }
1121
1122            // 4.2 Navigate targetNavigable to url
1123            navigate(
1124                cx,
1125                &window.root(),
1126                history_handling,
1127                false,
1128                load_data,
1129            )
1130        });
1131
1132        // 5. Set the form's planned navigation to the just-queued task.
1133        // Done above as part of incrementing the planned navigation counter.
1134
1135        // Note: task queued here.
1136        target
1137            .global()
1138            .task_manager()
1139            .dom_manipulation_task_source()
1140            .queue(task)
1141    }
1142
1143    /// Interactively validate the constraints of form elements
1144    /// <https://html.spec.whatwg.org/multipage/#interactively-validate-the-constraints>
1145    fn interactive_validation(&self, cx: &mut JSContext) -> Result<(), ()> {
1146        // Step 1 - 2: Statically validate the constraints of form,
1147        // and let `unhandled invalid controls` be the list of elements
1148        // returned if the result was negative.
1149        // If the result was positive, then return that result.
1150        let unhandled_invalid_controls = match self.static_validation(cx) {
1151            Ok(()) => return Ok(()),
1152            Err(err) => err,
1153        };
1154
1155        // Step 3: Report the problems with the constraints of at least one of the elements
1156        // given in unhandled invalid controls to the user.
1157        let mut first = true;
1158
1159        for elem in unhandled_invalid_controls {
1160            if let Some(validatable) = elem.as_maybe_validatable() {
1161                error!("Validation error: {}", validatable.validation_message(cx));
1162            }
1163            if first && let Some(html_elem) = elem.downcast::<HTMLElement>() {
1164                // Step 3.1: User agents may focus one of those elements in the process,
1165                // by running the focusing steps for that element,
1166                // and may change the scrolling position of the document, or perform
1167                // some other action that brings the element to the user's attention.
1168
1169                // Here we run focusing steps and scroll element into view.
1170                html_elem.Focus(cx, &FocusOptions::default());
1171                first = false;
1172            }
1173        }
1174
1175        // If it's form-associated and has a validation anchor, point the
1176        //  user there instead of the element itself.
1177        // Step 4
1178        Err(())
1179    }
1180
1181    /// Statitically validate the constraints of form elements
1182    /// <https://html.spec.whatwg.org/multipage/#statically-validate-the-constraints>
1183    fn static_validation(&self, cx: &mut JSContext) -> Result<(), Vec<DomRoot<Element>>> {
1184        // Step 1-3
1185        let invalid_controls = self
1186            .controls
1187            .borrow()
1188            .iter()
1189            .filter_map(|field| {
1190                if let Some(element) = field.downcast::<Element>() {
1191                    if element.is_invalid(cx, true) {
1192                        Some(DomRoot::from_ref(element))
1193                    } else {
1194                        None
1195                    }
1196                } else {
1197                    None
1198                }
1199            })
1200            .collect::<Vec<DomRoot<Element>>>();
1201        // Step 4: If invalid controls is empty, then return a positive result.
1202        if invalid_controls.is_empty() {
1203            return Ok(());
1204        }
1205        // Step 5-6
1206        let unhandled_invalid_controls = invalid_controls
1207            .into_iter()
1208            .filter_map(|field| {
1209                // Step 6.1: Let notCanceled be the result of firing an event named invalid at
1210                // field, with the cancelable attribute initialized to true.
1211                let not_canceled = field
1212                    .upcast::<EventTarget>()
1213                    .fire_cancelable_event(cx, atom!("invalid"));
1214                // Step 6.2: If notCanceled is true, then add field to unhandled invalid controls.
1215                if not_canceled {
1216                    return Some(field);
1217                }
1218                None
1219            })
1220            .collect::<Vec<DomRoot<Element>>>();
1221        // Step 7
1222        Err(unhandled_invalid_controls)
1223    }
1224
1225    /// <https://html.spec.whatwg.org/multipage/#constructing-the-form-data-set>
1226    /// terminology note:  "form data set" = "entry list"
1227    /// Steps range from 3 to 5
1228    /// 5.x substeps are mostly handled inside element-specific methods
1229    fn get_unclean_dataset(
1230        &self,
1231        submitter: Option<FormSubmitterElement>,
1232        encoding: Option<&'static Encoding>,
1233        can_gc: CanGc,
1234    ) -> Vec<FormDatum> {
1235        let mut data_set = Vec::new();
1236        for child in self.controls.borrow().iter() {
1237            // Step 5.1: The field element is disabled.
1238            if child.disabled_state() {
1239                continue;
1240            }
1241            let child = child.upcast::<Node>();
1242
1243            // Step 5.1: The field element has a datalist element ancestor.
1244            if child.ancestors().any(|a| a.is::<HTMLDataListElement>()) {
1245                continue;
1246            }
1247            if let NodeTypeId::Element(ElementTypeId::HTMLElement(element)) = child.type_id() {
1248                match element {
1249                    HTMLElementTypeId::HTMLInputElement => {
1250                        let input = child.downcast::<HTMLInputElement>().unwrap();
1251                        let (ref mut form_datums, should_continue) =
1252                            input.form_datums(submitter, encoding);
1253                        data_set.append(form_datums);
1254                        if should_continue {
1255                            continue;
1256                        }
1257                    },
1258                    HTMLElementTypeId::HTMLButtonElement => {
1259                        let button = child.downcast::<HTMLButtonElement>().unwrap();
1260                        if let Some(datum) = button.form_datum(submitter) {
1261                            data_set.push(datum);
1262                        }
1263                    },
1264                    HTMLElementTypeId::HTMLObjectElement => {
1265                        // Unimplemented
1266                    },
1267                    HTMLElementTypeId::HTMLSelectElement => {
1268                        let select = child.downcast::<HTMLSelectElement>().unwrap();
1269                        select.push_form_data(&mut data_set);
1270                    },
1271                    HTMLElementTypeId::HTMLTextAreaElement => {
1272                        let textarea = child.downcast::<HTMLTextAreaElement>().unwrap();
1273                        let name = textarea.Name();
1274                        if !name.is_empty() {
1275                            data_set.push(FormDatum {
1276                                ty: textarea.Type(),
1277                                name,
1278                                value: FormDatumValue::String(textarea.Value()),
1279                            });
1280                        }
1281                    },
1282                    HTMLElementTypeId::HTMLElement => {
1283                        let custom = child.downcast::<HTMLElement>().unwrap();
1284                        if custom.is_form_associated_custom_element() {
1285                            // https://html.spec.whatwg.org/multipage/#face-entry-construction
1286                            let internals =
1287                                custom.upcast::<Element>().ensure_element_internals(can_gc);
1288                            internals.perform_entry_construction(&mut data_set);
1289                            // Otherwise no form value has been set so there is nothing to do.
1290                        }
1291                    },
1292                    _ => (),
1293                }
1294            }
1295
1296            // Step: 5.11.1 Let dirname be the value of the element's dirname attribute.
1297            let child_element = child.downcast::<Element>().unwrap();
1298            let dirname = child_element.get_string_attribute(&local_name!("dirname"));
1299
1300            // Step: 5.11. If the element has a dirname attribute, that attribute's value is not the empty
1301            // string, and the element is an auto-directionality form-associated element:
1302            // From: <https://html.spec.whatwg.org/multipage/#auto-directionality-form-associated-elements>
1303            // Input elements whose type attribute is in the Hidden, Text, Search, Telephone, URL, Email,
1304            // Password, Submit Button, Reset Button, or Button state, and textarea elements.
1305            let is_input_auto_directionality_form_associated_element = child_element
1306                .downcast::<HTMLInputElement>()
1307                .is_some_and(|input| input.is_auto_directionality_form_associated_element());
1308            let is_textarea_element = child_element.is::<HTMLTextAreaElement>();
1309            if !dirname.is_empty() &&
1310                (is_input_auto_directionality_form_associated_element || is_textarea_element)
1311            {
1312                // Step: 5.11.2 Let dir be the string "ltr" if the directionality of the element is 'ltr',
1313                // and "rtl" otherwise (i.e., when the directionality of the element is 'rtl').
1314                let dir = DOMString::from(child_element.directionality());
1315
1316                // Step: 5.11.3 Create an entry with dirname and dir, and append it to entry list.
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        cx: &mut JSContext,
1331        submitter: Option<FormSubmitterElement>,
1332        encoding: Option<&'static Encoding>,
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, CanGc::from_cx(cx));
1344
1345        let window = self.owner_window();
1346
1347        // Step 6
1348        let form_data = FormData::new(Some(ret), &window.global(), CanGc::from_cx(cx));
1349
1350        // Step 7
1351        let event = FormDataEvent::new(
1352            &window,
1353            atom!("formdata"),
1354            EventBubbles::Bubbles,
1355            EventCancelable::NotCancelable,
1356            &form_data,
1357            CanGc::from_cx(cx),
1358        );
1359
1360        event
1361            .upcast::<Event>()
1362            .fire(cx, self.upcast::<EventTarget>());
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, cx: &mut JSContext, control: &T) {
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(cx);
1422    }
1423
1424    fn remove_control<T: ?Sized + FormControl>(&self, cx: &mut JSContext, control: &T) {
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(cx);
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, cx: &mut JSContext, form: &HTMLFormElement) {
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(cx, self);
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, cx: &mut JSContext) {
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(cx, form_id)
1699            } else {
1700                node.owner_document().GetElementById(cx, 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(cx, self);
1712            }
1713            if let Some(ref new_owner) = new_owner {
1714                new_owner.add_control(cx, self);
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, cx: &mut JSContext, mutation: AttributeMutation) {
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(cx);
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, cx: &mut JSContext) {
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                cx,
1783                AttributeMutation::Set(None, AttributeMutationReason::Directly),
1784            );
1785        }
1786    }
1787
1788    /// <https://html.spec.whatwg.org/multipage/#association-of-controls-and-forms>
1789    fn unbind_form_control_from_tree(&self, cx: &mut JSContext) {
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(cx);
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(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(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/#text/plain-encoding-algorithm>
1978fn encode_plaintext(pairs: impl Iterator<Item = (String, String)>) -> String {
1979    // Step 1. Let result be the empty string.
1980    let mut result = String::new();
1981    // Step 2. For each pair in pairs:
1982    for (name, value) in pairs {
1983        // Step 2.1. Append pair's name to result.
1984        result.push_str(&name);
1985        // Step 2.2. Append a single U+003D EQUALS SIGN character (=) to result.
1986        result.push('=');
1987        // Step 2.3. Append pair's value to result.
1988        result.push_str(&value);
1989        // Step 2.4. Append a U+000D CARRIAGE RETURN (CR) U+000A LINE FEED (LF)
1990        // character pair to result.
1991        result.push_str("\r\n");
1992    }
1993    // Step 3. Return result.
1994    result
1995}
1996
1997/// Encode a string with the form's encoding, converted to a byte sequence.
1998/// <https://encoding.spec.whatwg.org/#encode>
1999///
2000/// Characters that can't be encoded in the given charset are replaced
2001/// with HTML decimal numeric character references (e.g. 😂 → &#128514;).
2002fn encode_with_html_fallback<'a>(input: &'a str, encoding: &'static Encoding) -> Cow<'a, [u8]> {
2003    encoding.encode(input).0
2004}
2005
2006/// <https://html.spec.whatwg.org/multipage/#multipart/form-data-encoding-algorithm>
2007pub(crate) fn encode_multipart_form_data(
2008    form_data: &mut [FormDatum],
2009    boundary: String,
2010    encoding: &'static Encoding,
2011) -> Vec<u8> {
2012    let mut result = vec![];
2013
2014    // Step 2.4: For field names and filenames for file fields, the result
2015    // of the encoding must be escaped by replacing:
2016    //   0x0A (LF) bytes with `%0A`,
2017    //   0x0D (CR) bytes with `%0D`,
2018    //   0x22 (") bytes with `%22`.
2019    // The user agent must not perform any other escapes.
2020    fn escape_header_bytes(input: &[u8]) -> Vec<u8> {
2021        let mut output = Vec::with_capacity(input.len());
2022        for &b in input {
2023            match b {
2024                b'\n' => output.extend(b"%0A"),
2025                b'\r' => output.extend(b"%0D"),
2026                b'"' => output.extend(b"%22"),
2027                _ => output.push(b),
2028            }
2029        }
2030        output
2031    }
2032
2033    for entry in form_data.iter_mut() {
2034        // Step 1.1: Replace every occurrence of U+000D (CR) not followed by U+000A (LF),
2035        // and every occurrence of U+000A (LF) not preceded by U+000D (CR),
2036        // in entry's name, by a string consisting of a U+000D (CR) and U+000A (LF).
2037        entry.name = entry.name.normalize_crlf().into();
2038
2039        // Step 1.2: If entry's value is not a File object,
2040        // then replace every occurrence of U+000D (CR) not followed by U+000A (LF),
2041        // and every occurrence of U+000A (LF) not preceded by U+000D (CR), in entry's value,
2042        // by a string consisting of a U+000D (CR) and U+000A (LF).
2043        if let FormDatumValue::String(ref s) = entry.value {
2044            entry.value = FormDatumValue::String(s.normalize_crlf().into());
2045        }
2046
2047        // Step 2.6: Boundary string
2048        let mut boundary_bytes = format!("--{}\r\n", boundary).into_bytes();
2049        result.append(&mut boundary_bytes);
2050
2051        // Step 2.3: Encode name with the form's encoding
2052        let name_str = &*entry.name.str();
2053        let encoded_name = encode_with_html_fallback(name_str, encoding);
2054        // Step 2.4: Escape name for header
2055        let escaped_name = escape_header_bytes(&encoded_name);
2056
2057        match entry.value {
2058            FormDatumValue::String(ref s) => {
2059                // Step 2.3: Encode value with the form's encoding
2060                let value_str = &*s.str();
2061                let encoded_value = encode_with_html_fallback(value_str, encoding);
2062
2063                // Step 2.5: Non-file fields must not have `Content-Type` header specified
2064                result.extend(b"Content-Disposition: form-data; name=\"");
2065                result.extend(&escaped_name);
2066                result.extend(b"\"\r\n\r\n");
2067                result.extend_from_slice(&encoded_value);
2068                result.extend(b"\r\n");
2069            },
2070            FormDatumValue::File(ref f) => {
2071                // Step 2.3: Encode filename with the form's encoding
2072                let filename_str = &*f.name().str();
2073                let encoded_filename = encode_with_html_fallback(filename_str, encoding);
2074                // Step 2.4: Escape filename for header
2075                let escaped_filename = escape_header_bytes(&encoded_filename);
2076
2077                result.extend(b"Content-Disposition: form-data; name=\"");
2078                result.extend(&escaped_name);
2079                result.extend(b"\"; filename=\"");
2080                result.extend(&escaped_filename);
2081
2082                // https://tools.ietf.org/html/rfc7578#section-4.4
2083                result.extend(b"\"\r\nContent-Type: ");
2084
2085                let content_type: Mime = f
2086                    .upcast::<Blob>()
2087                    .Type()
2088                    .parse()
2089                    .unwrap_or(mime::TEXT_PLAIN);
2090                result.extend(content_type.as_ref().as_bytes());
2091                result.extend(b"\r\n\r\n");
2092
2093                let mut bytes = f.upcast::<Blob>().get_bytes().unwrap_or_default();
2094
2095                result.append(&mut bytes);
2096                result.extend(b"\r\n");
2097            },
2098        }
2099    }
2100
2101    // Step 2.6: Closing boundary string
2102    let mut boundary_bytes = format!("--{boundary}--\r\n").into_bytes();
2103    result.append(&mut boundary_bytes);
2104
2105    result
2106}
2107
2108// https://tools.ietf.org/html/rfc7578#section-4.1
2109pub(crate) fn generate_boundary() -> String {
2110    let i1 = random::<u32>();
2111    let i2 = random::<u32>();
2112
2113    format!("---------------------------{0}{1}", i1, i2)
2114}