Skip to main content

script/dom/html/
htmlformelement.rs

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