Skip to main content

script/dom/html/form_controls/
htmlformelement.rs

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