Skip to main content

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