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::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#[dom_struct]
93pub(crate) struct HTMLFormElement {
94 htmlelement: HTMLElement,
95 marked_for_reset: Cell<bool>,
96 constructing_entry_list: Cell<bool>,
98 elements: DomOnceCell<HTMLFormControlsCollection>,
99 controls: DomRefCell<Vec<Dom<Element>>>,
100
101 #[expect(clippy::type_complexity)]
103 past_names_map:
104 DomRefCell<HashMapTracedValues<Atom, (Dom<Element>, NoTrace<usize>), FxBuildHasher>>,
105
106 current_name_generation: Cell<usize>,
108
109 firing_submission_events: Cell<bool>,
110 rel_list: MutNullableDom<DOMTokenList>,
111
112 planned_navigation: Cell<usize>,
114
115 #[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 return !matches!(*inp.input_type(), InputType::Image(_));
174 } else {
175 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 make_getter!(AcceptCharset, "accept-charset");
218
219 make_setter!(SetAcceptCharset, "accept-charset");
221
222 make_form_action_getter!(Action, "action");
224
225 make_setter!(SetAction, "action");
227
228 make_enumerated_getter!(
230 Autocomplete,
231 "autocomplete",
232 "on" | "off",
233 missing => "on",
234 invalid => "on"
235 );
236
237 make_setter!(SetAutocomplete, "autocomplete");
239
240 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 make_setter!(SetEnctype, "enctype");
251
252 fn Encoding(&self) -> DOMString {
254 self.Enctype()
255 }
256
257 fn SetEncoding(&self, cx: &mut JSContext, value: DOMString) {
259 self.SetEnctype(cx, value)
260 }
261
262 make_enumerated_getter!(
264 Method,
265 "method",
266 "get" | "post" | "dialog",
267 missing => "get",
268 invalid => "get"
269 );
270
271 make_setter!(SetMethod, "method");
273
274 make_getter!(Name, "name");
276
277 make_atomic_setter!(SetName, "name");
279
280 make_bool_getter!(NoValidate, "novalidate");
282
283 make_bool_setter!(SetNoValidate, "novalidate");
285
286 make_getter!(Target, "target");
288
289 make_setter!(SetTarget, "target");
291
292 make_getter!(Rel, "rel");
294
295 fn Submit(&self, cx: &mut JSContext) {
297 self.submit(
298 cx,
299 SubmittedFrom::FromForm,
300 FormSubmitterElement::Form(self),
301 );
302 }
303
304 fn RequestSubmit(&self, cx: &mut JSContext, submitter: Option<&HTMLElement>) -> Fallible<()> {
306 let submitter: FormSubmitterElement = match submitter {
307 Some(submitter_element) => {
308 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 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 FormSubmitterElement::Form(self)
360 },
361 };
362 self.submit(cx, SubmittedFrom::NotFromForm, submitter);
364 Ok(())
365 }
366
367 fn Reset(&self, cx: &mut JSContext) {
369 self.reset(cx, ResetFrom::FromForm);
370 }
371
372 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 fn Length(&self, cx: &mut JSContext) -> u32 {
446 self.Elements(cx).Length(cx)
447 }
448
449 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 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 let mut candidates =
463 RadioNodeList::new_controls_except_image_inputs(cx, &window, self, &name);
464 let mut candidates_length = candidates.Length(cx);
465
466 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 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 if candidates_length > 1 {
486 return Some(RadioNodeListOrElement::RadioNodeList(candidates));
487 }
488
489 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 Some(RadioNodeListOrElement::Element(DomRoot::from_ref(
504 element_node.downcast::<Element>().unwrap(),
505 )))
506 }
507
508 fn SetRel(&self, cx: &mut JSContext, rel: DOMString) {
510 self.upcast::<Element>()
511 .set_tokenlist_attribute(cx, &local_name!("rel"), rel);
512 }
513
514 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 fn SupportedPropertyNames(&self, _: &NoGC) -> Vec<DOMString> {
532 #[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 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 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 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 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 sourced_names_vec.retain(|sn| !sn.name.to_string().is_empty());
649
650 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 fn CheckValidity(&self, cx: &mut JSContext) -> bool {
663 self.static_validation(cx).is_ok()
664 }
665
666 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 fn pick_encoding(&self) -> &'static Encoding {
687 if self
689 .upcast::<Element>()
690 .has_attribute(&local_name!("accept-charset"))
691 {
692 let input = self
694 .upcast::<Element>()
695 .get_string_attribute(&local_name!("accept-charset"));
696
697 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 return candidate_encodings.next().unwrap_or(UTF_8);
704 }
705
706 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 pub(crate) fn submit(
725 &self,
726 cx: &mut JSContext,
727 submit_method_flag: SubmittedFrom,
728 submitter: FormSubmitterElement,
729 ) {
730 if self.upcast::<Element>().cannot_navigate() {
732 return;
733 }
734
735 if self.constructing_entry_list.get() {
737 return;
738 }
739 let doc = self.owner_document();
741
742 if doc.has_active_sandboxing_flag(SandboxingFlagSet::SANDBOXED_FORMS_BROWSING_CONTEXT_FLAG)
745 {
746 return;
747 }
748
749 let base = doc.base_url();
750 if submit_method_flag == SubmittedFrom::NotFromForm {
753 if self.firing_submission_events.get() {
755 return;
756 }
757 self.firing_submission_events.set(true);
759 if !submitter.no_validate(self) && self.interactive_validation(cx).is_err() {
761 self.firing_submission_events.set(false);
762 return;
763 }
764 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 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 self.firing_submission_events.set(false);
793 if event.DefaultPrevented() {
795 return;
796 }
797 if self.upcast::<Element>().cannot_navigate() {
799 return;
800 }
801 }
802
803 let encoding = self.pick_encoding();
805
806 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 if self.upcast::<Element>().cannot_navigate() {
814 return;
815 }
816
817 let method = submitter.method();
819 let mut action = submitter.action();
824
825 if action.is_empty() {
827 action = DOMString::from(base.as_str());
828 }
829 let action_components = match doc.encoding_parse_a_url(&action.str()) {
831 Ok(url) => url,
832 Err(_) => return,
834 };
835 let scheme = action_components.scheme().to_owned();
837 let enctype = submitter.enctype();
839
840 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 None
850 };
851 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 let noopener = self.relations.get().get_element_noopener(target.as_ref());
860
861 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 return;
870 };
871
872 let Some(target_document) = chosen.document() else {
873 return;
874 };
875
876 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 match (&*scheme, method) {
903 (_, FormMethod::Dialog) => {
904 },
907 ("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 ("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 ("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 },
939 ("mailto", FormMethod::Get) => {
940 },
943 _ => (),
944 }
945 }
946
947 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 #[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 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 let pairs = form_data.iter().map(|field| {
1018 (
1019 field.name.normalize_crlf(),
1020 field.replace_value().normalize_crlf(),
1021 )
1022 });
1023 let body = encode_plaintext(pairs);
1026 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 fn plan_to_navigate(
1060 &self,
1061 mut load_data: LoadData,
1062 target: &Window,
1063 history_handling: NavigationHistoryBehavior,
1064 ) {
1065 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 self.planned_navigation
1079 .set(self.planned_navigation.get().wrapping_add(1));
1080 let planned_navigation = self.planned_navigation.get();
1081
1082 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 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 let form = Trusted::new(self);
1115 let window = Trusted::new(target);
1116 let task = task!(navigate_to_form_planned_navigation: move |cx| {
1117 if planned_navigation != form.root().planned_navigation.get() {
1121 return;
1122 }
1123
1124 if ongoing_navigation != window.root().ongoing_navigation() {
1127 return;
1128 }
1129
1130 navigate(
1132 cx,
1133 &window.root(),
1134 history_handling,
1135 false,
1136 load_data,
1137 )
1138 });
1139
1140 target
1145 .global()
1146 .task_manager()
1147 .dom_manipulation_task_source()
1148 .queue(task)
1149 }
1150
1151 fn interactive_validation(&self, cx: &mut JSContext) -> Result<(), ()> {
1154 let unhandled_invalid_controls = match self.static_validation(cx) {
1159 Ok(()) => return Ok(()),
1160 Err(err) => err,
1161 };
1162
1163 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 html_elem.Focus(cx, &FocusOptions::default());
1179 first = false;
1180 }
1181 }
1182
1183 Err(())
1187 }
1188
1189 fn static_validation(&self, cx: &mut JSContext) -> Result<(), Vec<DomRoot<Element>>> {
1192 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 if invalid_controls.is_empty() {
1211 return Ok(());
1212 }
1213 let unhandled_invalid_controls = invalid_controls
1215 .into_iter()
1216 .filter_map(|field| {
1217 let not_canceled = field
1220 .upcast::<EventTarget>()
1221 .fire_cancelable_event(cx, atom!("invalid"));
1222 if not_canceled {
1224 return Some(field);
1225 }
1226 None
1227 })
1228 .collect::<Vec<DomRoot<Element>>>();
1229 Err(unhandled_invalid_controls)
1231 }
1232
1233 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 if child.disabled_state() {
1247 continue;
1248 }
1249 let child = child.upcast::<Node>();
1250
1251 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 },
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 let internals = custom.ensure_element_internals(cx);
1295 internals.perform_entry_construction(&mut data_set);
1296 }
1298 },
1299 _ => (),
1300 }
1301 }
1302
1303 let child_element = child.downcast::<Element>().unwrap();
1305 let dirname = child_element.get_string_attribute(&local_name!("dirname"));
1306
1307 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 let dir = DOMString::from(child_element.directionality());
1322
1323 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 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 if self.constructing_entry_list.get() {
1343 return None;
1344 }
1345
1346 self.constructing_entry_list.set(true);
1348
1349 let ret = self.get_unclean_dataset(cx, submitter, encoding);
1351
1352 let window = self.owner_window();
1353
1354 let form_data = FormData::new(cx, Some(ret), &window.global());
1356
1357 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 self.constructing_entry_list.set(false);
1373
1374 Some(form_data.datums())
1376 }
1377
1378 pub(crate) fn reset(&self, cx: &mut JSContext, _reset_method_flag: ResetFrom) {
1380 if self.marked_for_reset.get() {
1382 return;
1383 } else {
1384 self.marked_for_reset.set(true);
1385 }
1386
1387 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 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 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#[derive(Clone, Copy, MallocSizeOf)]
1573pub(crate) enum FormSubmitterElement<'a> {
1574 Form(&'a HTMLFormElement),
1575 Input(&'a HTMLInputElement),
1576 Button(&'a HTMLButtonElement),
1577 }
1580
1581impl FormSubmitterElement<'_> {
1582 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 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 pub(crate) fn is_submit_button(&self) -> bool {
1686 match *self {
1687 FormSubmitterElement::Input(input_element) => input_element.is_submit_button(),
1690 FormSubmitterElement::Button(button_element) => button_element.is_submit_button(),
1692 _ => false,
1693 }
1694 }
1695
1696 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 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 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 if old_owner.is_some() &&
1743 !(self.is_listed() && has_form_id) &&
1744 nearest_form_ancestor == old_owner
1745 {
1746 return;
1747 }
1748
1749 let new_owner = if self.is_listed() && has_form_id && elem.is_connected() {
1751 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 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 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 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 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 fn bind_form_control_to_tree(&self, cx: &mut JSContext) {
1831 let elem = self.to_element();
1832 let node = elem.upcast::<Node>();
1833
1834 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 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 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 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 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 }
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 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
2035fn encode_plaintext(pairs: impl Iterator<Item = (String, String)>) -> String {
2037 let mut result = String::new();
2039 for (name, value) in pairs {
2041 result.push_str(&name);
2043 result.push('=');
2045 result.push_str(&value);
2047 result.push_str("\r\n");
2050 }
2051 result
2053}
2054
2055fn encode_with_html_fallback<'a>(input: &'a str, encoding: &'static Encoding) -> Cow<'a, [u8]> {
2061 encoding.encode(input).0
2062}
2063
2064pub(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 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 entry.name = entry.name.normalize_crlf().into();
2096
2097 if let FormDatumValue::String(ref s) = entry.value {
2102 entry.value = FormDatumValue::String(s.normalize_crlf().into());
2103 }
2104
2105 let mut boundary_bytes = format!("--{}\r\n", boundary).into_bytes();
2107 result.append(&mut boundary_bytes);
2108
2109 let name_str = &*entry.name.str();
2111 let encoded_name = encode_with_html_fallback(name_str, encoding);
2112 let escaped_name = escape_header_bytes(&encoded_name);
2114
2115 match entry.value {
2116 FormDatumValue::String(ref s) => {
2117 let value_str = &*s.str();
2119 let encoded_value = encode_with_html_fallback(value_str, encoding);
2120
2121 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 let filename_str = &*f.name().str();
2131 let encoded_filename = encode_with_html_fallback(filename_str, encoding);
2132 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 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 let mut boundary_bytes = format!("--{boundary}--\r\n").into_bytes();
2161 result.append(&mut boundary_bytes);
2162
2163 result
2164}
2165
2166pub(crate) fn generate_boundary() -> String {
2168 let i1 = random::<u32>();
2169 let i2 = random::<u32>();
2170
2171 format!("---------------------------{0}{1}", i1, i2)
2172}