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