1use std::borrow::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;
15use js::rust::HandleObject;
16use mime::{self, Mime};
17use net_traits::http_percent_encode;
18use net_traits::request::Referrer;
19use rand::random;
20use rustc_hash::FxBuildHasher;
21use script_bindings::match_domstring_ascii;
22use servo_constellation_traits::{LoadData, LoadOrigin, NavigationHistoryBehavior};
23use style::attr::AttrValue;
24use style::str::split_html_space_chars;
25use stylo_atoms::Atom;
26use stylo_dom::ElementState;
27
28use crate::body::Extractable;
29use crate::dom::attr::Attr;
30use crate::dom::bindings::cell::DomRefCell;
31use crate::dom::bindings::codegen::Bindings::AttrBinding::Attr_Binding::AttrMethods;
32use crate::dom::bindings::codegen::Bindings::BlobBinding::BlobMethods;
33use crate::dom::bindings::codegen::Bindings::DocumentBinding::DocumentMethods;
34use crate::dom::bindings::codegen::Bindings::EventBinding::EventMethods;
35use crate::dom::bindings::codegen::Bindings::HTMLButtonElementBinding::HTMLButtonElementMethods;
36use crate::dom::bindings::codegen::Bindings::HTMLElementBinding::HTMLElementMethods;
37use crate::dom::bindings::codegen::Bindings::HTMLFormControlsCollectionBinding::HTMLFormControlsCollectionMethods;
38use crate::dom::bindings::codegen::Bindings::HTMLFormElementBinding::HTMLFormElementMethods;
39use crate::dom::bindings::codegen::Bindings::HTMLInputElementBinding::HTMLInputElementMethods;
40use crate::dom::bindings::codegen::Bindings::HTMLOrSVGElementBinding::FocusOptions;
41use crate::dom::bindings::codegen::Bindings::HTMLTextAreaElementBinding::HTMLTextAreaElementMethods;
42use crate::dom::bindings::codegen::Bindings::NodeBinding::{NodeConstants, NodeMethods};
43use crate::dom::bindings::codegen::Bindings::NodeListBinding::NodeListMethods;
44use crate::dom::bindings::codegen::Bindings::RadioNodeListBinding::RadioNodeListMethods;
45use crate::dom::bindings::codegen::Bindings::WindowBinding::Window_Binding::WindowMethods;
46use crate::dom::bindings::codegen::UnionTypes::RadioNodeListOrElement;
47use crate::dom::bindings::error::{Error, Fallible};
48use crate::dom::bindings::inheritance::{Castable, ElementTypeId, HTMLElementTypeId, NodeTypeId};
49use crate::dom::bindings::refcounted::Trusted;
50use crate::dom::bindings::reflector::{DomGlobal, DomObject};
51use crate::dom::bindings::root::{Dom, DomOnceCell, DomRoot, MutNullableDom};
52use crate::dom::bindings::str::DOMString;
53use crate::dom::bindings::trace::{HashMapTracedValues, NoTrace};
54use crate::dom::blob::Blob;
55use crate::dom::customelementregistry::CallbackReaction;
56use crate::dom::document::Document;
57use crate::dom::domtokenlist::DOMTokenList;
58use crate::dom::element::{AttributeMutation, AttributeMutationReason, Element};
59use crate::dom::event::{Event, EventBubbles, EventCancelable};
60use crate::dom::eventtarget::EventTarget;
61use crate::dom::file::File;
62use crate::dom::formdata::FormData;
63use crate::dom::formdataevent::FormDataEvent;
64use crate::dom::html::htmlbuttonelement::HTMLButtonElement;
65use crate::dom::html::htmlcollection::CollectionFilter;
66use crate::dom::html::htmldatalistelement::HTMLDataListElement;
67use crate::dom::html::htmlelement::HTMLElement;
68use crate::dom::html::htmlfieldsetelement::HTMLFieldSetElement;
69use crate::dom::html::htmlformcontrolscollection::HTMLFormControlsCollection;
70use crate::dom::html::htmlimageelement::HTMLImageElement;
71use crate::dom::html::htmllabelelement::HTMLLabelElement;
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::input_element::HTMLInputElement;
78use crate::dom::input_element::input_type::InputType;
79use crate::dom::node::{
80 BindContext, Node, NodeFlags, NodeTraits, UnbindContext, VecPreOrderInsertionHelper,
81};
82use crate::dom::nodelist::{NodeList, RadioListMode};
83use crate::dom::radionodelist::RadioNodeList;
84use crate::dom::submitevent::SubmitEvent;
85use crate::dom::types::HTMLIFrameElement;
86use crate::dom::virtualmethods::VirtualMethods;
87use crate::dom::window::Window;
88use crate::links::{LinkRelations, get_element_target, valid_navigable_target_name_or_keyword};
89use crate::navigation::navigate;
90use crate::script_runtime::CanGc;
91use crate::script_thread::ScriptThread;
92
93#[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]
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 js::context::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(
192 &self,
193 index: u32,
194 mode: RadioListMode,
195 name: &Atom,
196 ) -> Option<DomRoot<Node>> {
197 self.controls
198 .borrow()
199 .iter()
200 .filter(|n| HTMLFormElement::filter_for_radio_list(mode, n, name))
201 .nth(index as usize)
202 .map(|n| DomRoot::from_ref(n.upcast::<Node>()))
203 }
204
205 pub(crate) fn count_for_radio_list(&self, mode: RadioListMode, name: &Atom) -> u32 {
206 self.controls
207 .borrow()
208 .iter()
209 .filter(|n| HTMLFormElement::filter_for_radio_list(mode, n, name))
210 .count() as u32
211 }
212}
213
214impl HTMLFormElementMethods<crate::DomTypeHolder> for HTMLFormElement {
215 make_getter!(AcceptCharset, "accept-charset");
217
218 make_setter!(SetAcceptCharset, "accept-charset");
220
221 make_form_action_getter!(Action, "action");
223
224 make_setter!(SetAction, "action");
226
227 make_enumerated_getter!(
229 Autocomplete,
230 "autocomplete",
231 "on" | "off",
232 missing => "on",
233 invalid => "on"
234 );
235
236 make_setter!(SetAutocomplete, "autocomplete");
238
239 make_enumerated_getter!(
241 Enctype,
242 "enctype",
243 "application/x-www-form-urlencoded" | "text/plain" | "multipart/form-data",
244 missing => "application/x-www-form-urlencoded",
245 invalid => "application/x-www-form-urlencoded"
246 );
247
248 make_setter!(SetEnctype, "enctype");
250
251 fn Encoding(&self) -> DOMString {
253 self.Enctype()
254 }
255
256 fn SetEncoding(&self, value: DOMString) {
258 self.SetEnctype(value)
259 }
260
261 make_enumerated_getter!(
263 Method,
264 "method",
265 "get" | "post" | "dialog",
266 missing => "get",
267 invalid => "get"
268 );
269
270 make_setter!(SetMethod, "method");
272
273 make_getter!(Name, "name");
275
276 make_atomic_setter!(SetName, "name");
278
279 make_bool_getter!(NoValidate, "novalidate");
281
282 make_bool_setter!(SetNoValidate, "novalidate");
284
285 make_getter!(Target, "target");
287
288 make_setter!(SetTarget, "target");
290
291 make_getter!(Rel, "rel");
293
294 fn Submit(&self, can_gc: CanGc) {
296 self.submit(
297 SubmittedFrom::FromForm,
298 FormSubmitterElement::Form(self),
299 can_gc,
300 );
301 }
302
303 fn RequestSubmit(&self, submitter: Option<&HTMLElement>, can_gc: CanGc) -> Fallible<()> {
305 let submitter: FormSubmitterElement = match submitter {
306 Some(submitter_element) => {
307 let error_not_a_submit_button =
309 Err(Error::Type(c"submitter must be a submit button".to_owned()));
310
311 let element = match submitter_element.upcast::<Node>().type_id() {
312 NodeTypeId::Element(ElementTypeId::HTMLElement(element)) => element,
313 _ => {
314 return error_not_a_submit_button;
315 },
316 };
317
318 let submit_button = match element {
319 HTMLElementTypeId::HTMLInputElement => FormSubmitterElement::Input(
320 submitter_element
321 .downcast::<HTMLInputElement>()
322 .expect("Failed to downcast submitter elem to HTMLInputElement."),
323 ),
324 HTMLElementTypeId::HTMLButtonElement => FormSubmitterElement::Button(
325 submitter_element
326 .downcast::<HTMLButtonElement>()
327 .expect("Failed to downcast submitter elem to HTMLButtonElement."),
328 ),
329 _ => {
330 return error_not_a_submit_button;
331 },
332 };
333
334 if !submit_button.is_submit_button() {
335 return error_not_a_submit_button;
336 }
337
338 let submitters_owner = submit_button.form_owner();
339
340 let owner = match submitters_owner {
342 Some(owner) => owner,
343 None => {
344 return Err(Error::NotFound(None));
345 },
346 };
347
348 if *owner != *self {
349 return Err(Error::NotFound(None));
350 }
351
352 submit_button
353 },
354 None => {
355 FormSubmitterElement::Form(self)
357 },
358 };
359 self.submit(SubmittedFrom::NotFromForm, submitter, can_gc);
361 Ok(())
362 }
363
364 fn Reset(&self, can_gc: CanGc) {
366 self.reset(ResetFrom::FromForm, can_gc);
367 }
368
369 fn Elements(&self, can_gc: CanGc) -> DomRoot<HTMLFormControlsCollection> {
371 #[derive(JSTraceable, MallocSizeOf)]
372 struct ElementsFilter {
373 form: DomRoot<HTMLFormElement>,
374 }
375 impl CollectionFilter for ElementsFilter {
376 fn filter<'a>(&self, elem: &'a Element, _root: &'a Node) -> bool {
377 let form_owner = match elem.upcast::<Node>().type_id() {
378 NodeTypeId::Element(ElementTypeId::HTMLElement(t)) => match t {
379 HTMLElementTypeId::HTMLButtonElement => {
380 elem.downcast::<HTMLButtonElement>().unwrap().form_owner()
381 },
382 HTMLElementTypeId::HTMLFieldSetElement => {
383 elem.downcast::<HTMLFieldSetElement>().unwrap().form_owner()
384 },
385 HTMLElementTypeId::HTMLInputElement => {
386 let input_elem = elem.downcast::<HTMLInputElement>().unwrap();
387 if matches!(*input_elem.input_type(), InputType::Image(_)) {
388 return false;
389 }
390 input_elem.form_owner()
391 },
392 HTMLElementTypeId::HTMLObjectElement => {
393 elem.downcast::<HTMLObjectElement>().unwrap().form_owner()
394 },
395 HTMLElementTypeId::HTMLOutputElement => {
396 elem.downcast::<HTMLOutputElement>().unwrap().form_owner()
397 },
398 HTMLElementTypeId::HTMLSelectElement => {
399 elem.downcast::<HTMLSelectElement>().unwrap().form_owner()
400 },
401 HTMLElementTypeId::HTMLTextAreaElement => {
402 elem.downcast::<HTMLTextAreaElement>().unwrap().form_owner()
403 },
404 HTMLElementTypeId::HTMLElement => {
405 let html_element = elem.downcast::<HTMLElement>().unwrap();
406 if html_element.is_form_associated_custom_element() {
407 html_element.form_owner()
408 } else {
409 return false;
410 }
411 },
412 _ => {
413 debug_assert!(
414 !elem.downcast::<HTMLElement>().unwrap().is_listed_element()
415 );
416 return false;
417 },
418 },
419 _ => return false,
420 };
421
422 match form_owner {
423 Some(form_owner) => form_owner == self.form,
424 None => false,
425 }
426 }
427 }
428 DomRoot::from_ref(self.elements.init_once(|| {
429 let filter = Box::new(ElementsFilter {
430 form: DomRoot::from_ref(self),
431 });
432 let window = self.owner_window();
433 HTMLFormControlsCollection::new(&window, self, filter, can_gc)
434 }))
435 }
436
437 fn Length(&self) -> u32 {
439 self.Elements(CanGc::note()).Length()
440 }
441
442 fn IndexedGetter(&self, index: u32, can_gc: CanGc) -> Option<DomRoot<Element>> {
444 let elements = self.Elements(can_gc);
445 elements.IndexedGetter(index)
446 }
447
448 fn NamedGetter(&self, name: DOMString, can_gc: CanGc) -> Option<RadioNodeListOrElement> {
450 let window = self.owner_window();
451
452 let name = Atom::from(name);
453
454 let mut candidates =
456 RadioNodeList::new_controls_except_image_inputs(&window, self, &name, can_gc);
457 let mut candidates_length = candidates.Length();
458
459 if candidates_length == 0 {
461 candidates = RadioNodeList::new_images(&window, self, &name, can_gc);
462 candidates_length = candidates.Length();
463 }
464
465 let mut past_names_map = self.past_names_map.borrow_mut();
466
467 if candidates_length == 0 {
469 if past_names_map.contains_key(&name) {
470 return Some(RadioNodeListOrElement::Element(DomRoot::from_ref(
471 &*past_names_map.get(&name).unwrap().0,
472 )));
473 }
474 return None;
475 }
476
477 if candidates_length > 1 {
479 return Some(RadioNodeListOrElement::RadioNodeList(candidates));
480 }
481
482 let element_node = candidates.upcast::<NodeList>().Item(0).unwrap();
485 past_names_map.insert(
486 name,
487 (
488 Dom::from_ref(element_node.downcast::<Element>().unwrap()),
489 NoTrace(self.current_name_generation.get() + 1),
490 ),
491 );
492 self.current_name_generation
493 .set(self.current_name_generation.get() + 1);
494
495 Some(RadioNodeListOrElement::Element(DomRoot::from_ref(
497 element_node.downcast::<Element>().unwrap(),
498 )))
499 }
500
501 fn SetRel(&self, rel: DOMString, can_gc: CanGc) {
503 self.upcast::<Element>()
504 .set_tokenlist_attribute(&local_name!("rel"), rel, can_gc);
505 }
506
507 fn RelList(&self, can_gc: CanGc) -> DomRoot<DOMTokenList> {
509 self.rel_list.or_init(|| {
510 DOMTokenList::new(
511 self.upcast(),
512 &local_name!("rel"),
513 Some(vec![
514 Atom::from("noopener"),
515 Atom::from("noreferrer"),
516 Atom::from("opener"),
517 ]),
518 can_gc,
519 )
520 })
521 }
522
523 fn SupportedPropertyNames(&self) -> Vec<DOMString> {
525 #[derive(Debug, Eq, Ord, PartialEq, PartialOrd)]
527 enum SourcedNameSource {
528 Id,
529 Name,
530 Past(usize),
531 }
532
533 impl SourcedNameSource {
534 fn is_past(&self) -> bool {
535 matches!(self, SourcedNameSource::Past(..))
536 }
537 }
538
539 struct SourcedName {
540 name: Atom,
541 element: DomRoot<Element>,
542 source: SourcedNameSource,
543 }
544
545 let mut sourced_names_vec: Vec<SourcedName> = Vec::new();
546
547 for child in self.controls.borrow().iter() {
549 if child
550 .downcast::<HTMLElement>()
551 .is_some_and(|c| c.is_listed_element())
552 {
553 if let Some(id_atom) = child.get_id() {
554 let entry = SourcedName {
555 name: id_atom,
556 element: DomRoot::from_ref(child),
557 source: SourcedNameSource::Id,
558 };
559 sourced_names_vec.push(entry);
560 }
561 if let Some(name_atom) = child.get_name() {
562 let entry = SourcedName {
563 name: name_atom,
564 element: DomRoot::from_ref(child),
565 source: SourcedNameSource::Name,
566 };
567 sourced_names_vec.push(entry);
568 }
569 }
570 }
571
572 for child in self.controls.borrow().iter() {
574 if child.is::<HTMLImageElement>() {
575 if let Some(id_atom) = child.get_id() {
576 let entry = SourcedName {
577 name: id_atom,
578 element: DomRoot::from_ref(child),
579 source: SourcedNameSource::Id,
580 };
581 sourced_names_vec.push(entry);
582 }
583 if let Some(name_atom) = child.get_name() {
584 let entry = SourcedName {
585 name: name_atom,
586 element: DomRoot::from_ref(child),
587 source: SourcedNameSource::Name,
588 };
589 sourced_names_vec.push(entry);
590 }
591 }
592 }
593
594 let past_names_map = self.past_names_map.borrow();
596 for (key, val) in past_names_map.iter() {
597 let entry = SourcedName {
598 name: key.clone(),
599 element: DomRoot::from_ref(&*val.0),
600 source: SourcedNameSource::Past(self.current_name_generation.get() - val.1.0),
601 };
602 sourced_names_vec.push(entry);
603 }
604
605 sourced_names_vec.sort_by(|a, b| {
617 if a.element
618 .upcast::<Node>()
619 .CompareDocumentPosition(b.element.upcast::<Node>()) ==
620 0
621 {
622 if a.source.is_past() && b.source.is_past() {
623 b.source.cmp(&a.source)
624 } else {
625 a.source.cmp(&b.source)
626 }
627 } else if a
628 .element
629 .upcast::<Node>()
630 .CompareDocumentPosition(b.element.upcast::<Node>()) &
631 NodeConstants::DOCUMENT_POSITION_FOLLOWING ==
632 NodeConstants::DOCUMENT_POSITION_FOLLOWING
633 {
634 std::cmp::Ordering::Less
635 } else {
636 std::cmp::Ordering::Greater
637 }
638 });
639
640 sourced_names_vec.retain(|sn| !sn.name.to_string().is_empty());
642
643 let mut names_vec: Vec<DOMString> = Vec::new();
645 for elem in sourced_names_vec.iter() {
646 if !names_vec.iter().any(|name| *name == *elem.name) {
647 names_vec.push(DOMString::from(&*elem.name));
648 }
649 }
650
651 names_vec
652 }
653
654 fn CheckValidity(&self, cx: &mut JSContext) -> bool {
656 self.static_validation(CanGc::from_cx(cx)).is_ok()
657 }
658
659 fn ReportValidity(&self, cx: &mut JSContext) -> bool {
661 self.interactive_validation(CanGc::from_cx(cx)).is_ok()
662 }
663}
664
665#[derive(Clone, Copy, MallocSizeOf, PartialEq)]
666pub(crate) enum SubmittedFrom {
667 FromForm,
668 NotFromForm,
669}
670
671#[derive(Clone, Copy, MallocSizeOf)]
672pub(crate) enum ResetFrom {
673 FromForm,
674 NotFromForm,
675}
676
677impl HTMLFormElement {
678 fn pick_encoding(&self) -> &'static Encoding {
680 if self
682 .upcast::<Element>()
683 .has_attribute(&local_name!("accept-charset"))
684 {
685 let input = self
687 .upcast::<Element>()
688 .get_string_attribute(&local_name!("accept-charset"));
689
690 let input = input.str();
692 let mut candidate_encodings =
693 split_html_space_chars(&input).filter_map(|c| Encoding::for_label(c.as_bytes()));
694
695 return candidate_encodings.next().unwrap_or(UTF_8);
697 }
698
699 self.owner_document().encoding()
701 }
702
703 fn encode_plaintext(&self, form_data: &mut [FormDatum]) -> String {
705 let mut result = String::new();
707
708 for entry in form_data.iter() {
710 let value = match &entry.value {
711 FormDatumValue::File(f) => f.name(),
712 FormDatumValue::String(s) => s,
713 };
714 result.push_str(&format!("{}={}\r\n", entry.name, value));
715 }
716
717 result
719 }
720
721 pub(crate) fn update_validity(&self, can_gc: CanGc) {
722 let is_any_invalid = self
723 .controls
724 .borrow()
725 .iter()
726 .any(|control| control.is_invalid(false, can_gc));
727
728 self.upcast::<Element>()
729 .set_state(ElementState::VALID, !is_any_invalid);
730 self.upcast::<Element>()
731 .set_state(ElementState::INVALID, is_any_invalid);
732 }
733
734 pub(crate) fn submit(
736 &self,
737 submit_method_flag: SubmittedFrom,
738 submitter: FormSubmitterElement,
739 can_gc: CanGc,
740 ) {
741 if self.upcast::<Element>().cannot_navigate() {
743 return;
744 }
745
746 if self.constructing_entry_list.get() {
748 return;
749 }
750 let doc = self.owner_document();
752
753 if doc.has_active_sandboxing_flag(SandboxingFlagSet::SANDBOXED_FORMS_BROWSING_CONTEXT_FLAG)
756 {
757 return;
758 }
759
760 let base = doc.base_url();
761 if submit_method_flag == SubmittedFrom::NotFromForm {
764 if self.firing_submission_events.get() {
766 return;
767 }
768 self.firing_submission_events.set(true);
770 if !submitter.no_validate(self) && self.interactive_validation(can_gc).is_err() {
772 self.firing_submission_events.set(false);
773 return;
774 }
775 let submitter_button = match submitter {
779 FormSubmitterElement::Form(f) => {
780 if f == self {
781 None
782 } else {
783 Some(f.upcast::<HTMLElement>())
784 }
785 },
786 FormSubmitterElement::Input(i) => Some(i.upcast::<HTMLElement>()),
787 FormSubmitterElement::Button(b) => Some(b.upcast::<HTMLElement>()),
788 };
789
790 let event = SubmitEvent::new(
792 self.global().as_window(),
793 atom!("submit"),
794 true,
795 true,
796 submitter_button.map(DomRoot::from_ref),
797 can_gc,
798 );
799 let event = event.upcast::<Event>();
800 event.fire(self.upcast::<EventTarget>(), can_gc);
801
802 self.firing_submission_events.set(false);
804 if event.DefaultPrevented() {
806 return;
807 }
808 if self.upcast::<Element>().cannot_navigate() {
810 return;
811 }
812 }
813
814 let encoding = self.pick_encoding();
816
817 let mut form_data = match self.get_form_dataset(Some(submitter), Some(encoding), can_gc) {
819 Some(form_data) => form_data,
820 None => return,
821 };
822
823 if self.upcast::<Element>().cannot_navigate() {
825 return;
826 }
827
828 let method = submitter.method();
830 let mut action = submitter.action();
835
836 if action.is_empty() {
838 action = DOMString::from(base.as_str());
839 }
840 let action_components = match doc.encoding_parse_a_url(&action.str()) {
842 Ok(url) => url,
843 Err(_) => return,
845 };
846 let scheme = action_components.scheme().to_owned();
848 let enctype = submitter.enctype();
850
851 let form_target_attribute = submitter.target();
854 let form_target = if submitter.is_submit_button() &&
855 valid_navigable_target_name_or_keyword(&form_target_attribute)
856 {
857 Some(form_target_attribute)
858 } else {
859 None
861 };
862 let form_owner = submitter.form_owner();
864 let form = form_owner.as_deref().unwrap_or(self);
865 let target = get_element_target(form.upcast::<Element>(), form_target);
866
867 let noopener = self.relations.get().get_element_noopener(target.as_ref());
869
870 let source = doc.browsing_context().unwrap();
873 let (maybe_chosen, _new) =
874 source.choose_browsing_context(target.unwrap_or_default(), noopener);
875
876 let Some(chosen) = maybe_chosen else {
877 return;
879 };
880 let target_document = match chosen.document() {
887 Some(doc) => doc,
888 None => return,
889 };
890 let target_window = target_document.window();
891 let mut load_data = LoadData::new(
892 LoadOrigin::Script(doc.origin().snapshot()),
893 action_components,
894 target_document.about_base_url(),
895 None,
896 target_window.as_global_scope().get_referrer(),
897 target_document.get_referrer_policy(),
898 Some(target_window.as_global_scope().is_secure_context()),
899 Some(target_document.insecure_requests_policy()),
900 target_document.has_trustworthy_ancestor_origin(),
901 target_document.creation_sandboxing_flag_set_considering_parent_iframe(),
902 );
903
904 match (&*scheme, method) {
908 (_, FormMethod::Dialog) => {
909 },
912 ("http", FormMethod::Get) | ("https", FormMethod::Get) | ("data", FormMethod::Get) => {
914 load_data
915 .headers
916 .typed_insert(ContentType::from(mime::APPLICATION_WWW_FORM_URLENCODED));
917 self.mutate_action_url(&mut form_data, load_data, encoding, target_window);
918 },
919 ("http", FormMethod::Post) | ("https", FormMethod::Post) => {
921 load_data.method = Method::POST;
922 self.submit_entity_body(
923 &mut form_data,
924 load_data,
925 enctype,
926 encoding,
927 target_window,
928 can_gc,
929 );
930 },
931 ("file", _) |
933 ("about", _) |
934 ("data", FormMethod::Post) |
935 ("ftp", _) |
936 ("javascript", _) => {
937 self.plan_to_navigate(load_data, target_window);
938 },
939 ("mailto", FormMethod::Post) => {
940 },
943 ("mailto", FormMethod::Get) => {
944 },
947 _ => (),
948 }
949 }
950
951 fn mutate_action_url(
953 &self,
954 form_data: &mut [FormDatum],
955 mut load_data: LoadData,
956 encoding: &'static Encoding,
957 target: &Window,
958 ) {
959 let charset = encoding.name();
960
961 self.set_url_query_pairs(
962 &mut load_data.url,
963 form_data
964 .iter()
965 .map(|field| (field.name.str(), field.replace_value(charset))),
966 );
967
968 self.plan_to_navigate(load_data, target);
969 }
970
971 fn submit_entity_body(
973 &self,
974 form_data: &mut [FormDatum],
975 mut load_data: LoadData,
976 enctype: FormEncType,
977 encoding: &'static Encoding,
978 target: &Window,
979 can_gc: CanGc,
980 ) {
981 let boundary = generate_boundary();
982 let bytes = match enctype {
983 FormEncType::UrlEncoded => {
984 let charset = encoding.name();
985 load_data
986 .headers
987 .typed_insert(ContentType::from(mime::APPLICATION_WWW_FORM_URLENCODED));
988
989 let mut url = load_data.url.clone();
990 self.set_url_query_pairs(
991 &mut url,
992 form_data
993 .iter()
994 .map(|field| (field.name.str(), field.replace_value(charset))),
995 );
996
997 url.query().unwrap_or("").to_string().into_bytes()
998 },
999 FormEncType::MultipartFormData => {
1000 let mime: Mime = format!("multipart/form-data; boundary={}", boundary)
1001 .parse()
1002 .unwrap();
1003 load_data.headers.typed_insert(ContentType::from(mime));
1004 encode_multipart_form_data(form_data, boundary, encoding)
1005 },
1006 FormEncType::TextPlain => {
1007 load_data
1008 .headers
1009 .typed_insert(ContentType::from(mime::TEXT_PLAIN));
1010 self.encode_plaintext(form_data).into_bytes()
1011 },
1012 };
1013
1014 let global = self.global();
1015
1016 let request_body = bytes
1017 .extract(&global, false, can_gc)
1018 .expect("Couldn't extract body.")
1019 .into_net_request_body()
1020 .0;
1021 load_data.data = Some(request_body);
1022
1023 self.plan_to_navigate(load_data, target);
1024 }
1025
1026 fn set_url_query_pairs<T>(
1027 &self,
1028 url: &mut servo_url::ServoUrl,
1029 pairs: impl Iterator<Item = (T, String)>,
1030 ) where
1031 T: AsRef<str>,
1032 {
1033 let encoding = self.pick_encoding();
1034 url.as_mut_url()
1035 .query_pairs_mut()
1036 .encoding_override(Some(&|s| encoding.encode(s).0))
1037 .clear()
1038 .extend_pairs(pairs);
1039 }
1040
1041 fn plan_to_navigate(&self, mut load_data: LoadData, target: &Window) {
1043 let elem = self.upcast::<Element>();
1048 let referrer = match elem.get_attribute(&local_name!("rel")) {
1049 Some(ref link_types) if link_types.Value().contains("noreferrer") => {
1050 Referrer::NoReferrer
1051 },
1052 _ => target.as_global_scope().get_referrer(),
1053 };
1054
1055 self.planned_navigation
1058 .set(self.planned_navigation.get().wrapping_add(1));
1059 let planned_navigation = self.planned_navigation.get();
1060
1061 let ongoing_navigation = target.set_ongoing_navigation();
1077
1078 let referrer_policy = target.Document().get_referrer_policy();
1079 load_data.creator_pipeline_id = Some(target.pipeline_id());
1080 load_data.referrer = referrer;
1081 load_data.referrer_policy = referrer_policy;
1082
1083 if let Some(window_proxy) = target.undiscarded_window_proxy() {
1086 if let Some(frame) = window_proxy
1087 .frame_element()
1088 .and_then(|e| e.downcast::<HTMLIFrameElement>())
1089 {
1090 frame.note_pending_navigation()
1091 }
1092 }
1093
1094 let form = Trusted::new(self);
1097 let window = Trusted::new(target);
1098 let task = task!(navigate_to_form_planned_navigation: move |cx| {
1099 if planned_navigation != form.root().planned_navigation.get() {
1103 return;
1104 }
1105
1106 if ongoing_navigation != window.root().ongoing_navigation() {
1109 return;
1110 }
1111
1112 navigate(
1114 cx,
1115 &window.root(),
1116 NavigationHistoryBehavior::Push,
1117 false,
1118 load_data,
1119 );
1120 });
1121
1122 target
1127 .global()
1128 .task_manager()
1129 .dom_manipulation_task_source()
1130 .queue(task)
1131 }
1132
1133 fn interactive_validation(&self, can_gc: CanGc) -> Result<(), ()> {
1136 let unhandled_invalid_controls = match self.static_validation(can_gc) {
1141 Ok(()) => return Ok(()),
1142 Err(err) => err,
1143 };
1144
1145 let mut first = true;
1148
1149 for elem in unhandled_invalid_controls {
1150 if let Some(validatable) = elem.as_maybe_validatable() {
1151 error!("Validation error: {}", validatable.validation_message());
1152 }
1153 if first {
1154 if let Some(html_elem) = elem.downcast::<HTMLElement>() {
1155 html_elem.Focus(&FocusOptions::default(), can_gc);
1162 first = false;
1163 }
1164 }
1165 }
1166
1167 Err(())
1171 }
1172
1173 fn static_validation(&self, can_gc: CanGc) -> Result<(), Vec<DomRoot<Element>>> {
1176 let invalid_controls = self
1178 .controls
1179 .borrow()
1180 .iter()
1181 .filter_map(|field| {
1182 if let Some(element) = field.downcast::<Element>() {
1183 if element.is_invalid(true, can_gc) {
1184 Some(DomRoot::from_ref(element))
1185 } else {
1186 None
1187 }
1188 } else {
1189 None
1190 }
1191 })
1192 .collect::<Vec<DomRoot<Element>>>();
1193 if invalid_controls.is_empty() {
1195 return Ok(());
1196 }
1197 let unhandled_invalid_controls = invalid_controls
1199 .into_iter()
1200 .filter_map(|field| {
1201 let not_canceled = field
1204 .upcast::<EventTarget>()
1205 .fire_cancelable_event(atom!("invalid"), can_gc);
1206 if not_canceled {
1208 return Some(field);
1209 }
1210 None
1211 })
1212 .collect::<Vec<DomRoot<Element>>>();
1213 Err(unhandled_invalid_controls)
1215 }
1216
1217 fn get_unclean_dataset(
1222 &self,
1223 submitter: Option<FormSubmitterElement>,
1224 encoding: Option<&'static Encoding>,
1225 can_gc: CanGc,
1226 ) -> Vec<FormDatum> {
1227 let mut data_set = Vec::new();
1228 for child in self.controls.borrow().iter() {
1229 if child.disabled_state() {
1231 continue;
1232 }
1233 let child = child.upcast::<Node>();
1234
1235 if child.ancestors().any(|a| a.is::<HTMLDataListElement>()) {
1237 continue;
1238 }
1239 if let NodeTypeId::Element(ElementTypeId::HTMLElement(element)) = child.type_id() {
1240 match element {
1241 HTMLElementTypeId::HTMLInputElement => {
1242 let input = child.downcast::<HTMLInputElement>().unwrap();
1243 data_set.append(&mut input.form_datums(submitter, encoding));
1244 },
1245 HTMLElementTypeId::HTMLButtonElement => {
1246 let button = child.downcast::<HTMLButtonElement>().unwrap();
1247 if let Some(datum) = button.form_datum(submitter) {
1248 data_set.push(datum);
1249 }
1250 },
1251 HTMLElementTypeId::HTMLObjectElement => {
1252 },
1254 HTMLElementTypeId::HTMLSelectElement => {
1255 let select = child.downcast::<HTMLSelectElement>().unwrap();
1256 select.push_form_data(&mut data_set);
1257 },
1258 HTMLElementTypeId::HTMLTextAreaElement => {
1259 let textarea = child.downcast::<HTMLTextAreaElement>().unwrap();
1260 let name = textarea.Name();
1261 if !name.is_empty() {
1262 data_set.push(FormDatum {
1263 ty: textarea.Type(),
1264 name,
1265 value: FormDatumValue::String(textarea.Value()),
1266 });
1267 }
1268 },
1269 HTMLElementTypeId::HTMLElement => {
1270 let custom = child.downcast::<HTMLElement>().unwrap();
1271 if custom.is_form_associated_custom_element() {
1272 let internals =
1274 custom.upcast::<Element>().ensure_element_internals(can_gc);
1275 internals.perform_entry_construction(&mut data_set);
1276 }
1278 },
1279 _ => (),
1280 }
1281 }
1282
1283 let child_element = child.downcast::<Element>().unwrap();
1287 let input_matches = child_element
1288 .downcast::<HTMLInputElement>()
1289 .is_some_and(|input| {
1290 matches!(
1291 *input.input_type(),
1292 InputType::Text(_) | InputType::Search(_)
1293 )
1294 });
1295 let textarea_matches = child_element.is::<HTMLTextAreaElement>();
1296 let dirname = child_element.get_string_attribute(&local_name!("dirname"));
1297 if (input_matches || textarea_matches) && !dirname.is_empty() {
1298 let dir = DOMString::from(child_element.directionality());
1299 data_set.push(FormDatum {
1300 ty: DOMString::from("string"),
1301 name: dirname,
1302 value: FormDatumValue::String(dir),
1303 });
1304 }
1305 }
1306 data_set
1307 }
1308
1309 pub(crate) fn get_form_dataset(
1311 &self,
1312 submitter: Option<FormSubmitterElement>,
1313 encoding: Option<&'static Encoding>,
1314 can_gc: CanGc,
1315 ) -> Option<Vec<FormDatum>> {
1316 if self.constructing_entry_list.get() {
1318 return None;
1319 }
1320
1321 self.constructing_entry_list.set(true);
1323
1324 let ret = self.get_unclean_dataset(submitter, encoding, can_gc);
1326
1327 let window = self.owner_window();
1328
1329 let form_data = FormData::new(Some(ret), &window.global(), can_gc);
1331
1332 let event = FormDataEvent::new(
1334 &window,
1335 atom!("formdata"),
1336 EventBubbles::Bubbles,
1337 EventCancelable::NotCancelable,
1338 &form_data,
1339 can_gc,
1340 );
1341
1342 event
1343 .upcast::<Event>()
1344 .fire(self.upcast::<EventTarget>(), can_gc);
1345
1346 self.constructing_entry_list.set(false);
1348
1349 Some(form_data.datums())
1351 }
1352
1353 pub(crate) fn reset(&self, _reset_method_flag: ResetFrom, can_gc: CanGc) {
1355 if self.marked_for_reset.get() {
1357 return;
1358 } else {
1359 self.marked_for_reset.set(true);
1360 }
1361
1362 let reset = self
1366 .upcast::<EventTarget>()
1367 .fire_bubbling_cancelable_event(atom!("reset"), can_gc);
1368 if !reset {
1369 return;
1370 }
1371
1372 let controls: Vec<_> = self
1373 .controls
1374 .borrow()
1375 .iter()
1376 .map(|c| c.as_rooted())
1377 .collect();
1378
1379 for child in controls {
1380 child.reset(can_gc);
1381 }
1382 self.marked_for_reset.set(false);
1383 }
1384
1385 fn add_control<T: ?Sized + FormControl>(&self, control: &T, can_gc: CanGc) {
1386 {
1387 let root = self.upcast::<Element>().root_element();
1388 let root = root.upcast::<Node>();
1389 let mut controls = self.controls.borrow_mut();
1390 controls.insert_pre_order(control.to_element(), root);
1391 }
1392 self.update_validity(can_gc);
1393 }
1394
1395 fn remove_control<T: ?Sized + FormControl>(&self, control: &T, can_gc: CanGc) {
1396 {
1397 let control = control.to_element();
1398 let mut controls = self.controls.borrow_mut();
1399 controls
1400 .iter()
1401 .position(|c| &**c == control)
1402 .map(|idx| controls.remove(idx));
1403
1404 let mut past_names_map = self.past_names_map.borrow_mut();
1409 past_names_map.0.retain(|_k, v| v.0 != control);
1410 }
1411 self.update_validity(can_gc);
1412 }
1413}
1414
1415impl Element {
1416 pub(crate) fn is_resettable(&self) -> bool {
1417 let NodeTypeId::Element(ElementTypeId::HTMLElement(element_type)) =
1418 self.upcast::<Node>().type_id()
1419 else {
1420 return false;
1421 };
1422 matches!(
1423 element_type,
1424 HTMLElementTypeId::HTMLInputElement |
1425 HTMLElementTypeId::HTMLSelectElement |
1426 HTMLElementTypeId::HTMLTextAreaElement |
1427 HTMLElementTypeId::HTMLOutputElement |
1428 HTMLElementTypeId::HTMLElement
1429 )
1430 }
1431
1432 #[expect(unsafe_code)]
1433 pub(crate) fn reset(&self, _can_gc: CanGc) {
1434 let mut cx = unsafe { script_bindings::script_runtime::temp_cx() };
1436 let cx = &mut cx;
1437
1438 if !self.is_resettable() {
1439 return;
1440 }
1441
1442 if let Some(input_element) = self.downcast::<HTMLInputElement>() {
1443 input_element.reset(CanGc::from_cx(cx));
1444 } else if let Some(select_element) = self.downcast::<HTMLSelectElement>() {
1445 select_element.reset();
1446 } else if let Some(textarea_element) = self.downcast::<HTMLTextAreaElement>() {
1447 textarea_element.reset(CanGc::from_cx(cx));
1448 } else if let Some(output_element) = self.downcast::<HTMLOutputElement>() {
1449 output_element.reset(cx);
1450 } else if let Some(html_element) = self.downcast::<HTMLElement>() {
1451 if html_element.is_form_associated_custom_element() {
1452 ScriptThread::enqueue_callback_reaction(
1453 html_element.upcast::<Element>(),
1454 CallbackReaction::FormReset,
1455 None,
1456 )
1457 }
1458 }
1459 }
1460}
1461
1462#[derive(Clone, JSTraceable, MallocSizeOf)]
1463pub(crate) enum FormDatumValue {
1464 File(DomRoot<File>),
1465 String(DOMString),
1466}
1467
1468#[derive(Clone, JSTraceable, MallocSizeOf)]
1469pub(crate) struct FormDatum {
1470 pub(crate) ty: DOMString,
1471 pub(crate) name: DOMString,
1472 pub(crate) value: FormDatumValue,
1473}
1474
1475impl FormDatum {
1476 pub(crate) fn replace_value(&self, charset: &str) -> String {
1477 if self.name.to_ascii_lowercase() == "_charset_" && self.ty == "hidden" {
1478 return charset.to_string();
1479 }
1480
1481 match self.value {
1482 FormDatumValue::File(ref f) => String::from(f.name().clone()),
1483 FormDatumValue::String(ref s) => String::from(s.clone()),
1484 }
1485 }
1486}
1487
1488#[derive(Clone, Copy, MallocSizeOf)]
1489pub(crate) enum FormEncType {
1490 TextPlain,
1491 UrlEncoded,
1492 MultipartFormData,
1493}
1494
1495#[derive(Clone, Copy, MallocSizeOf)]
1496pub(crate) enum FormMethod {
1497 Get,
1498 Post,
1499 Dialog,
1500}
1501
1502#[derive(Clone, Copy, MallocSizeOf)]
1504pub(crate) enum FormSubmitterElement<'a> {
1505 Form(&'a HTMLFormElement),
1506 Input(&'a HTMLInputElement),
1507 Button(&'a HTMLButtonElement),
1508 }
1511
1512impl FormSubmitterElement<'_> {
1513 fn action(&self) -> DOMString {
1514 match *self {
1515 FormSubmitterElement::Form(form) => form.Action(),
1516 FormSubmitterElement::Input(input_element) => input_element.get_form_attribute(
1517 &local_name!("formaction"),
1518 |i| i.FormAction(),
1519 |f| f.Action(),
1520 ),
1521 FormSubmitterElement::Button(button_element) => button_element.get_form_attribute(
1522 &local_name!("formaction"),
1523 |i| i.FormAction(),
1524 |f| f.Action(),
1525 ),
1526 }
1527 }
1528
1529 fn enctype(&self) -> FormEncType {
1530 let attr = match *self {
1531 FormSubmitterElement::Form(form) => form.Enctype(),
1532 FormSubmitterElement::Input(input_element) => input_element.get_form_attribute(
1533 &local_name!("formenctype"),
1534 |i| i.FormEnctype(),
1535 |f| f.Enctype(),
1536 ),
1537 FormSubmitterElement::Button(button_element) => button_element.get_form_attribute(
1538 &local_name!("formenctype"),
1539 |i| i.FormEnctype(),
1540 |f| f.Enctype(),
1541 ),
1542 };
1543 match_domstring_ascii!(attr,
1546 "multipart/form-data" => FormEncType::MultipartFormData,
1547 "text/plain" => FormEncType::TextPlain,
1548 _ => FormEncType::UrlEncoded,
1549 )
1550 }
1551
1552 fn method(&self) -> FormMethod {
1553 let attr = match *self {
1554 FormSubmitterElement::Form(form) => form.Method(),
1555 FormSubmitterElement::Input(input_element) => input_element.get_form_attribute(
1556 &local_name!("formmethod"),
1557 |i| i.FormMethod(),
1558 |f| f.Method(),
1559 ),
1560 FormSubmitterElement::Button(button_element) => button_element.get_form_attribute(
1561 &local_name!("formmethod"),
1562 |i| i.FormMethod(),
1563 |f| f.Method(),
1564 ),
1565 };
1566 match_domstring_ascii!(attr,
1567 "dialog" => FormMethod::Dialog,
1568 "post" => FormMethod::Post,
1569 _ => FormMethod::Get,
1570 )
1571 }
1572
1573 fn target(&self) -> DOMString {
1574 match *self {
1575 FormSubmitterElement::Form(form) => form.Target(),
1576 FormSubmitterElement::Input(input_element) => input_element.get_form_attribute(
1577 &local_name!("formtarget"),
1578 |i| i.FormTarget(),
1579 |f| f.Target(),
1580 ),
1581 FormSubmitterElement::Button(button_element) => button_element.get_form_attribute(
1582 &local_name!("formtarget"),
1583 |i| i.FormTarget(),
1584 |f| f.Target(),
1585 ),
1586 }
1587 }
1588
1589 fn no_validate(&self, _form_owner: &HTMLFormElement) -> bool {
1590 match *self {
1591 FormSubmitterElement::Form(form) => form.NoValidate(),
1592 FormSubmitterElement::Input(input_element) => input_element.get_form_boolean_attribute(
1593 &local_name!("formnovalidate"),
1594 |i| i.FormNoValidate(),
1595 |f| f.NoValidate(),
1596 ),
1597 FormSubmitterElement::Button(button_element) => button_element
1598 .get_form_boolean_attribute(
1599 &local_name!("formnovalidate"),
1600 |i| i.FormNoValidate(),
1601 |f| f.NoValidate(),
1602 ),
1603 }
1604 }
1605
1606 pub(crate) fn is_submit_button(&self) -> bool {
1608 match *self {
1609 FormSubmitterElement::Input(input_element) => input_element.is_submit_button(),
1612 FormSubmitterElement::Button(button_element) => button_element.is_submit_button(),
1614 _ => false,
1615 }
1616 }
1617
1618 pub(crate) fn form_owner(&self) -> Option<DomRoot<HTMLFormElement>> {
1620 match *self {
1621 FormSubmitterElement::Button(button_el) => button_el.form_owner(),
1622 FormSubmitterElement::Input(input_el) => input_el.form_owner(),
1623 _ => None,
1624 }
1625 }
1626}
1627
1628pub(crate) trait FormControl: DomObject<ReflectorType = ()> {
1629 fn form_owner(&self) -> Option<DomRoot<HTMLFormElement>>;
1630
1631 fn set_form_owner(&self, form: Option<&HTMLFormElement>);
1632
1633 fn to_element(&self) -> ∈
1634
1635 fn is_listed(&self) -> bool {
1636 true
1637 }
1638
1639 fn set_form_owner_from_parser(&self, form: &HTMLFormElement, can_gc: CanGc) {
1644 let elem = self.to_element();
1645 let node = elem.upcast::<Node>();
1646 node.set_flag(NodeFlags::PARSER_ASSOCIATED_FORM_OWNER, true);
1647 form.add_control(self, can_gc);
1648 self.set_form_owner(Some(form));
1649 }
1650
1651 fn reset_form_owner(&self, can_gc: CanGc) {
1653 let elem = self.to_element();
1654 let node = elem.upcast::<Node>();
1655 let old_owner = self.form_owner();
1656 let has_form_id = elem.has_attribute(&local_name!("form"));
1657 let nearest_form_ancestor = node
1658 .ancestors()
1659 .find_map(DomRoot::downcast::<HTMLFormElement>);
1660
1661 if old_owner.is_some() &&
1663 !(self.is_listed() && has_form_id) &&
1664 nearest_form_ancestor == old_owner
1665 {
1666 return;
1667 }
1668
1669 let new_owner = if self.is_listed() && has_form_id && elem.is_connected() {
1670 let doc = node.owner_document();
1672 let form_id = elem.get_string_attribute(&local_name!("form"));
1673 doc.GetElementById(form_id)
1674 .and_then(DomRoot::downcast::<HTMLFormElement>)
1675 } else {
1676 nearest_form_ancestor
1678 };
1679
1680 if old_owner != new_owner {
1681 if let Some(o) = old_owner {
1682 o.remove_control(self, can_gc);
1683 }
1684 if let Some(ref new_owner) = new_owner {
1685 new_owner.add_control(self, can_gc);
1686 }
1687 if let Some(html_elem) = elem.downcast::<HTMLElement>() {
1689 if html_elem.is_form_associated_custom_element() {
1690 ScriptThread::enqueue_callback_reaction(
1691 elem,
1692 CallbackReaction::FormAssociated(
1693 new_owner.as_ref().map(|form| DomRoot::from_ref(&**form)),
1694 ),
1695 None,
1696 )
1697 }
1698 }
1699 self.set_form_owner(new_owner.as_deref());
1700 }
1701 }
1702
1703 fn form_attribute_mutated(&self, mutation: AttributeMutation, can_gc: CanGc) {
1705 match mutation {
1706 AttributeMutation::Set(..) => {
1707 self.register_if_necessary();
1708 },
1709 AttributeMutation::Removed => {
1710 self.unregister_if_necessary();
1711 },
1712 }
1713
1714 self.reset_form_owner(can_gc);
1715 }
1716
1717 fn register_if_necessary(&self) {
1719 let elem = self.to_element();
1720 let form_id = elem.get_string_attribute(&local_name!("form"));
1721 let node = elem.upcast::<Node>();
1722
1723 if self.is_listed() && !form_id.is_empty() && node.is_connected() {
1724 node.owner_document()
1725 .register_form_id_listener(form_id, self);
1726 }
1727 }
1728
1729 fn unregister_if_necessary(&self) {
1730 let elem = self.to_element();
1731 let form_id = elem.get_string_attribute(&local_name!("form"));
1732
1733 if self.is_listed() && !form_id.is_empty() {
1734 elem.owner_document()
1735 .unregister_form_id_listener(form_id, self);
1736 }
1737 }
1738
1739 fn bind_form_control_to_tree(&self, can_gc: CanGc) {
1741 let elem = self.to_element();
1742 let node = elem.upcast::<Node>();
1743
1744 let must_skip_reset = node.get_flag(NodeFlags::PARSER_ASSOCIATED_FORM_OWNER);
1749 node.set_flag(NodeFlags::PARSER_ASSOCIATED_FORM_OWNER, false);
1750
1751 if !must_skip_reset {
1752 self.form_attribute_mutated(
1753 AttributeMutation::Set(None, AttributeMutationReason::Directly),
1754 can_gc,
1755 );
1756 }
1757 }
1758
1759 fn unbind_form_control_from_tree(&self, can_gc: CanGc) {
1761 let elem = self.to_element();
1762 let has_form_attr = elem.has_attribute(&local_name!("form"));
1763 let same_subtree = self
1764 .form_owner()
1765 .is_none_or(|form| elem.is_in_same_home_subtree(&*form));
1766
1767 self.unregister_if_necessary();
1768
1769 if !same_subtree || (self.is_listed() && has_form_attr) {
1775 self.reset_form_owner(can_gc);
1776 }
1777 }
1778
1779 fn get_form_attribute<InputFn, OwnerFn>(
1780 &self,
1781 attr: &LocalName,
1782 input: InputFn,
1783 owner: OwnerFn,
1784 ) -> DOMString
1785 where
1786 InputFn: Fn(&Self) -> DOMString,
1787 OwnerFn: Fn(&HTMLFormElement) -> DOMString,
1788 Self: Sized,
1789 {
1790 if self.to_element().has_attribute(attr) {
1791 input(self)
1792 } else {
1793 self.form_owner().map_or(DOMString::new(), |t| owner(&t))
1794 }
1795 }
1796
1797 fn get_form_boolean_attribute<InputFn, OwnerFn>(
1798 &self,
1799 attr: &LocalName,
1800 input: InputFn,
1801 owner: OwnerFn,
1802 ) -> bool
1803 where
1804 InputFn: Fn(&Self) -> bool,
1805 OwnerFn: Fn(&HTMLFormElement) -> bool,
1806 Self: Sized,
1807 {
1808 if self.to_element().has_attribute(attr) {
1809 input(self)
1810 } else {
1811 self.form_owner().is_some_and(|t| owner(&t))
1812 }
1813 }
1814
1815 fn is_candidate_for_constraint_validation(&self) -> bool {
1817 let element = self.to_element();
1818 let html_element = element.downcast::<HTMLElement>();
1819 if let Some(html_element) = html_element {
1820 html_element.is_submittable_element() || element.is_instance_validatable()
1821 } else {
1822 false
1823 }
1824 }
1825
1826 fn moving_steps(&self, can_gc: CanGc) {
1827 let same_subtree = self
1830 .form_owner()
1831 .is_none_or(|form| self.to_element().is_in_same_home_subtree(&*form));
1832 if !same_subtree {
1833 self.reset_form_owner(can_gc)
1834 }
1835 }
1836
1837 }
1840
1841impl VirtualMethods for HTMLFormElement {
1842 fn super_type(&self) -> Option<&dyn VirtualMethods> {
1843 Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods)
1844 }
1845
1846 fn unbind_from_tree(&self, context: &UnbindContext, can_gc: CanGc) {
1847 self.super_type().unwrap().unbind_from_tree(context, can_gc);
1848
1849 rooted_vec!(let mut to_reset);
1852 to_reset.extend(
1853 self.controls
1854 .borrow()
1855 .iter()
1856 .filter(|c| !c.is_in_same_home_subtree(self))
1857 .cloned(),
1858 );
1859
1860 for control in to_reset.iter() {
1861 control
1862 .as_maybe_form_control()
1863 .expect("Element must be a form control")
1864 .reset_form_owner(can_gc);
1865 }
1866 }
1867
1868 fn parse_plain_attribute(&self, name: &LocalName, value: DOMString) -> AttrValue {
1869 match name {
1870 &local_name!("rel") => AttrValue::from_serialized_tokenlist(value.into()),
1871 _ => self
1872 .super_type()
1873 .unwrap()
1874 .parse_plain_attribute(name, value),
1875 }
1876 }
1877
1878 fn attribute_mutated(
1879 &self,
1880 cx: &mut js::context::JSContext,
1881 attr: &Attr,
1882 mutation: AttributeMutation,
1883 ) {
1884 self.super_type()
1885 .unwrap()
1886 .attribute_mutated(cx, attr, mutation);
1887
1888 match *attr.local_name() {
1889 local_name!("rel") | local_name!("rev") => {
1890 self.relations
1891 .set(LinkRelations::for_element(self.upcast()));
1892 },
1893 _ => {},
1894 }
1895 }
1896
1897 fn bind_to_tree(&self, cx: &mut JSContext, context: &BindContext) {
1898 if let Some(s) = self.super_type() {
1899 s.bind_to_tree(cx, context);
1900 }
1901
1902 self.relations
1903 .set(LinkRelations::for_element(self.upcast()));
1904 }
1905}
1906
1907pub(crate) trait FormControlElementHelpers {
1908 fn as_maybe_form_control(&self) -> Option<&dyn FormControl>;
1909}
1910
1911impl FormControlElementHelpers for Element {
1912 fn as_maybe_form_control(&self) -> Option<&dyn FormControl> {
1913 let node = self.upcast::<Node>();
1914
1915 match node.type_id() {
1916 NodeTypeId::Element(ElementTypeId::HTMLElement(
1917 HTMLElementTypeId::HTMLButtonElement,
1918 )) => Some(self.downcast::<HTMLButtonElement>().unwrap() as &dyn FormControl),
1919 NodeTypeId::Element(ElementTypeId::HTMLElement(
1920 HTMLElementTypeId::HTMLFieldSetElement,
1921 )) => Some(self.downcast::<HTMLFieldSetElement>().unwrap() as &dyn FormControl),
1922 NodeTypeId::Element(ElementTypeId::HTMLElement(
1923 HTMLElementTypeId::HTMLImageElement,
1924 )) => Some(self.downcast::<HTMLImageElement>().unwrap() as &dyn FormControl),
1925 NodeTypeId::Element(ElementTypeId::HTMLElement(
1926 HTMLElementTypeId::HTMLInputElement,
1927 )) => Some(self.downcast::<HTMLInputElement>().unwrap() as &dyn FormControl),
1928 NodeTypeId::Element(ElementTypeId::HTMLElement(
1929 HTMLElementTypeId::HTMLLabelElement,
1930 )) => Some(self.downcast::<HTMLLabelElement>().unwrap() as &dyn FormControl),
1931 NodeTypeId::Element(ElementTypeId::HTMLElement(
1932 HTMLElementTypeId::HTMLLegendElement,
1933 )) => Some(self.downcast::<HTMLLegendElement>().unwrap() as &dyn FormControl),
1934 NodeTypeId::Element(ElementTypeId::HTMLElement(
1935 HTMLElementTypeId::HTMLObjectElement,
1936 )) => Some(self.downcast::<HTMLObjectElement>().unwrap() as &dyn FormControl),
1937 NodeTypeId::Element(ElementTypeId::HTMLElement(
1938 HTMLElementTypeId::HTMLOutputElement,
1939 )) => Some(self.downcast::<HTMLOutputElement>().unwrap() as &dyn FormControl),
1940 NodeTypeId::Element(ElementTypeId::HTMLElement(
1941 HTMLElementTypeId::HTMLSelectElement,
1942 )) => Some(self.downcast::<HTMLSelectElement>().unwrap() as &dyn FormControl),
1943 NodeTypeId::Element(ElementTypeId::HTMLElement(
1944 HTMLElementTypeId::HTMLTextAreaElement,
1945 )) => Some(self.downcast::<HTMLTextAreaElement>().unwrap() as &dyn FormControl),
1946 _ => self.downcast::<HTMLElement>().and_then(|elem| {
1947 if elem.is_form_associated_custom_element() {
1948 Some(elem as &dyn FormControl)
1949 } else {
1950 None
1951 }
1952 }),
1953 }
1954 }
1955}
1956
1957pub(crate) fn encode_multipart_form_data(
1959 form_data: &mut [FormDatum],
1960 boundary: String,
1961 encoding: &'static Encoding,
1962) -> Vec<u8> {
1963 let mut result = vec![];
1964
1965 fn clean_crlf(s: &DOMString) -> DOMString {
1967 let mut buf = "".to_owned();
1968 let mut prev = ' ';
1969 for ch in s.str().chars() {
1970 match ch {
1971 '\n' if prev != '\r' => {
1972 buf.push('\r');
1973 buf.push('\n');
1974 },
1975 '\n' => {
1976 buf.push('\n');
1977 },
1978 _ if prev == '\r' => {
1981 buf.push('\r');
1982 buf.push('\n');
1983 buf.push(ch);
1984 },
1985 _ => buf.push(ch),
1986 };
1987 prev = ch;
1988 }
1989 if prev == '\r' {
1991 buf.push('\n');
1992 }
1993 DOMString::from(buf)
1994 }
1995
1996 for entry in form_data.iter_mut() {
1997 entry.name = clean_crlf(&entry.name);
1999
2000 if let FormDatumValue::String(ref s) = entry.value {
2003 entry.value = FormDatumValue::String(clean_crlf(s));
2004 }
2005
2006 let mut boundary_bytes = format!("--{}\r\n", boundary).into_bytes();
2011 result.append(&mut boundary_bytes);
2012
2013 match entry.value {
2016 FormDatumValue::String(ref s) => {
2017 let content_disposition = format!("form-data; name=\"{}\"", entry.name);
2018 let mut bytes =
2019 format!("Content-Disposition: {content_disposition}\r\n\r\n{s}\r\n",)
2020 .into_bytes();
2021 result.append(&mut bytes);
2022 },
2023 FormDatumValue::File(ref f) => {
2024 let charset = encoding.name();
2025 let extra = if charset.to_lowercase() == "utf-8" {
2026 format!("filename=\"{}\"", String::from(f.name().str()))
2027 } else {
2028 format!(
2029 "filename*=\"{}\"''{}",
2030 charset,
2031 http_percent_encode(&f.name().as_bytes())
2032 )
2033 };
2034
2035 let content_disposition = format!("form-data; name=\"{}\"; {}", entry.name, extra);
2036 let content_type: Mime = f
2038 .upcast::<Blob>()
2039 .Type()
2040 .parse()
2041 .unwrap_or(mime::TEXT_PLAIN);
2042 let mut type_bytes = format!(
2043 "Content-Disposition: {}\r\nContent-Type: {}\r\n\r\n",
2044 content_disposition, content_type
2045 )
2046 .into_bytes();
2047 result.append(&mut type_bytes);
2048
2049 let mut bytes = f.upcast::<Blob>().get_bytes().unwrap_or(vec![]);
2050
2051 result.append(&mut bytes);
2052 result.extend(b"\r\n");
2053 },
2054 }
2055 }
2056
2057 let mut boundary_bytes = format!("--{boundary}--\r\n").into_bytes();
2058 result.append(&mut boundary_bytes);
2059
2060 result
2061}
2062
2063pub(crate) fn generate_boundary() -> String {
2065 let i1 = random::<u32>();
2066 let i2 = random::<u32>();
2067
2068 format!("---------------------------{0}{1}", i1, i2)
2069}