1use std::borrow::{Cow, ToOwned};
6use std::cell::Cell;
7
8use content_security_policy::sandboxing_directive::SandboxingFlagSet;
9use dom_struct::dom_struct;
10use encoding_rs::{Encoding, UTF_8};
11use headers::{ContentType, HeaderMapExt};
12use html5ever::{LocalName, Prefix, local_name};
13use http::Method;
14use js::context::{JSContext, NoGC};
15use js::rust::HandleObject;
16use mime::{self, Mime};
17use net_traits::request::Referrer;
18use rand::random;
19use rustc_hash::FxBuildHasher;
20use script_bindings::cell::DomRefCell;
21use script_bindings::codegen::GenericBindings::DocumentFragmentBinding::DocumentFragmentMethods;
22use script_bindings::dom::UnrootedDom;
23use script_bindings::domstring::TracedDOMString;
24use script_bindings::match_domstring_ascii;
25use script_bindings::reflector::DomObject;
26use servo_constellation_traits::{LoadData, LoadOrigin, NavigationHistoryBehavior};
27use style::attr::AttrValue;
28use style::str::split_html_space_chars;
29use stylo_atoms::Atom;
30use stylo_dom::ElementState;
31
32use crate::dom::bindings::codegen::Bindings::BlobBinding::BlobMethods;
33use crate::dom::bindings::codegen::Bindings::DocumentBinding::DocumentMethods;
34use crate::dom::bindings::codegen::Bindings::EventBinding::EventMethods;
35use crate::dom::bindings::codegen::Bindings::HTMLButtonElementBinding::HTMLButtonElementMethods;
36use crate::dom::bindings::codegen::Bindings::HTMLElementBinding::HTMLElementMethods;
37use crate::dom::bindings::codegen::Bindings::HTMLFormControlsCollectionBinding::HTMLFormControlsCollectionMethods;
38use crate::dom::bindings::codegen::Bindings::HTMLFormElementBinding::HTMLFormElementMethods;
39use crate::dom::bindings::codegen::Bindings::HTMLInputElementBinding::HTMLInputElementMethods;
40use crate::dom::bindings::codegen::Bindings::HTMLOrSVGOrMathMLElementBinding::FocusOptions;
41use crate::dom::bindings::codegen::Bindings::HTMLTextAreaElementBinding::HTMLTextAreaElementMethods;
42use crate::dom::bindings::codegen::Bindings::NodeBinding::{NodeConstants, NodeMethods};
43use crate::dom::bindings::codegen::Bindings::NodeListBinding::NodeListMethods;
44use crate::dom::bindings::codegen::Bindings::RadioNodeListBinding::RadioNodeListMethods;
45use crate::dom::bindings::codegen::UnionTypes::RadioNodeListOrElement;
46use crate::dom::bindings::error::{Error, Fallible};
47use crate::dom::bindings::inheritance::{Castable, ElementTypeId, HTMLElementTypeId, NodeTypeId};
48use crate::dom::bindings::refcounted::Trusted;
49use crate::dom::bindings::reflector::DomGlobal;
50use crate::dom::bindings::root::{Dom, DomOnceCell, DomRoot, MutNullableDom};
51use crate::dom::bindings::str::DOMString;
52use crate::dom::bindings::trace::{HashMapTracedValues, NoTrace};
53use crate::dom::blob::Blob;
54use crate::dom::customelementregistry::CallbackReaction;
55use crate::dom::document::Document;
56use crate::dom::domtokenlist::DOMTokenList;
57use crate::dom::element::attributes::storage::AttrRef;
58use crate::dom::element::{AttributeMutation, Element};
59use crate::dom::event::{Event, EventBubbles, EventCancelable};
60use crate::dom::eventtarget::EventTarget;
61use crate::dom::file::File;
62use crate::dom::formdata::FormData;
63use crate::dom::formdataevent::FormDataEvent;
64use crate::dom::html::form_controls::htmlinputelement::HTMLInputElement;
65use crate::dom::html::form_controls::input_type::InputType;
66use crate::dom::html::htmlbuttonelement::HTMLButtonElement;
67use crate::dom::html::htmlcollection::CollectionFilter;
68use crate::dom::html::htmldatalistelement::HTMLDataListElement;
69use crate::dom::html::htmlelement::HTMLElement;
70use crate::dom::html::htmlfieldsetelement::HTMLFieldSetElement;
71use crate::dom::html::htmlformcontrolscollection::HTMLFormControlsCollection;
72use crate::dom::html::htmlimageelement::HTMLImageElement;
73use crate::dom::html::htmllegendelement::HTMLLegendElement;
74use crate::dom::html::htmlobjectelement::HTMLObjectElement;
75use crate::dom::html::htmloutputelement::HTMLOutputElement;
76use crate::dom::html::htmlselectelement::HTMLSelectElement;
77use crate::dom::html::htmltextareaelement::HTMLTextAreaElement;
78use crate::dom::html::links::relations::{
79 LinkRelations, get_element_target, valid_navigable_target_name_or_keyword,
80};
81use crate::dom::node::virtualmethods::VirtualMethods;
82use crate::dom::node::{Node, NodeFlags, NodeTraits, UnbindContext, VecPreOrderInsertionHelper};
83use crate::dom::nodelist::{NodeList, RadioListMode};
84use crate::dom::radionodelist::RadioNodeList;
85use crate::dom::submitevent::SubmitEvent;
86use crate::dom::types::{DocumentFragment, HTMLIFrameElement};
87use crate::dom::window::Window;
88use crate::event_loop::script_thread::ScriptThread;
89use crate::fetch::body::Extractable;
90use crate::navigation::navigate;
91
92#[dom_struct]
94pub(crate) struct HTMLFormElement {
95 htmlelement: HTMLElement,
96 marked_for_reset: Cell<bool>,
97 constructing_entry_list: Cell<bool>,
99 elements: DomOnceCell<HTMLFormControlsCollection>,
100 controls: DomRefCell<Vec<Dom<Element>>>,
101
102 #[expect(clippy::type_complexity)]
104 past_names_map:
105 DomRefCell<HashMapTracedValues<Atom, (Dom<Element>, NoTrace<usize>), FxBuildHasher>>,
106
107 current_name_generation: Cell<usize>,
109
110 firing_submission_events: Cell<bool>,
111 rel_list: MutNullableDom<DOMTokenList>,
112
113 planned_navigation: Cell<usize>,
115
116 #[no_trace]
118 relations: Cell<LinkRelations>,
119}
120
121impl HTMLFormElement {
122 fn new_inherited(
123 local_name: LocalName,
124 prefix: Option<Prefix>,
125 document: &Document,
126 ) -> HTMLFormElement {
127 HTMLFormElement {
128 htmlelement: HTMLElement::new_inherited_with_state(
129 ElementState::VALID,
130 local_name,
131 prefix,
132 document,
133 ),
134 marked_for_reset: Cell::new(false),
135 constructing_entry_list: Cell::new(false),
136 elements: Default::default(),
137 controls: DomRefCell::new(Vec::new()),
138 past_names_map: DomRefCell::new(HashMapTracedValues::new_fx()),
139 current_name_generation: Cell::new(0),
140 firing_submission_events: Cell::new(false),
141 rel_list: Default::default(),
142 planned_navigation: Default::default(),
143 relations: Cell::new(LinkRelations::empty()),
144 }
145 }
146
147 pub(crate) fn new(
148 cx: &mut JSContext,
149 local_name: LocalName,
150 prefix: Option<Prefix>,
151 document: &Document,
152 proto: Option<HandleObject>,
153 ) -> DomRoot<HTMLFormElement> {
154 Node::reflect_node_with_proto(
155 cx,
156 Box::new(HTMLFormElement::new_inherited(local_name, prefix, document)),
157 document,
158 proto,
159 )
160 }
161
162 fn filter_for_radio_list(mode: RadioListMode, child: &Element, name: &Atom) -> bool {
163 if let Some(child) = child.downcast::<Element>() {
164 match mode {
165 RadioListMode::ControlsExceptImageInputs => {
166 if child
167 .downcast::<HTMLElement>()
168 .is_some_and(|c| c.is_listed_element()) &&
169 (child.get_id().is_some_and(|i| i == *name) ||
170 child.get_name().is_some_and(|n| n == *name))
171 {
172 if let Some(inp) = child.downcast::<HTMLInputElement>() {
173 return !matches!(*inp.input_type(), InputType::Image(_));
175 } else {
176 return true;
178 }
179 }
180 return false;
181 },
182 RadioListMode::Images => {
183 return child.is::<HTMLImageElement>() &&
184 (child.get_id().is_some_and(|i| i == *name) ||
185 child.get_name().is_some_and(|n| n == *name));
186 },
187 }
188 }
189 false
190 }
191
192 pub(crate) fn nth_for_radio_list<'a>(
193 &self,
194 no_gc: &'a NoGC,
195 index: u32,
196 mode: RadioListMode,
197 name: &Atom,
198 ) -> Option<UnrootedDom<'a, Node>> {
199 self.controls
200 .borrow()
201 .iter()
202 .filter(|n| HTMLFormElement::filter_for_radio_list(mode, n, name))
203 .nth(index as usize)
204 .map(|node| UnrootedDom::upcast(node.as_unrooted(no_gc)))
205 }
206
207 pub(crate) fn count_for_radio_list(&self, mode: RadioListMode, name: &Atom) -> u32 {
208 self.controls
209 .borrow()
210 .iter()
211 .filter(|n| HTMLFormElement::filter_for_radio_list(mode, n, name))
212 .count() as u32
213 }
214}
215
216impl HTMLFormElementMethods<crate::DomTypeHolder> for HTMLFormElement {
217 make_getter!(AcceptCharset, "accept-charset");
219
220 make_setter!(SetAcceptCharset, "accept-charset");
222
223 make_form_action_getter!(Action, "action");
225
226 make_setter!(SetAction, "action");
228
229 make_enumerated_getter!(
231 Autocomplete,
232 "autocomplete",
233 "on" | "off",
234 missing => "on",
235 invalid => "on"
236 );
237
238 make_setter!(SetAutocomplete, "autocomplete");
240
241 make_enumerated_getter!(
243 Enctype,
244 "enctype",
245 "application/x-www-form-urlencoded" | "text/plain" | "multipart/form-data",
246 missing => "application/x-www-form-urlencoded",
247 invalid => "application/x-www-form-urlencoded"
248 );
249
250 make_setter!(SetEnctype, "enctype");
252
253 fn Encoding(&self) -> DOMString {
255 self.Enctype()
256 }
257
258 fn SetEncoding(&self, cx: &mut JSContext, value: DOMString) {
260 self.SetEnctype(cx, value)
261 }
262
263 make_enumerated_getter!(
265 Method,
266 "method",
267 "get" | "post" | "dialog",
268 missing => "get",
269 invalid => "get"
270 );
271
272 make_setter!(SetMethod, "method");
274
275 make_getter!(Name, "name");
277
278 make_atomic_setter!(SetName, "name");
280
281 make_bool_getter!(NoValidate, "novalidate");
283
284 make_bool_setter!(SetNoValidate, "novalidate");
286
287 make_getter!(Target, "target");
289
290 make_setter!(SetTarget, "target");
292
293 make_getter!(Rel, "rel");
295
296 fn Submit(&self, cx: &mut JSContext) {
298 self.submit(
299 cx,
300 SubmittedFrom::FromForm,
301 FormSubmitterElement::Form(self),
302 );
303 }
304
305 fn RequestSubmit(&self, cx: &mut JSContext, submitter: Option<&HTMLElement>) -> Fallible<()> {
307 let submitter: FormSubmitterElement = match submitter {
308 Some(submitter_element) => {
309 let error_not_a_submit_button =
311 Err(Error::Type(c"submitter must be a submit button".to_owned()));
312
313 let element = match submitter_element.upcast::<Node>().type_id() {
314 NodeTypeId::Element(ElementTypeId::HTMLElement(element)) => element,
315 _ => {
316 return error_not_a_submit_button;
317 },
318 };
319
320 let submit_button = match element {
321 HTMLElementTypeId::HTMLInputElement => FormSubmitterElement::Input(
322 submitter_element
323 .downcast::<HTMLInputElement>()
324 .expect("Failed to downcast submitter elem to HTMLInputElement."),
325 ),
326 HTMLElementTypeId::HTMLButtonElement => FormSubmitterElement::Button(
327 submitter_element
328 .downcast::<HTMLButtonElement>()
329 .expect("Failed to downcast submitter elem to HTMLButtonElement."),
330 ),
331 _ => {
332 return error_not_a_submit_button;
333 },
334 };
335
336 if !submit_button.is_submit_button() {
337 return error_not_a_submit_button;
338 }
339
340 let submitters_owner = submit_button.form_owner();
341
342 let owner = match submitters_owner {
344 Some(owner) => owner,
345 None => {
346 return Err(Error::NotFound(Some(
347 "Form element's owner does not exist".into(),
348 )));
349 },
350 };
351
352 if *owner != *self {
353 return Err(Error::NotFound(Some("The form that owns this submitter element does not match the form element provided".into())));
354 }
355
356 submit_button
357 },
358 None => {
359 FormSubmitterElement::Form(self)
361 },
362 };
363 self.submit(cx, SubmittedFrom::NotFromForm, submitter);
365 Ok(())
366 }
367
368 fn Reset(&self, cx: &mut JSContext) {
370 self.reset(cx, ResetFrom::FromForm);
371 }
372
373 fn Elements(&self, cx: &mut JSContext) -> DomRoot<HTMLFormControlsCollection> {
375 #[derive(JSTraceable, MallocSizeOf)]
376 #[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
377 struct ElementsFilter {
378 form: Dom<HTMLFormElement>,
379 }
380 impl CollectionFilter for ElementsFilter {
381 fn filter<'a>(&self, elem: &'a Element, _root: &'a Node) -> bool {
382 let form_owner = match elem.upcast::<Node>().type_id() {
383 NodeTypeId::Element(ElementTypeId::HTMLElement(t)) => match t {
384 HTMLElementTypeId::HTMLButtonElement => {
385 elem.downcast::<HTMLButtonElement>().unwrap().form_owner()
386 },
387 HTMLElementTypeId::HTMLFieldSetElement => {
388 elem.downcast::<HTMLFieldSetElement>().unwrap().form_owner()
389 },
390 HTMLElementTypeId::HTMLInputElement => {
391 let input_elem = elem.downcast::<HTMLInputElement>().unwrap();
392 if matches!(*input_elem.input_type(), InputType::Image(_)) {
393 return false;
394 }
395 input_elem.form_owner()
396 },
397 HTMLElementTypeId::HTMLObjectElement => {
398 elem.downcast::<HTMLObjectElement>().unwrap().form_owner()
399 },
400 HTMLElementTypeId::HTMLOutputElement => {
401 elem.downcast::<HTMLOutputElement>().unwrap().form_owner()
402 },
403 HTMLElementTypeId::HTMLSelectElement => {
404 elem.downcast::<HTMLSelectElement>().unwrap().form_owner()
405 },
406 HTMLElementTypeId::HTMLTextAreaElement => {
407 elem.downcast::<HTMLTextAreaElement>().unwrap().form_owner()
408 },
409 HTMLElementTypeId::HTMLElement => {
410 let html_element = elem.downcast::<HTMLElement>().unwrap();
411 if html_element.is_form_associated_custom_element() {
412 html_element.form_owner()
413 } else {
414 return false;
415 }
416 },
417 _ => {
418 debug_assert!(
419 !elem.downcast::<HTMLElement>().unwrap().is_listed_element()
420 );
421 return false;
422 },
423 },
424 _ => return false,
425 };
426
427 form_owner
428 .as_deref()
429 .is_some_and(|form_owner| self.form == form_owner)
430 }
431 }
432 DomRoot::from_ref(self.elements.init_once(|| {
433 let window = self.owner_window();
434 HTMLFormControlsCollection::new(
435 cx,
436 &window,
437 self,
438 Box::new(ElementsFilter {
439 form: Dom::from_ref(self),
440 }),
441 )
442 }))
443 }
444
445 fn Length(&self, cx: &mut JSContext) -> u32 {
447 self.Elements(cx).Length(cx)
448 }
449
450 fn IndexedGetter(&self, cx: &mut JSContext, index: u32) -> Option<DomRoot<Element>> {
452 let elements = self.Elements(cx);
453 elements.IndexedGetter(cx, index)
454 }
455
456 fn NamedGetter(&self, cx: &mut JSContext, name: DOMString) -> Option<RadioNodeListOrElement> {
458 let window = self.owner_window();
459
460 let name = Atom::from(name);
461
462 let mut candidates =
464 RadioNodeList::new_controls_except_image_inputs(cx, &window, self, &name);
465 let mut candidates_length = candidates.Length(cx);
466
467 if candidates_length == 0 {
469 candidates = RadioNodeList::new_images(cx, &window, self, &name);
470 candidates_length = candidates.Length(cx);
471 }
472
473 let mut past_names_map = self.past_names_map.safe_borrow_mut(cx.no_gc());
474
475 if candidates_length == 0 {
477 if past_names_map.contains_key(&name) {
478 return Some(RadioNodeListOrElement::Element(DomRoot::from_ref(
479 &*past_names_map.get(&name).unwrap().0,
480 )));
481 }
482 return None;
483 }
484
485 if candidates_length > 1 {
487 return Some(RadioNodeListOrElement::RadioNodeList(candidates));
488 }
489
490 let element_node = candidates.upcast::<NodeList>().Item(cx, 0).unwrap();
493 past_names_map.insert(
494 name,
495 (
496 Dom::from_ref(element_node.downcast::<Element>().unwrap()),
497 NoTrace(self.current_name_generation.get() + 1),
498 ),
499 );
500 self.current_name_generation
501 .set(self.current_name_generation.get() + 1);
502
503 Some(RadioNodeListOrElement::Element(DomRoot::from_ref(
505 element_node.downcast::<Element>().unwrap(),
506 )))
507 }
508
509 fn SetRel(&self, cx: &mut JSContext, rel: DOMString) {
511 self.upcast::<Element>()
512 .set_tokenlist_attribute(cx, &local_name!("rel"), rel);
513 }
514
515 fn RelList(&self, cx: &mut JSContext) -> DomRoot<DOMTokenList> {
517 self.rel_list.or_init(|| {
518 DOMTokenList::new(
519 cx,
520 self.upcast(),
521 &local_name!("rel"),
522 Some(vec![
523 Atom::from("noopener"),
524 Atom::from("noreferrer"),
525 Atom::from("opener"),
526 ]),
527 )
528 })
529 }
530
531 fn SupportedPropertyNames(&self, no_gc: &NoGC) -> Vec<DOMString> {
533 #[derive(Debug, Eq, Ord, PartialEq, PartialOrd)]
535 enum SourcedNameSource {
536 Id,
537 Name,
538 Past(usize),
539 }
540
541 impl SourcedNameSource {
542 fn is_past(&self) -> bool {
543 matches!(self, SourcedNameSource::Past(..))
544 }
545 }
546
547 struct SourcedName {
548 name: Atom,
549 element: DomRoot<Element>,
550 source: SourcedNameSource,
551 }
552
553 let mut sourced_names_vec: Vec<SourcedName> = Vec::new();
554
555 for child in self.controls.borrow().iter() {
557 if child
558 .downcast::<HTMLElement>()
559 .is_some_and(|c| c.is_listed_element())
560 {
561 if let Some(id_atom) = child.get_id() {
562 let entry = SourcedName {
563 name: id_atom,
564 element: DomRoot::from_ref(child),
565 source: SourcedNameSource::Id,
566 };
567 sourced_names_vec.push(entry);
568 }
569 if let Some(name_atom) = child.get_name() {
570 let entry = SourcedName {
571 name: name_atom,
572 element: DomRoot::from_ref(child),
573 source: SourcedNameSource::Name,
574 };
575 sourced_names_vec.push(entry);
576 }
577 }
578 }
579
580 for child in self.controls.borrow().iter() {
582 if child.is::<HTMLImageElement>() {
583 if let Some(id_atom) = child.get_id() {
584 let entry = SourcedName {
585 name: id_atom,
586 element: DomRoot::from_ref(child),
587 source: SourcedNameSource::Id,
588 };
589 sourced_names_vec.push(entry);
590 }
591 if let Some(name_atom) = child.get_name() {
592 let entry = SourcedName {
593 name: name_atom,
594 element: DomRoot::from_ref(child),
595 source: SourcedNameSource::Name,
596 };
597 sourced_names_vec.push(entry);
598 }
599 }
600 }
601
602 let past_names_map = self.past_names_map.borrow();
604 for (key, val) in past_names_map.iter() {
605 let entry = SourcedName {
606 name: key.clone(),
607 element: DomRoot::from_ref(&*val.0),
608 source: SourcedNameSource::Past(self.current_name_generation.get() - val.1.0),
609 };
610 sourced_names_vec.push(entry);
611 }
612
613 sourced_names_vec.sort_by(|a, b| {
625 if a.element
626 .upcast::<Node>()
627 .CompareDocumentPosition(no_gc, b.element.upcast::<Node>()) ==
628 0
629 {
630 if a.source.is_past() && b.source.is_past() {
631 b.source.cmp(&a.source)
632 } else {
633 a.source.cmp(&b.source)
634 }
635 } else if a
636 .element
637 .upcast::<Node>()
638 .CompareDocumentPosition(no_gc, b.element.upcast::<Node>()) &
639 NodeConstants::DOCUMENT_POSITION_FOLLOWING ==
640 NodeConstants::DOCUMENT_POSITION_FOLLOWING
641 {
642 std::cmp::Ordering::Less
643 } else {
644 std::cmp::Ordering::Greater
645 }
646 });
647
648 sourced_names_vec.retain(|sn| !sn.name.to_string().is_empty());
650
651 let mut names_vec: Vec<DOMString> = Vec::new();
653 for elem in sourced_names_vec.iter() {
654 if !names_vec.iter().any(|name| *name == *elem.name) {
655 names_vec.push(TracedDOMString::from(elem.name.clone()).root());
656 }
657 }
658
659 names_vec
660 }
661
662 fn CheckValidity(&self, cx: &mut JSContext) -> bool {
664 self.static_validation(cx).is_ok()
665 }
666
667 fn ReportValidity(&self, cx: &mut JSContext) -> bool {
669 self.interactive_validation(cx).is_ok()
670 }
671}
672
673#[derive(Clone, Copy, MallocSizeOf, PartialEq)]
674pub(crate) enum SubmittedFrom {
675 FromForm,
676 NotFromForm,
677}
678
679#[derive(Clone, Copy, MallocSizeOf)]
680pub(crate) enum ResetFrom {
681 FromForm,
682 NotFromForm,
683}
684
685impl HTMLFormElement {
686 fn pick_encoding(&self) -> &'static Encoding {
688 if self
690 .upcast::<Element>()
691 .has_attribute(&local_name!("accept-charset"))
692 {
693 let input = self
695 .upcast::<Element>()
696 .get_string_attribute(&local_name!("accept-charset"));
697
698 let input = input.str();
700 let mut candidate_encodings =
701 split_html_space_chars(&input).filter_map(|c| Encoding::for_label(c.as_bytes()));
702
703 return candidate_encodings.next().unwrap_or(UTF_8);
705 }
706
707 self.owner_document().encoding()
709 }
710
711 pub(crate) fn update_validity(&self, cx: &mut JSContext) {
712 let is_any_invalid = self
713 .controls
714 .borrow()
715 .iter()
716 .any(|control| control.is_invalid(cx, false));
717
718 self.upcast::<Element>()
719 .set_state(cx.no_gc(), ElementState::VALID, !is_any_invalid);
720 self.upcast::<Element>()
721 .set_state(cx.no_gc(), ElementState::INVALID, is_any_invalid);
722 }
723
724 pub(crate) fn submit(
726 &self,
727 cx: &mut JSContext,
728 submit_method_flag: SubmittedFrom,
729 submitter: FormSubmitterElement,
730 ) {
731 if self.upcast::<Element>().cannot_navigate() {
733 return;
734 }
735
736 if self.constructing_entry_list.get() {
738 return;
739 }
740 let doc = self.owner_document();
742
743 if doc.has_active_sandboxing_flag(SandboxingFlagSet::SANDBOXED_FORMS_BROWSING_CONTEXT_FLAG)
746 {
747 return;
748 }
749
750 let base = doc.base_url();
751 if submit_method_flag == SubmittedFrom::NotFromForm {
754 if self.firing_submission_events.get() {
756 return;
757 }
758 self.firing_submission_events.set(true);
760 if !submitter.no_validate(self) && self.interactive_validation(cx).is_err() {
762 self.firing_submission_events.set(false);
763 return;
764 }
765 let submitter_button = match submitter {
769 FormSubmitterElement::Form(f) => {
770 if f == self {
771 None
772 } else {
773 Some(f.upcast::<HTMLElement>())
774 }
775 },
776 FormSubmitterElement::Input(i) => Some(i.upcast::<HTMLElement>()),
777 FormSubmitterElement::Button(b) => Some(b.upcast::<HTMLElement>()),
778 };
779
780 let event = SubmitEvent::new(
782 cx,
783 self.global().as_window(),
784 atom!("submit"),
785 true,
786 true,
787 submitter_button,
788 );
789 let event = event.upcast::<Event>();
790 event.fire(cx, self.upcast::<EventTarget>());
791
792 self.firing_submission_events.set(false);
794 if event.DefaultPrevented() {
796 return;
797 }
798 if self.upcast::<Element>().cannot_navigate() {
800 return;
801 }
802 }
803
804 let encoding = self.pick_encoding();
806
807 let mut form_data = match self.get_form_dataset(cx, Some(submitter), Some(encoding)) {
809 Some(form_data) => form_data,
810 None => return,
811 };
812
813 if self.upcast::<Element>().cannot_navigate() {
815 return;
816 }
817
818 let method = submitter.method();
820 let mut action = submitter.action();
825
826 if action.is_empty() {
828 action = DOMString::from(base.as_str());
829 }
830 let action_components = match doc.encoding_parse_a_url(&action.str()) {
832 Ok(url) => url,
833 Err(_) => return,
835 };
836 let scheme = action_components.scheme().to_owned();
838 let enctype = submitter.enctype();
840
841 let form_target_attribute = submitter.target();
844 let form_target = if submitter.is_submit_button() &&
845 valid_navigable_target_name_or_keyword(&form_target_attribute)
846 {
847 Some(form_target_attribute)
848 } else {
849 None
851 };
852 let form_owner = submitter.form_owner();
855 let form = form_owner.as_deref().unwrap_or(self);
856 let target = get_element_target(form.upcast::<Element>(), form_target);
857
858 let noopener = self.relations.get().get_element_noopener(target.as_ref());
861
862 let Some(chosen) = doc.browsing_context().and_then(|source| {
865 source
866 .choose_a_navigable(cx, target.unwrap_or_default(), noopener)
867 .0
868 }) else {
869 return;
871 };
872
873 let Some(target_document) = chosen.document() else {
874 return;
875 };
876
877 let history_handling = if doc == target_document && !doc.completely_loaded() {
881 NavigationHistoryBehavior::Replace
882 } else {
883 NavigationHistoryBehavior::Auto
884 };
885
886 let target_window = target_document.window();
887 let mut load_data = LoadData::new(
888 LoadOrigin::Script(doc.origin().snapshot()),
889 action_components,
890 target_document.about_base_url(),
891 Some(doc.pipeline_id()),
892 target_window.as_global_scope().get_referrer(),
893 target_document.get_referrer_policy(),
894 Some(target_window.as_global_scope().is_secure_context()),
895 Some(target_document.insecure_requests_policy()),
896 target_document.has_trustworthy_ancestor_origin(),
897 target_document.creation_sandboxing_flag_set_considering_parent_iframe(),
898 );
899
900 match (&*scheme, method) {
904 (_, FormMethod::Dialog) => {
905 },
908 ("http", FormMethod::Get) | ("https", FormMethod::Get) | ("data", FormMethod::Get) => {
910 load_data
911 .headers
912 .typed_insert(ContentType::from(mime::APPLICATION_WWW_FORM_URLENCODED));
913 self.mutate_action_url(&mut form_data, load_data, target_window, history_handling);
914 },
915 ("http", FormMethod::Post) | ("https", FormMethod::Post) => {
917 load_data.method = Method::POST;
918 self.submit_entity_body(
919 cx,
920 &mut form_data,
921 load_data,
922 enctype,
923 encoding,
924 target_window,
925 history_handling,
926 );
927 },
928 ("file", _) |
930 ("about", _) |
931 ("data", FormMethod::Post) |
932 ("ftp", _) |
933 ("javascript", _) => {
934 self.plan_to_navigate(load_data, target_window, history_handling);
935 },
936 ("mailto", FormMethod::Post) => {
937 },
940 ("mailto", FormMethod::Get) => {
941 },
944 _ => (),
945 }
946 }
947
948 fn mutate_action_url(
950 &self,
951 form_data: &mut [FormDatum],
952 mut load_data: LoadData,
953 target: &Window,
954 history_handling: NavigationHistoryBehavior,
955 ) {
956 self.set_url_query_pairs(
957 &mut load_data.url,
958 form_data.iter().map(|field| {
959 (
960 field.name.normalize_crlf(),
961 field.replace_value().normalize_crlf(),
962 )
963 }),
964 );
965
966 self.plan_to_navigate(load_data, target, history_handling);
967 }
968
969 #[allow(clippy::too_many_arguments)]
971 fn submit_entity_body(
972 &self,
973 cx: &mut JSContext,
974 form_data: &mut [FormDatum],
975 mut load_data: LoadData,
976 enctype: FormEncType,
977 encoding: &'static Encoding,
978 target: &Window,
979 history_handling: NavigationHistoryBehavior,
980 ) {
981 let boundary = generate_boundary();
982 let bytes = match enctype {
983 FormEncType::UrlEncoded => {
984 load_data
985 .headers
986 .typed_insert(ContentType::from(mime::APPLICATION_WWW_FORM_URLENCODED));
987
988 let mut url = load_data.url.clone();
989 self.set_url_query_pairs(
990 &mut url,
991 form_data.iter().map(|field| {
995 (
996 field.name.normalize_crlf(),
997 field.replace_value().normalize_crlf(),
998 )
999 }),
1000 );
1001
1002 url.query().unwrap_or("").to_string().into_bytes()
1003 },
1004 FormEncType::MultipartFormData => {
1005 let mime: Mime = format!("multipart/form-data; boundary={}", boundary)
1006 .parse()
1007 .unwrap();
1008 load_data.headers.typed_insert(ContentType::from(mime));
1009 encode_multipart_form_data(form_data, boundary, encoding)
1010 },
1011 FormEncType::TextPlain => {
1012 load_data
1013 .headers
1014 .typed_insert(ContentType::from(mime::TEXT_PLAIN));
1015 let pairs = form_data.iter().map(|field| {
1019 (
1020 field.name.normalize_crlf(),
1021 field.replace_value().normalize_crlf(),
1022 )
1023 });
1024 let body = encode_plaintext(pairs);
1027 encode_with_html_fallback(&body, encoding).into_owned()
1029 },
1030 };
1031
1032 let global = self.global();
1033
1034 let request_body = bytes
1035 .extract(cx, &global, false)
1036 .expect("Couldn't extract body.")
1037 .into_net_request_body(cx)
1038 .0;
1039 load_data.data = Some(request_body);
1040
1041 self.plan_to_navigate(load_data, target, history_handling);
1042 }
1043
1044 fn set_url_query_pairs<T>(
1045 &self,
1046 url: &mut servo_url::ServoUrl,
1047 pairs: impl Iterator<Item = (T, String)>,
1048 ) where
1049 T: AsRef<str>,
1050 {
1051 let encoding = self.pick_encoding();
1052 url.as_mut_url()
1053 .query_pairs_mut()
1054 .encoding_override(Some(&|s| encode_with_html_fallback(s, encoding)))
1055 .clear()
1056 .extend_pairs(pairs);
1057 }
1058
1059 fn plan_to_navigate(
1061 &self,
1062 mut load_data: LoadData,
1063 target: &Window,
1064 history_handling: NavigationHistoryBehavior,
1065 ) {
1066 let document = self.owner_document();
1071 let element = self.upcast::<Element>();
1072 let referrer = match element.get_attribute_string_value(&local_name!("rel")) {
1073 Some(link_types) if link_types.contains("noreferrer") => Referrer::NoReferrer,
1074 _ => document.window().as_global_scope().get_referrer(),
1075 };
1076
1077 self.planned_navigation
1080 .set(self.planned_navigation.get().wrapping_add(1));
1081 let planned_navigation = self.planned_navigation.get();
1082
1083 let ongoing_navigation = target.set_ongoing_navigation();
1098
1099 let referrer_policy = document.get_referrer_policy();
1100 load_data.referrer = referrer;
1101 load_data.referrer_policy = referrer_policy;
1102
1103 if let Some(window_proxy) = target.undiscarded_window_proxy() &&
1106 let Some(frame) = window_proxy
1107 .frame_element()
1108 .and_then(|e| e.downcast::<HTMLIFrameElement>())
1109 {
1110 frame.note_pending_navigation()
1111 }
1112
1113 let form = Trusted::new(self);
1116 let window = Trusted::new(target);
1117 let task = task!(navigate_to_form_planned_navigation: move |cx| {
1118 if planned_navigation != form.root().planned_navigation.get() {
1122 return;
1123 }
1124
1125 if ongoing_navigation != window.root().ongoing_navigation() {
1128 return;
1129 }
1130
1131 navigate(
1133 cx,
1134 &window.root(),
1135 history_handling,
1136 false,
1137 load_data,
1138 )
1139 });
1140
1141 target
1146 .global()
1147 .task_manager()
1148 .dom_manipulation_task_source()
1149 .queue(task)
1150 }
1151
1152 fn interactive_validation(&self, cx: &mut JSContext) -> Result<(), ()> {
1155 let unhandled_invalid_controls = match self.static_validation(cx) {
1160 Ok(()) => return Ok(()),
1161 Err(err) => err,
1162 };
1163
1164 let mut first = true;
1167
1168 for elem in unhandled_invalid_controls {
1169 if let Some(validatable) = elem.as_maybe_validatable() {
1170 error!("Validation error: {}", validatable.validation_message(cx));
1171 }
1172 if first && let Some(html_elem) = elem.downcast::<HTMLElement>() {
1173 html_elem.Focus(cx, &FocusOptions::default());
1180 first = false;
1181 }
1182 }
1183
1184 Err(())
1188 }
1189
1190 fn static_validation(&self, cx: &mut JSContext) -> Result<(), Vec<DomRoot<Element>>> {
1193 let invalid_controls = self
1195 .controls
1196 .borrow()
1197 .iter()
1198 .filter_map(|field| {
1199 if let Some(element) = field.downcast::<Element>() {
1200 if element.is_invalid(cx, true) {
1201 Some(DomRoot::from_ref(element))
1202 } else {
1203 None
1204 }
1205 } else {
1206 None
1207 }
1208 })
1209 .collect::<Vec<DomRoot<Element>>>();
1210 if invalid_controls.is_empty() {
1212 return Ok(());
1213 }
1214 let unhandled_invalid_controls = invalid_controls
1216 .into_iter()
1217 .filter_map(|field| {
1218 let not_canceled = field
1221 .upcast::<EventTarget>()
1222 .fire_cancelable_event(cx, atom!("invalid"));
1223 if not_canceled {
1225 return Some(field);
1226 }
1227 None
1228 })
1229 .collect::<Vec<DomRoot<Element>>>();
1230 Err(unhandled_invalid_controls)
1232 }
1233
1234 fn get_unclean_dataset(
1239 &self,
1240 cx: &mut JSContext,
1241 submitter: Option<FormSubmitterElement>,
1242 encoding: Option<&'static Encoding>,
1243 ) -> Vec<FormDatum> {
1244 let mut data_set = Vec::new();
1245 for child in self.controls.borrow().iter() {
1246 if child.disabled_state() {
1248 continue;
1249 }
1250 let child = child.upcast::<Node>();
1251
1252 if child.ancestors().any(|a| a.is::<HTMLDataListElement>()) {
1254 continue;
1255 }
1256 if let NodeTypeId::Element(ElementTypeId::HTMLElement(element)) = child.type_id() {
1257 match element {
1258 HTMLElementTypeId::HTMLInputElement => {
1259 let input = child.downcast::<HTMLInputElement>().unwrap();
1260 let (ref mut form_datums, should_continue) =
1261 input.form_datums(submitter, encoding);
1262 data_set.append(form_datums);
1263 if should_continue {
1264 continue;
1265 }
1266 },
1267 HTMLElementTypeId::HTMLButtonElement => {
1268 let button = child.downcast::<HTMLButtonElement>().unwrap();
1269 if let Some(datum) = button.form_datum(submitter) {
1270 data_set.push(datum);
1271 }
1272 },
1273 HTMLElementTypeId::HTMLObjectElement => {
1274 },
1276 HTMLElementTypeId::HTMLSelectElement => {
1277 let select = child.downcast::<HTMLSelectElement>().unwrap();
1278 select.push_form_data(cx.no_gc(), &mut data_set);
1279 },
1280 HTMLElementTypeId::HTMLTextAreaElement => {
1281 let textarea = child.downcast::<HTMLTextAreaElement>().unwrap();
1282 let name = textarea.Name();
1283 if !name.is_empty() {
1284 data_set.push(FormDatum {
1285 ty: textarea.Type(),
1286 name,
1287 value: FormDatumValue::String(textarea.Value()),
1288 });
1289 }
1290 },
1291 HTMLElementTypeId::HTMLElement => {
1292 let custom = child.downcast::<HTMLElement>().unwrap();
1293 if custom.is_form_associated_custom_element() {
1294 let internals = custom.ensure_element_internals(cx);
1296 internals.perform_entry_construction(&mut data_set);
1297 }
1299 },
1300 _ => (),
1301 }
1302 }
1303
1304 let child_element = child.downcast::<Element>().unwrap();
1306 let dirname = child_element.get_string_attribute(&local_name!("dirname"));
1307
1308 let is_input_auto_directionality_form_associated_element = child_element
1314 .downcast::<HTMLInputElement>()
1315 .is_some_and(|input| input.is_auto_directionality_form_associated_element());
1316 let is_textarea_element = child_element.is::<HTMLTextAreaElement>();
1317 if !dirname.is_empty() &&
1318 (is_input_auto_directionality_form_associated_element || is_textarea_element)
1319 {
1320 let dir = DOMString::from(child_element.directionality());
1323
1324 data_set.push(FormDatum {
1326 ty: DOMString::from_static("string"),
1327 name: dirname,
1328 value: FormDatumValue::String(dir),
1329 });
1330 }
1331 }
1332 data_set
1333 }
1334
1335 pub(crate) fn get_form_dataset(
1337 &self,
1338 cx: &mut JSContext,
1339 submitter: Option<FormSubmitterElement>,
1340 encoding: Option<&'static Encoding>,
1341 ) -> Option<Vec<FormDatum>> {
1342 if self.constructing_entry_list.get() {
1344 return None;
1345 }
1346
1347 self.constructing_entry_list.set(true);
1349
1350 let ret = self.get_unclean_dataset(cx, submitter, encoding);
1352
1353 let window = self.owner_window();
1354
1355 let form_data = FormData::new(cx, Some(ret), &window.global());
1357
1358 let event = FormDataEvent::new(
1360 cx,
1361 &window,
1362 atom!("formdata"),
1363 EventBubbles::Bubbles,
1364 EventCancelable::NotCancelable,
1365 &form_data,
1366 );
1367
1368 event
1369 .upcast::<Event>()
1370 .fire(cx, self.upcast::<EventTarget>());
1371
1372 self.constructing_entry_list.set(false);
1374
1375 Some(form_data.datums())
1377 }
1378
1379 pub(crate) fn reset(&self, cx: &mut JSContext, _reset_method_flag: ResetFrom) {
1381 if self.marked_for_reset.get() {
1383 return;
1384 } else {
1385 self.marked_for_reset.set(true);
1386 }
1387
1388 let reset = self
1392 .upcast::<EventTarget>()
1393 .fire_bubbling_cancelable_event(cx, atom!("reset"));
1394 if !reset {
1395 return;
1396 }
1397
1398 let controls: Vec<_> = self
1399 .controls
1400 .borrow()
1401 .iter()
1402 .map(|c| c.as_rooted())
1403 .collect();
1404
1405 for child in controls {
1406 child.reset(cx);
1407 }
1408 self.marked_for_reset.set(false);
1409 }
1410
1411 fn add_control<T: ?Sized + FormControl>(&self, cx: &mut JSContext, control: &T) {
1412 {
1413 let root = self.upcast::<Element>().root_element();
1414 let root = root.upcast::<Node>();
1415 let mut controls = self.controls.safe_borrow_mut(cx.no_gc());
1416
1417 let control_element = control.to_element();
1423 if control_element.upcast::<Node>().is_in_a_document_tree() {
1424 controls.insert_pre_order(control_element, root);
1425 } else {
1426 controls.push(Dom::from_ref(control_element));
1427 }
1428 }
1429 self.update_validity(cx);
1430 }
1431
1432 fn remove_control<T: ?Sized + FormControl>(&self, cx: &mut JSContext, control: &T) {
1433 {
1434 let control = control.to_element();
1435 let mut controls = self.controls.safe_borrow_mut(cx.no_gc());
1436 controls
1437 .iter()
1438 .position(|c| &**c == control)
1439 .map(|idx| controls.remove(idx));
1440
1441 let mut past_names_map = self.past_names_map.safe_borrow_mut(cx.no_gc());
1446 past_names_map.0.retain(|_k, v| v.0 != control);
1447 }
1448 self.update_validity(cx);
1449 }
1450}
1451
1452impl Element {
1453 pub(crate) fn is_resettable(&self) -> bool {
1454 let NodeTypeId::Element(ElementTypeId::HTMLElement(element_type)) =
1455 self.upcast::<Node>().type_id()
1456 else {
1457 return false;
1458 };
1459 matches!(
1460 element_type,
1461 HTMLElementTypeId::HTMLInputElement |
1462 HTMLElementTypeId::HTMLSelectElement |
1463 HTMLElementTypeId::HTMLTextAreaElement |
1464 HTMLElementTypeId::HTMLOutputElement |
1465 HTMLElementTypeId::HTMLElement
1466 )
1467 }
1468
1469 pub(crate) fn reset(&self, cx: &mut JSContext) {
1470 if !self.is_resettable() {
1471 return;
1472 }
1473
1474 if let Some(input_element) = self.downcast::<HTMLInputElement>() {
1475 input_element.reset(cx);
1476 } else if let Some(select_element) = self.downcast::<HTMLSelectElement>() {
1477 select_element.reset(cx);
1478 } else if let Some(textarea_element) = self.downcast::<HTMLTextAreaElement>() {
1479 textarea_element.reset(cx);
1480 } else if let Some(output_element) = self.downcast::<HTMLOutputElement>() {
1481 output_element.reset(cx);
1482 } else if let Some(html_element) = self.downcast::<HTMLElement>() &&
1483 html_element.is_form_associated_custom_element()
1484 {
1485 ScriptThread::enqueue_callback_reaction(
1486 cx,
1487 html_element.upcast::<Element>(),
1488 CallbackReaction::FormReset,
1489 None,
1490 )
1491 }
1492 }
1493}
1494
1495#[derive(JSTraceable, MallocSizeOf)]
1496#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
1497pub(crate) enum FormDatumValueUnrooted {
1498 File(Dom<File>),
1499 String(DOMString),
1500}
1501
1502#[derive(JSTraceable, MallocSizeOf)]
1503#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
1504pub(crate) struct FormDatumUnrooted {
1505 pub(crate) ty: DOMString,
1506 pub(crate) name: DOMString,
1507 pub(crate) value: FormDatumValueUnrooted,
1508}
1509
1510impl FormDatumUnrooted {
1511 pub(crate) fn root(&self) -> FormDatum {
1512 FormDatum {
1513 ty: self.ty.clone(),
1514 name: self.name.clone(),
1515 value: match &self.value {
1516 FormDatumValueUnrooted::File(file) => FormDatumValue::File(file.as_rooted()),
1517 FormDatumValueUnrooted::String(s) => FormDatumValue::String(s.clone()),
1518 },
1519 }
1520 }
1521}
1522
1523impl From<FormDatum> for FormDatumUnrooted {
1524 fn from(value: FormDatum) -> Self {
1525 Self {
1526 ty: value.ty,
1527 name: value.name,
1528 value: match value.value {
1529 FormDatumValue::File(file) => FormDatumValueUnrooted::File(file.as_traced()),
1530 FormDatumValue::String(s) => FormDatumValueUnrooted::String(s),
1531 },
1532 }
1533 }
1534}
1535
1536#[derive(JSTraceable, MallocSizeOf)]
1537pub(crate) enum FormDatumValue {
1538 File(DomRoot<File>),
1539 String(DOMString),
1540}
1541
1542#[derive(JSTraceable, MallocSizeOf)]
1543pub(crate) struct FormDatum {
1544 pub(crate) ty: DOMString,
1545 pub(crate) name: DOMString,
1546 pub(crate) value: FormDatumValue,
1547}
1548
1549impl FormDatum {
1550 pub(crate) fn replace_value(&self) -> DOMString {
1551 match self.value {
1552 FormDatumValue::File(ref f) => f.name().clone().root(),
1553 FormDatumValue::String(ref s) => s.clone(),
1554 }
1555 }
1556}
1557
1558#[derive(Clone, Copy, MallocSizeOf)]
1559pub(crate) enum FormEncType {
1560 TextPlain,
1561 UrlEncoded,
1562 MultipartFormData,
1563}
1564
1565#[derive(Clone, Copy, MallocSizeOf)]
1566pub(crate) enum FormMethod {
1567 Get,
1568 Post,
1569 Dialog,
1570}
1571
1572#[derive(Clone, Copy, MallocSizeOf)]
1574pub(crate) enum FormSubmitterElement<'a> {
1575 Form(&'a HTMLFormElement),
1576 Input(&'a HTMLInputElement),
1577 Button(&'a HTMLButtonElement),
1578 }
1581
1582impl FormSubmitterElement<'_> {
1583 fn action(&self) -> DOMString {
1585 match *self {
1586 FormSubmitterElement::Form(form) => form.Action(),
1587 FormSubmitterElement::Input(input_element) => input_element
1588 .to_element()
1589 .get_nullable_string_attribute(&local_name!("formaction"))
1590 .unwrap_or_else(|| {
1591 input_element
1592 .form_owner()
1593 .map(|form| form.Action())
1594 .unwrap_or_default()
1595 }),
1596 FormSubmitterElement::Button(button_element) => button_element
1597 .to_element()
1598 .get_nullable_string_attribute(&local_name!("formaction"))
1599 .unwrap_or_else(|| {
1600 button_element
1601 .form_owner()
1602 .map(|form| form.Action())
1603 .unwrap_or_default()
1604 }),
1605 }
1606 }
1607
1608 fn enctype(&self) -> FormEncType {
1609 let attr = match *self {
1610 FormSubmitterElement::Form(form) => form.Enctype(),
1611 FormSubmitterElement::Input(input_element) => input_element.get_form_attribute(
1612 &local_name!("formenctype"),
1613 |i| i.FormEnctype(),
1614 |f| f.Enctype(),
1615 ),
1616 FormSubmitterElement::Button(button_element) => button_element.get_form_attribute(
1617 &local_name!("formenctype"),
1618 |i| i.FormEnctype(),
1619 |f| f.Enctype(),
1620 ),
1621 };
1622 match_domstring_ascii!(attr,
1625 "multipart/form-data" => FormEncType::MultipartFormData,
1626 "text/plain" => FormEncType::TextPlain,
1627 _ => FormEncType::UrlEncoded,
1628 )
1629 }
1630
1631 fn method(&self) -> FormMethod {
1632 let attr = match *self {
1633 FormSubmitterElement::Form(form) => form.Method(),
1634 FormSubmitterElement::Input(input_element) => input_element.get_form_attribute(
1635 &local_name!("formmethod"),
1636 |i| i.FormMethod(),
1637 |f| f.Method(),
1638 ),
1639 FormSubmitterElement::Button(button_element) => button_element.get_form_attribute(
1640 &local_name!("formmethod"),
1641 |i| i.FormMethod(),
1642 |f| f.Method(),
1643 ),
1644 };
1645 match_domstring_ascii!(attr,
1646 "dialog" => FormMethod::Dialog,
1647 "post" => FormMethod::Post,
1648 _ => FormMethod::Get,
1649 )
1650 }
1651
1652 fn target(&self) -> DOMString {
1653 match *self {
1654 FormSubmitterElement::Form(form) => form.Target(),
1655 FormSubmitterElement::Input(input_element) => input_element.get_form_attribute(
1656 &local_name!("formtarget"),
1657 |i| i.FormTarget(),
1658 |f| f.Target(),
1659 ),
1660 FormSubmitterElement::Button(button_element) => button_element.get_form_attribute(
1661 &local_name!("formtarget"),
1662 |i| i.FormTarget(),
1663 |f| f.Target(),
1664 ),
1665 }
1666 }
1667
1668 fn no_validate(&self, _form_owner: &HTMLFormElement) -> bool {
1669 match *self {
1670 FormSubmitterElement::Form(form) => form.NoValidate(),
1671 FormSubmitterElement::Input(input_element) => input_element.get_form_boolean_attribute(
1672 &local_name!("formnovalidate"),
1673 |i| i.FormNoValidate(),
1674 |f| f.NoValidate(),
1675 ),
1676 FormSubmitterElement::Button(button_element) => button_element
1677 .get_form_boolean_attribute(
1678 &local_name!("formnovalidate"),
1679 |i| i.FormNoValidate(),
1680 |f| f.NoValidate(),
1681 ),
1682 }
1683 }
1684
1685 pub(crate) fn is_submit_button(&self) -> bool {
1687 match *self {
1688 FormSubmitterElement::Input(input_element) => input_element.is_submit_button(),
1691 FormSubmitterElement::Button(button_element) => button_element.is_submit_button(),
1693 _ => false,
1694 }
1695 }
1696
1697 pub(crate) fn form_owner(&self) -> Option<DomRoot<HTMLFormElement>> {
1699 match *self {
1700 FormSubmitterElement::Button(button_el) => button_el.form_owner(),
1701 FormSubmitterElement::Input(input_el) => input_el.form_owner(),
1702 _ => None,
1703 }
1704 }
1705}
1706
1707pub(crate) trait FormControl: DomObject<ReflectorType = ()> + NodeTraits {
1708 fn form_owner(&self) -> Option<DomRoot<HTMLFormElement>>;
1709 fn set_form_owner(&self, cx: &mut JSContext, form: Option<&HTMLFormElement>);
1710 fn to_html_element(&self) -> &HTMLElement;
1711
1712 fn to_element(&self) -> &Element {
1713 self.to_html_element().upcast::<Element>()
1714 }
1715
1716 fn is_listed(&self) -> bool {
1717 self.to_html_element().is_listed_element()
1718 }
1719
1720 fn set_form_owner_from_parser(&self, cx: &mut JSContext, form: &HTMLFormElement) {
1725 let elem = self.to_element();
1726 let node = elem.upcast::<Node>();
1727 node.set_flag(NodeFlags::PARSER_ASSOCIATED_FORM_OWNER, true);
1728 form.add_control(cx, self);
1729 self.set_form_owner(cx, Some(form));
1730 }
1731
1732 fn reset_form_owner(&self, cx: &mut JSContext) {
1734 let elem = self.to_element();
1735 let node = elem.upcast::<Node>();
1736 let old_owner = self.form_owner();
1737 let has_form_id = elem.has_attribute(&local_name!("form"));
1738 let nearest_form_ancestor = node
1739 .ancestors()
1740 .find_map(DomRoot::downcast::<HTMLFormElement>);
1741
1742 if old_owner.is_some() &&
1744 !(self.is_listed() && has_form_id) &&
1745 nearest_form_ancestor == old_owner
1746 {
1747 return;
1748 }
1749
1750 let new_owner = if self.is_listed() && has_form_id && elem.is_connected() {
1752 let form_id = elem.get_string_attribute(&local_name!("form"));
1756 let first_relevant_element = if let Some(shadow_root) = self.containing_shadow_root() {
1757 shadow_root
1758 .upcast::<DocumentFragment>()
1759 .GetElementById(cx, form_id)
1760 } else {
1761 node.owner_document().GetElementById(cx, form_id)
1762 };
1763
1764 first_relevant_element.and_then(DomRoot::downcast::<HTMLFormElement>)
1765 } else {
1766 nearest_form_ancestor
1768 };
1769
1770 if old_owner != new_owner {
1771 if let Some(o) = old_owner {
1772 o.remove_control(cx, self);
1773 }
1774 if let Some(ref new_owner) = new_owner {
1775 new_owner.add_control(cx, self);
1776 }
1777 if let Some(html_elem) = elem.downcast::<HTMLElement>() &&
1779 html_elem.is_form_associated_custom_element()
1780 {
1781 ScriptThread::enqueue_callback_reaction(
1782 cx,
1783 elem,
1784 CallbackReaction::FormAssociated(
1785 new_owner.as_ref().map(|form| DomRoot::from_ref(&**form)),
1786 ),
1787 None,
1788 )
1789 }
1790 self.set_form_owner(cx, new_owner.as_deref());
1791 }
1792 }
1793
1794 fn form_attribute_mutated(&self, cx: &mut JSContext, mutation: AttributeMutation) {
1796 match mutation {
1797 AttributeMutation::Set(_) => {
1798 self.register_if_necessary();
1799 },
1800 AttributeMutation::Removed => {
1801 self.unregister_if_necessary();
1802 },
1803 }
1804
1805 self.reset_form_owner(cx);
1806 }
1807
1808 fn register_if_necessary(&self) {
1810 let elem = self.to_element();
1811 let form_id = elem.get_string_attribute(&local_name!("form"));
1812 let node = elem.upcast::<Node>();
1813
1814 if self.is_listed() && !form_id.is_empty() && node.is_connected() {
1815 node.owner_document()
1816 .register_form_id_listener(form_id, self);
1817 }
1818 }
1819
1820 fn unregister_if_necessary(&self) {
1821 let elem = self.to_element();
1822 let form_id = elem.get_string_attribute(&local_name!("form"));
1823
1824 if self.is_listed() && !form_id.is_empty() {
1825 elem.owner_document()
1826 .unregister_form_id_listener(form_id, self);
1827 }
1828 }
1829
1830 fn bind_form_control_to_tree(&self, cx: &mut JSContext) {
1832 let elem = self.to_element();
1833 let node = elem.upcast::<Node>();
1834
1835 let must_skip_reset = node.get_flag(NodeFlags::PARSER_ASSOCIATED_FORM_OWNER);
1840 node.set_flag(NodeFlags::PARSER_ASSOCIATED_FORM_OWNER, false);
1841
1842 if !must_skip_reset {
1843 self.form_attribute_mutated(cx, AttributeMutation::Set(None));
1844 }
1845 }
1846
1847 fn unbind_form_control_from_tree(&self, cx: &mut JSContext) {
1849 let elem = self.to_element();
1850 let has_form_attr = elem.has_attribute(&local_name!("form"));
1851 let same_subtree = self
1852 .form_owner()
1853 .is_none_or(|form| elem.is_in_same_home_subtree(&*form));
1854
1855 self.unregister_if_necessary();
1856
1857 if !same_subtree || (self.is_listed() && has_form_attr) {
1863 self.reset_form_owner(cx);
1864 }
1865 }
1866
1867 fn get_form_attribute<InputFn, OwnerFn>(
1868 &self,
1869 attr: &LocalName,
1870 input: InputFn,
1871 owner: OwnerFn,
1872 ) -> DOMString
1873 where
1874 InputFn: Fn(&Self) -> DOMString,
1875 OwnerFn: Fn(&HTMLFormElement) -> DOMString,
1876 Self: Sized,
1877 {
1878 if self.to_element().has_attribute(attr) {
1879 input(self)
1880 } else {
1881 self.form_owner().map_or(DOMString::new(), |t| owner(&t))
1882 }
1883 }
1884
1885 fn get_form_boolean_attribute<InputFn, OwnerFn>(
1886 &self,
1887 attr: &LocalName,
1888 input: InputFn,
1889 owner: OwnerFn,
1890 ) -> bool
1891 where
1892 InputFn: Fn(&Self) -> bool,
1893 OwnerFn: Fn(&HTMLFormElement) -> bool,
1894 Self: Sized,
1895 {
1896 if self.to_element().has_attribute(attr) {
1897 input(self)
1898 } else {
1899 self.form_owner().is_some_and(|t| owner(&t))
1900 }
1901 }
1902
1903 fn is_candidate_for_constraint_validation(&self, no_gc: &NoGC) -> bool {
1905 let element = self.to_element();
1906 let html_element = element.downcast::<HTMLElement>();
1907 if let Some(html_element) = html_element {
1908 html_element.is_submittable_element() || element.is_instance_validatable(no_gc)
1909 } else {
1910 false
1911 }
1912 }
1913
1914 fn moving_steps(&self, cx: &mut JSContext) {
1915 let same_subtree = self
1918 .form_owner()
1919 .is_none_or(|form| self.to_element().is_in_same_home_subtree(&*form));
1920 if !same_subtree {
1921 self.reset_form_owner(cx)
1922 }
1923 }
1924
1925 }
1928
1929impl VirtualMethods for HTMLFormElement {
1930 fn super_type(&self) -> Option<&dyn VirtualMethods> {
1931 Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods)
1932 }
1933
1934 fn unbind_from_tree(&self, cx: &mut JSContext, context: &UnbindContext) {
1935 self.super_type().unwrap().unbind_from_tree(cx, context);
1936
1937 rooted_vec!(let mut to_reset);
1940 to_reset.extend(
1941 self.controls
1942 .borrow()
1943 .iter()
1944 .filter(|c| !c.is_in_same_home_subtree(self))
1945 .cloned(),
1946 );
1947
1948 for control in to_reset.iter() {
1949 control
1950 .as_maybe_form_control()
1951 .expect("Element must be a form control")
1952 .reset_form_owner(cx);
1953 }
1954 }
1955
1956 fn parse_plain_attribute(&self, name: &LocalName, value: DOMString) -> AttrValue {
1957 match name {
1958 &local_name!("rel") => AttrValue::from_serialized_tokenlist(value.into()),
1959 _ => self
1960 .super_type()
1961 .unwrap()
1962 .parse_plain_attribute(name, value),
1963 }
1964 }
1965
1966 fn attribute_mutated(
1967 &self,
1968 cx: &mut JSContext,
1969 attr: AttrRef<'_>,
1970 mutation: AttributeMutation,
1971 ) {
1972 self.super_type()
1973 .unwrap()
1974 .attribute_mutated(cx, attr, mutation);
1975
1976 match *attr.local_name() {
1977 local_name!("rel") | local_name!("rev") => {
1978 self.relations
1979 .set(LinkRelations::for_element(self.upcast()));
1980 },
1981 _ => {},
1982 }
1983 }
1984}
1985
1986pub(crate) trait FormControlElementHelpers {
1987 fn as_maybe_form_control(&self) -> Option<&dyn FormControl>;
1988}
1989
1990impl FormControlElementHelpers for Element {
1991 fn as_maybe_form_control(&self) -> Option<&dyn FormControl> {
1992 let node = self.upcast::<Node>();
1993
1994 match node.type_id() {
1995 NodeTypeId::Element(ElementTypeId::HTMLElement(
1996 HTMLElementTypeId::HTMLButtonElement,
1997 )) => Some(self.downcast::<HTMLButtonElement>().unwrap() as &dyn FormControl),
1998 NodeTypeId::Element(ElementTypeId::HTMLElement(
1999 HTMLElementTypeId::HTMLFieldSetElement,
2000 )) => Some(self.downcast::<HTMLFieldSetElement>().unwrap() as &dyn FormControl),
2001 NodeTypeId::Element(ElementTypeId::HTMLElement(
2002 HTMLElementTypeId::HTMLImageElement,
2003 )) => Some(self.downcast::<HTMLImageElement>().unwrap() as &dyn FormControl),
2004 NodeTypeId::Element(ElementTypeId::HTMLElement(
2005 HTMLElementTypeId::HTMLInputElement,
2006 )) => Some(self.downcast::<HTMLInputElement>().unwrap() as &dyn FormControl),
2007 NodeTypeId::Element(ElementTypeId::HTMLElement(
2008 HTMLElementTypeId::HTMLLegendElement,
2009 )) => Some(self.downcast::<HTMLLegendElement>().unwrap() as &dyn FormControl),
2010 NodeTypeId::Element(ElementTypeId::HTMLElement(
2011 HTMLElementTypeId::HTMLObjectElement,
2012 )) => Some(self.downcast::<HTMLObjectElement>().unwrap() as &dyn FormControl),
2013 NodeTypeId::Element(ElementTypeId::HTMLElement(
2014 HTMLElementTypeId::HTMLOutputElement,
2015 )) => Some(self.downcast::<HTMLOutputElement>().unwrap() as &dyn FormControl),
2016 NodeTypeId::Element(ElementTypeId::HTMLElement(
2017 HTMLElementTypeId::HTMLSelectElement,
2018 )) => Some(self.downcast::<HTMLSelectElement>().unwrap() as &dyn FormControl),
2019 NodeTypeId::Element(ElementTypeId::HTMLElement(
2020 HTMLElementTypeId::HTMLTextAreaElement,
2021 )) => Some(self.downcast::<HTMLTextAreaElement>().unwrap() as &dyn FormControl),
2022 _ => self.downcast::<HTMLElement>().and_then(|elem| {
2023 if elem.is_form_associated_custom_element() {
2024 Some(elem as &dyn FormControl)
2025 } else {
2026 None
2027 }
2028 }),
2029 }
2030 }
2031}
2032
2033fn encode_plaintext(pairs: impl Iterator<Item = (String, String)>) -> String {
2035 let mut result = String::new();
2037 for (name, value) in pairs {
2039 result.push_str(&name);
2041 result.push('=');
2043 result.push_str(&value);
2045 result.push_str("\r\n");
2048 }
2049 result
2051}
2052
2053fn encode_with_html_fallback<'a>(input: &'a str, encoding: &'static Encoding) -> Cow<'a, [u8]> {
2059 encoding.encode(input).0
2060}
2061
2062pub(crate) fn encode_multipart_form_data(
2064 form_data: &mut [FormDatum],
2065 boundary: String,
2066 encoding: &'static Encoding,
2067) -> Vec<u8> {
2068 let mut result = vec![];
2069
2070 fn escape_header_bytes(input: &[u8]) -> Vec<u8> {
2077 let mut output = Vec::with_capacity(input.len());
2078 for &b in input {
2079 match b {
2080 b'\n' => output.extend(b"%0A"),
2081 b'\r' => output.extend(b"%0D"),
2082 b'"' => output.extend(b"%22"),
2083 _ => output.push(b),
2084 }
2085 }
2086 output
2087 }
2088
2089 for entry in form_data.iter_mut() {
2090 entry.name = entry.name.normalize_crlf().into();
2094
2095 if let FormDatumValue::String(ref s) = entry.value {
2100 entry.value = FormDatumValue::String(s.normalize_crlf().into());
2101 }
2102
2103 let mut boundary_bytes = format!("--{}\r\n", boundary).into_bytes();
2105 result.append(&mut boundary_bytes);
2106
2107 let name_str = &*entry.name.str();
2109 let encoded_name = encode_with_html_fallback(name_str, encoding);
2110 let escaped_name = escape_header_bytes(&encoded_name);
2112
2113 match entry.value {
2114 FormDatumValue::String(ref s) => {
2115 let value_str = &*s.str();
2117 let encoded_value = encode_with_html_fallback(value_str, encoding);
2118
2119 result.extend(b"Content-Disposition: form-data; name=\"");
2121 result.extend(&escaped_name);
2122 result.extend(b"\"\r\n\r\n");
2123 result.extend_from_slice(&encoded_value);
2124 result.extend(b"\r\n");
2125 },
2126 FormDatumValue::File(ref f) => {
2127 let filename_str = &*f.name().str();
2129 let encoded_filename = encode_with_html_fallback(filename_str, encoding);
2130 let escaped_filename = escape_header_bytes(&encoded_filename);
2132
2133 result.extend(b"Content-Disposition: form-data; name=\"");
2134 result.extend(&escaped_name);
2135 result.extend(b"\"; filename=\"");
2136 result.extend(&escaped_filename);
2137
2138 result.extend(b"\"\r\nContent-Type: ");
2140
2141 let content_type: Mime = f
2142 .upcast::<Blob>()
2143 .Type()
2144 .parse()
2145 .unwrap_or(mime::TEXT_PLAIN);
2146 result.extend(content_type.as_ref().as_bytes());
2147 result.extend(b"\r\n\r\n");
2148
2149 let mut bytes = f.upcast::<Blob>().get_bytes().unwrap_or_default();
2150
2151 result.append(&mut bytes);
2152 result.extend(b"\r\n");
2153 },
2154 }
2155 }
2156
2157 let mut boundary_bytes = format!("--{boundary}--\r\n").into_bytes();
2159 result.append(&mut boundary_bytes);
2160
2161 result
2162}
2163
2164pub(crate) fn generate_boundary() -> String {
2166 let i1 = random::<u32>();
2167 let i2 = random::<u32>();
2168
2169 format!("---------------------------{0}{1}", i1, i2)
2170}