1use std::borrow::ToOwned;
6use std::cell::Cell;
7
8use constellation_traits::{LoadData, LoadOrigin, NavigationHistoryBehavior};
9use content_security_policy::sandboxing_directive::SandboxingFlagSet;
10use dom_struct::dom_struct;
11use encoding_rs::{Encoding, UTF_8};
12use headers::{ContentType, HeaderMapExt};
13use html5ever::{LocalName, Prefix, local_name, ns};
14use http::Method;
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 style::attr::AttrValue;
23use style::str::split_html_space_chars;
24use stylo_atoms::Atom;
25use stylo_dom::ElementState;
26
27use crate::body::Extractable;
28use crate::dom::attr::Attr;
29use crate::dom::bindings::cell::DomRefCell;
30use crate::dom::bindings::codegen::Bindings::AttrBinding::Attr_Binding::AttrMethods;
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::Bindings::WindowBinding::Window_Binding::WindowMethods;
45use crate::dom::bindings::codegen::UnionTypes::RadioNodeListOrElement;
46use crate::dom::bindings::error::{Error, Fallible};
47use crate::dom::bindings::inheritance::{Castable, ElementTypeId, HTMLElementTypeId, NodeTypeId};
48use crate::dom::bindings::refcounted::Trusted;
49use crate::dom::bindings::reflector::{DomGlobal, DomObject};
50use crate::dom::bindings::root::{Dom, DomOnceCell, DomRoot, MutNullableDom};
51use crate::dom::bindings::str::DOMString;
52use crate::dom::bindings::trace::{HashMapTracedValues, NoTrace};
53use crate::dom::blob::Blob;
54use crate::dom::customelementregistry::CallbackReaction;
55use crate::dom::document::Document;
56use crate::dom::domtokenlist::DOMTokenList;
57use crate::dom::element::{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::htmlbuttonelement::HTMLButtonElement;
64use crate::dom::html::htmlcollection::CollectionFilter;
65use crate::dom::html::htmldatalistelement::HTMLDataListElement;
66use crate::dom::html::htmlelement::HTMLElement;
67use crate::dom::html::htmlfieldsetelement::HTMLFieldSetElement;
68use crate::dom::html::htmlformcontrolscollection::HTMLFormControlsCollection;
69use crate::dom::html::htmlimageelement::HTMLImageElement;
70use crate::dom::html::htmlinputelement::{HTMLInputElement, InputType};
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::node::{
78 BindContext, Node, NodeFlags, NodeTraits, UnbindContext, VecPreOrderInsertionHelper,
79};
80use crate::dom::nodelist::{NodeList, RadioListMode};
81use crate::dom::radionodelist::RadioNodeList;
82use crate::dom::submitevent::SubmitEvent;
83use crate::dom::types::HTMLIFrameElement;
84use crate::dom::virtualmethods::VirtualMethods;
85use crate::dom::window::Window;
86use crate::links::{LinkRelations, get_element_target, valid_navigable_target_name_or_keyword};
87use crate::script_runtime::CanGc;
88use crate::script_thread::ScriptThread;
89
90#[dom_struct]
91pub(crate) struct HTMLFormElement {
92 htmlelement: HTMLElement,
93 marked_for_reset: Cell<bool>,
94 constructing_entry_list: Cell<bool>,
96 elements: DomOnceCell<HTMLFormControlsCollection>,
97 controls: DomRefCell<Vec<Dom<Element>>>,
98
99 #[expect(clippy::type_complexity)]
101 past_names_map:
102 DomRefCell<HashMapTracedValues<Atom, (Dom<Element>, NoTrace<usize>), FxBuildHasher>>,
103
104 current_name_generation: Cell<usize>,
106
107 firing_submission_events: Cell<bool>,
108 rel_list: MutNullableDom<DOMTokenList>,
109
110 planned_navigation: Cell<usize>,
112
113 #[no_trace]
114 relations: Cell<LinkRelations>,
115}
116
117impl HTMLFormElement {
118 fn new_inherited(
119 local_name: LocalName,
120 prefix: Option<Prefix>,
121 document: &Document,
122 ) -> HTMLFormElement {
123 HTMLFormElement {
124 htmlelement: HTMLElement::new_inherited_with_state(
125 ElementState::VALID,
126 local_name,
127 prefix,
128 document,
129 ),
130 marked_for_reset: Cell::new(false),
131 constructing_entry_list: Cell::new(false),
132 elements: Default::default(),
133 controls: DomRefCell::new(Vec::new()),
134 past_names_map: DomRefCell::new(HashMapTracedValues::new_fx()),
135 current_name_generation: Cell::new(0),
136 firing_submission_events: Cell::new(false),
137 rel_list: Default::default(),
138 planned_navigation: Default::default(),
139 relations: Cell::new(LinkRelations::empty()),
140 }
141 }
142
143 pub(crate) fn new(
144 local_name: LocalName,
145 prefix: Option<Prefix>,
146 document: &Document,
147 proto: Option<HandleObject>,
148 can_gc: CanGc,
149 ) -> DomRoot<HTMLFormElement> {
150 Node::reflect_node_with_proto(
151 Box::new(HTMLFormElement::new_inherited(local_name, prefix, document)),
152 document,
153 proto,
154 can_gc,
155 )
156 }
157
158 fn filter_for_radio_list(mode: RadioListMode, child: &Element, name: &Atom) -> bool {
159 if let Some(child) = child.downcast::<Element>() {
160 match mode {
161 RadioListMode::ControlsExceptImageInputs => {
162 if child
163 .downcast::<HTMLElement>()
164 .is_some_and(|c| c.is_listed_element()) &&
165 (child.get_id().is_some_and(|i| i == *name) ||
166 child.get_name().is_some_and(|n| n == *name))
167 {
168 if let Some(inp) = child.downcast::<HTMLInputElement>() {
169 return inp.input_type() != InputType::Image;
171 } else {
172 return true;
174 }
175 }
176 return false;
177 },
178 RadioListMode::Images => {
179 return child.is::<HTMLImageElement>() &&
180 (child.get_id().is_some_and(|i| i == *name) ||
181 child.get_name().is_some_and(|n| n == *name));
182 },
183 }
184 }
185 false
186 }
187
188 pub(crate) fn nth_for_radio_list(
189 &self,
190 index: u32,
191 mode: RadioListMode,
192 name: &Atom,
193 ) -> Option<DomRoot<Node>> {
194 self.controls
195 .borrow()
196 .iter()
197 .filter(|n| HTMLFormElement::filter_for_radio_list(mode, n, name))
198 .nth(index as usize)
199 .map(|n| DomRoot::from_ref(n.upcast::<Node>()))
200 }
201
202 pub(crate) fn count_for_radio_list(&self, mode: RadioListMode, name: &Atom) -> u32 {
203 self.controls
204 .borrow()
205 .iter()
206 .filter(|n| HTMLFormElement::filter_for_radio_list(mode, n, name))
207 .count() as u32
208 }
209}
210
211impl HTMLFormElementMethods<crate::DomTypeHolder> for HTMLFormElement {
212 make_getter!(AcceptCharset, "accept-charset");
214
215 make_setter!(SetAcceptCharset, "accept-charset");
217
218 make_form_action_getter!(Action, "action");
220
221 make_setter!(SetAction, "action");
223
224 make_enumerated_getter!(
226 Autocomplete,
227 "autocomplete",
228 "on" | "off",
229 missing => "on",
230 invalid => "on"
231 );
232
233 make_setter!(SetAutocomplete, "autocomplete");
235
236 make_enumerated_getter!(
238 Enctype,
239 "enctype",
240 "application/x-www-form-urlencoded" | "text/plain" | "multipart/form-data",
241 missing => "application/x-www-form-urlencoded",
242 invalid => "application/x-www-form-urlencoded"
243 );
244
245 make_setter!(SetEnctype, "enctype");
247
248 fn Encoding(&self) -> DOMString {
250 self.Enctype()
251 }
252
253 fn SetEncoding(&self, value: DOMString) {
255 self.SetEnctype(value)
256 }
257
258 make_enumerated_getter!(
260 Method,
261 "method",
262 "get" | "post" | "dialog",
263 missing => "get",
264 invalid => "get"
265 );
266
267 make_setter!(SetMethod, "method");
269
270 make_getter!(Name, "name");
272
273 make_atomic_setter!(SetName, "name");
275
276 make_bool_getter!(NoValidate, "novalidate");
278
279 make_bool_setter!(SetNoValidate, "novalidate");
281
282 make_getter!(Target, "target");
284
285 make_setter!(SetTarget, "target");
287
288 make_getter!(Rel, "rel");
290
291 fn Submit(&self, can_gc: CanGc) {
293 self.submit(
294 SubmittedFrom::FromForm,
295 FormSubmitterElement::Form(self),
296 can_gc,
297 );
298 }
299
300 fn RequestSubmit(&self, submitter: Option<&HTMLElement>, can_gc: CanGc) -> Fallible<()> {
302 let submitter: FormSubmitterElement = match submitter {
303 Some(submitter_element) => {
304 let error_not_a_submit_button =
306 Err(Error::Type(c"submitter must be a submit button".to_owned()));
307
308 let element = match submitter_element.upcast::<Node>().type_id() {
309 NodeTypeId::Element(ElementTypeId::HTMLElement(element)) => element,
310 _ => {
311 return error_not_a_submit_button;
312 },
313 };
314
315 let submit_button = match element {
316 HTMLElementTypeId::HTMLInputElement => FormSubmitterElement::Input(
317 submitter_element
318 .downcast::<HTMLInputElement>()
319 .expect("Failed to downcast submitter elem to HTMLInputElement."),
320 ),
321 HTMLElementTypeId::HTMLButtonElement => FormSubmitterElement::Button(
322 submitter_element
323 .downcast::<HTMLButtonElement>()
324 .expect("Failed to downcast submitter elem to HTMLButtonElement."),
325 ),
326 _ => {
327 return error_not_a_submit_button;
328 },
329 };
330
331 if !submit_button.is_submit_button() {
332 return error_not_a_submit_button;
333 }
334
335 let submitters_owner = submit_button.form_owner();
336
337 let owner = match submitters_owner {
339 Some(owner) => owner,
340 None => {
341 return Err(Error::NotFound(None));
342 },
343 };
344
345 if *owner != *self {
346 return Err(Error::NotFound(None));
347 }
348
349 submit_button
350 },
351 None => {
352 FormSubmitterElement::Form(self)
354 },
355 };
356 self.submit(SubmittedFrom::NotFromForm, submitter, can_gc);
358 Ok(())
359 }
360
361 fn Reset(&self, can_gc: CanGc) {
363 self.reset(ResetFrom::FromForm, can_gc);
364 }
365
366 fn Elements(&self, can_gc: CanGc) -> DomRoot<HTMLFormControlsCollection> {
368 #[derive(JSTraceable, MallocSizeOf)]
369 struct ElementsFilter {
370 form: DomRoot<HTMLFormElement>,
371 }
372 impl CollectionFilter for ElementsFilter {
373 fn filter<'a>(&self, elem: &'a Element, _root: &'a Node) -> bool {
374 let form_owner = match elem.upcast::<Node>().type_id() {
375 NodeTypeId::Element(ElementTypeId::HTMLElement(t)) => match t {
376 HTMLElementTypeId::HTMLButtonElement => {
377 elem.downcast::<HTMLButtonElement>().unwrap().form_owner()
378 },
379 HTMLElementTypeId::HTMLFieldSetElement => {
380 elem.downcast::<HTMLFieldSetElement>().unwrap().form_owner()
381 },
382 HTMLElementTypeId::HTMLInputElement => {
383 let input_elem = elem.downcast::<HTMLInputElement>().unwrap();
384 if input_elem.input_type() == InputType::Image {
385 return false;
386 }
387 input_elem.form_owner()
388 },
389 HTMLElementTypeId::HTMLObjectElement => {
390 elem.downcast::<HTMLObjectElement>().unwrap().form_owner()
391 },
392 HTMLElementTypeId::HTMLOutputElement => {
393 elem.downcast::<HTMLOutputElement>().unwrap().form_owner()
394 },
395 HTMLElementTypeId::HTMLSelectElement => {
396 elem.downcast::<HTMLSelectElement>().unwrap().form_owner()
397 },
398 HTMLElementTypeId::HTMLTextAreaElement => {
399 elem.downcast::<HTMLTextAreaElement>().unwrap().form_owner()
400 },
401 HTMLElementTypeId::HTMLElement => {
402 let html_element = elem.downcast::<HTMLElement>().unwrap();
403 if html_element.is_form_associated_custom_element() {
404 html_element.form_owner()
405 } else {
406 return false;
407 }
408 },
409 _ => {
410 debug_assert!(
411 !elem.downcast::<HTMLElement>().unwrap().is_listed_element()
412 );
413 return false;
414 },
415 },
416 _ => return false,
417 };
418
419 match form_owner {
420 Some(form_owner) => form_owner == self.form,
421 None => false,
422 }
423 }
424 }
425 DomRoot::from_ref(self.elements.init_once(|| {
426 let filter = Box::new(ElementsFilter {
427 form: DomRoot::from_ref(self),
428 });
429 let window = self.owner_window();
430 HTMLFormControlsCollection::new(&window, self, filter, can_gc)
431 }))
432 }
433
434 fn Length(&self) -> u32 {
436 self.Elements(CanGc::note()).Length()
437 }
438
439 fn IndexedGetter(&self, index: u32, can_gc: CanGc) -> Option<DomRoot<Element>> {
441 let elements = self.Elements(can_gc);
442 elements.IndexedGetter(index)
443 }
444
445 fn NamedGetter(&self, name: DOMString, can_gc: CanGc) -> Option<RadioNodeListOrElement> {
447 let window = self.owner_window();
448
449 let name = Atom::from(name);
450
451 let mut candidates =
453 RadioNodeList::new_controls_except_image_inputs(&window, self, &name, can_gc);
454 let mut candidates_length = candidates.Length();
455
456 if candidates_length == 0 {
458 candidates = RadioNodeList::new_images(&window, self, &name, can_gc);
459 candidates_length = candidates.Length();
460 }
461
462 let mut past_names_map = self.past_names_map.borrow_mut();
463
464 if candidates_length == 0 {
466 if past_names_map.contains_key(&name) {
467 return Some(RadioNodeListOrElement::Element(DomRoot::from_ref(
468 &*past_names_map.get(&name).unwrap().0,
469 )));
470 }
471 return None;
472 }
473
474 if candidates_length > 1 {
476 return Some(RadioNodeListOrElement::RadioNodeList(candidates));
477 }
478
479 let element_node = candidates.upcast::<NodeList>().Item(0).unwrap();
482 past_names_map.insert(
483 name,
484 (
485 Dom::from_ref(element_node.downcast::<Element>().unwrap()),
486 NoTrace(self.current_name_generation.get() + 1),
487 ),
488 );
489 self.current_name_generation
490 .set(self.current_name_generation.get() + 1);
491
492 Some(RadioNodeListOrElement::Element(DomRoot::from_ref(
494 element_node.downcast::<Element>().unwrap(),
495 )))
496 }
497
498 fn SetRel(&self, rel: DOMString, can_gc: CanGc) {
500 self.upcast::<Element>()
501 .set_tokenlist_attribute(&local_name!("rel"), rel, can_gc);
502 }
503
504 fn RelList(&self, can_gc: CanGc) -> DomRoot<DOMTokenList> {
506 self.rel_list.or_init(|| {
507 DOMTokenList::new(
508 self.upcast(),
509 &local_name!("rel"),
510 Some(vec![
511 Atom::from("noopener"),
512 Atom::from("noreferrer"),
513 Atom::from("opener"),
514 ]),
515 can_gc,
516 )
517 })
518 }
519
520 fn SupportedPropertyNames(&self) -> Vec<DOMString> {
522 #[derive(Debug, Eq, Ord, PartialEq, PartialOrd)]
524 enum SourcedNameSource {
525 Id,
526 Name,
527 Past(usize),
528 }
529
530 impl SourcedNameSource {
531 fn is_past(&self) -> bool {
532 matches!(self, SourcedNameSource::Past(..))
533 }
534 }
535
536 struct SourcedName {
537 name: Atom,
538 element: DomRoot<Element>,
539 source: SourcedNameSource,
540 }
541
542 let mut sourced_names_vec: Vec<SourcedName> = Vec::new();
543
544 for child in self.controls.borrow().iter() {
546 if child
547 .downcast::<HTMLElement>()
548 .is_some_and(|c| c.is_listed_element())
549 {
550 if let Some(id_atom) = child.get_id() {
551 let entry = SourcedName {
552 name: id_atom,
553 element: DomRoot::from_ref(child),
554 source: SourcedNameSource::Id,
555 };
556 sourced_names_vec.push(entry);
557 }
558 if let Some(name_atom) = child.get_name() {
559 let entry = SourcedName {
560 name: name_atom,
561 element: DomRoot::from_ref(child),
562 source: SourcedNameSource::Name,
563 };
564 sourced_names_vec.push(entry);
565 }
566 }
567 }
568
569 for child in self.controls.borrow().iter() {
571 if child.is::<HTMLImageElement>() {
572 if let Some(id_atom) = child.get_id() {
573 let entry = SourcedName {
574 name: id_atom,
575 element: DomRoot::from_ref(child),
576 source: SourcedNameSource::Id,
577 };
578 sourced_names_vec.push(entry);
579 }
580 if let Some(name_atom) = child.get_name() {
581 let entry = SourcedName {
582 name: name_atom,
583 element: DomRoot::from_ref(child),
584 source: SourcedNameSource::Name,
585 };
586 sourced_names_vec.push(entry);
587 }
588 }
589 }
590
591 let past_names_map = self.past_names_map.borrow();
593 for (key, val) in past_names_map.iter() {
594 let entry = SourcedName {
595 name: key.clone(),
596 element: DomRoot::from_ref(&*val.0),
597 source: SourcedNameSource::Past(self.current_name_generation.get() - val.1.0),
598 };
599 sourced_names_vec.push(entry);
600 }
601
602 sourced_names_vec.sort_by(|a, b| {
614 if a.element
615 .upcast::<Node>()
616 .CompareDocumentPosition(b.element.upcast::<Node>()) ==
617 0
618 {
619 if a.source.is_past() && b.source.is_past() {
620 b.source.cmp(&a.source)
621 } else {
622 a.source.cmp(&b.source)
623 }
624 } else if a
625 .element
626 .upcast::<Node>()
627 .CompareDocumentPosition(b.element.upcast::<Node>()) &
628 NodeConstants::DOCUMENT_POSITION_FOLLOWING ==
629 NodeConstants::DOCUMENT_POSITION_FOLLOWING
630 {
631 std::cmp::Ordering::Less
632 } else {
633 std::cmp::Ordering::Greater
634 }
635 });
636
637 sourced_names_vec.retain(|sn| !sn.name.to_string().is_empty());
639
640 let mut names_vec: Vec<DOMString> = Vec::new();
642 for elem in sourced_names_vec.iter() {
643 if !names_vec.iter().any(|name| *name == *elem.name) {
644 names_vec.push(DOMString::from(&*elem.name));
645 }
646 }
647
648 names_vec
649 }
650
651 fn CheckValidity(&self, can_gc: CanGc) -> bool {
653 self.static_validation(can_gc).is_ok()
654 }
655
656 fn ReportValidity(&self, can_gc: CanGc) -> bool {
658 self.interactive_validation(can_gc).is_ok()
659 }
660}
661
662#[derive(Clone, Copy, MallocSizeOf, PartialEq)]
663pub(crate) enum SubmittedFrom {
664 FromForm,
665 NotFromForm,
666}
667
668#[derive(Clone, Copy, MallocSizeOf)]
669pub(crate) enum ResetFrom {
670 FromForm,
671 NotFromForm,
672}
673
674impl HTMLFormElement {
675 fn pick_encoding(&self) -> &'static Encoding {
677 if self
679 .upcast::<Element>()
680 .has_attribute(&local_name!("accept-charset"))
681 {
682 let input = self
684 .upcast::<Element>()
685 .get_string_attribute(&local_name!("accept-charset"));
686
687 let input = input.str();
689 let mut candidate_encodings =
690 split_html_space_chars(&input).filter_map(|c| Encoding::for_label(c.as_bytes()));
691
692 return candidate_encodings.next().unwrap_or(UTF_8);
694 }
695
696 self.owner_document().encoding()
698 }
699
700 fn encode_plaintext(&self, form_data: &mut [FormDatum]) -> String {
702 let mut result = String::new();
704
705 for entry in form_data.iter() {
707 let value = match &entry.value {
708 FormDatumValue::File(f) => f.name(),
709 FormDatumValue::String(s) => s,
710 };
711 result.push_str(&format!("{}={}\r\n", entry.name, value));
712 }
713
714 result
716 }
717
718 pub(crate) fn update_validity(&self, can_gc: CanGc) {
719 let is_any_invalid = self
720 .controls
721 .borrow()
722 .iter()
723 .any(|control| control.is_invalid(false, can_gc));
724
725 self.upcast::<Element>()
726 .set_state(ElementState::VALID, !is_any_invalid);
727 self.upcast::<Element>()
728 .set_state(ElementState::INVALID, is_any_invalid);
729 }
730
731 pub(crate) fn submit(
733 &self,
734 submit_method_flag: SubmittedFrom,
735 submitter: FormSubmitterElement,
736 can_gc: CanGc,
737 ) {
738 if self.upcast::<Element>().cannot_navigate() {
740 return;
741 }
742
743 if self.constructing_entry_list.get() {
745 return;
746 }
747 let doc = self.owner_document();
749
750 if doc.has_active_sandboxing_flag(SandboxingFlagSet::SANDBOXED_FORMS_BROWSING_CONTEXT_FLAG)
753 {
754 return;
755 }
756
757 let base = doc.base_url();
758 if submit_method_flag == SubmittedFrom::NotFromForm {
761 if self.firing_submission_events.get() {
763 return;
764 }
765 self.firing_submission_events.set(true);
767 if !submitter.no_validate(self) && self.interactive_validation(can_gc).is_err() {
769 self.firing_submission_events.set(false);
770 return;
771 }
772 let submitter_button = match submitter {
776 FormSubmitterElement::Form(f) => {
777 if f == self {
778 None
779 } else {
780 Some(f.upcast::<HTMLElement>())
781 }
782 },
783 FormSubmitterElement::Input(i) => Some(i.upcast::<HTMLElement>()),
784 FormSubmitterElement::Button(b) => Some(b.upcast::<HTMLElement>()),
785 };
786
787 let event = SubmitEvent::new(
789 self.global().as_window(),
790 atom!("submit"),
791 true,
792 true,
793 submitter_button.map(DomRoot::from_ref),
794 can_gc,
795 );
796 let event = event.upcast::<Event>();
797 event.fire(self.upcast::<EventTarget>(), can_gc);
798
799 self.firing_submission_events.set(false);
801 if event.DefaultPrevented() {
803 return;
804 }
805 if self.upcast::<Element>().cannot_navigate() {
807 return;
808 }
809 }
810
811 let encoding = self.pick_encoding();
813
814 let mut form_data = match self.get_form_dataset(Some(submitter), Some(encoding), can_gc) {
816 Some(form_data) => form_data,
817 None => return,
818 };
819
820 if self.upcast::<Element>().cannot_navigate() {
822 return;
823 }
824
825 let method = submitter.method();
827 let mut action = submitter.action();
832
833 if action.is_empty() {
835 action = DOMString::from(base.as_str());
836 }
837 let action_components = match base.join(&action.str()) {
839 Ok(url) => url,
840 Err(_) => return,
842 };
843 let scheme = action_components.scheme().to_owned();
845 let enctype = submitter.enctype();
847
848 let form_target_attribute = submitter.target();
851 let form_target = if submitter.is_submit_button() &&
852 valid_navigable_target_name_or_keyword(&form_target_attribute)
853 {
854 Some(form_target_attribute)
855 } else {
856 None
858 };
859 let form_owner = submitter.form_owner();
861 let form = form_owner.as_deref().unwrap_or(self);
862 let target = get_element_target(form.upcast::<Element>(), form_target);
863
864 let noopener = self.relations.get().get_element_noopener(target.as_ref());
866
867 let source = doc.browsing_context().unwrap();
870 let (maybe_chosen, _new) =
871 source.choose_browsing_context(target.unwrap_or_default(), noopener);
872
873 let Some(chosen) = maybe_chosen else {
874 return;
876 };
877 let target_document = match chosen.document() {
884 Some(doc) => doc,
885 None => return,
886 };
887 let target_window = target_document.window();
888 let mut load_data = LoadData::new(
889 LoadOrigin::Script(doc.origin().snapshot()),
890 action_components,
891 target_document.about_base_url(),
892 None,
893 target_window.as_global_scope().get_referrer(),
894 target_document.get_referrer_policy(),
895 Some(target_window.as_global_scope().is_secure_context()),
896 Some(target_document.insecure_requests_policy()),
897 target_document.has_trustworthy_ancestor_origin(),
898 target_document.creation_sandboxing_flag_set_considering_parent_iframe(),
899 );
900
901 match (&*scheme, method) {
905 (_, FormMethod::Dialog) => {
906 },
909 ("http", FormMethod::Get) | ("https", FormMethod::Get) | ("data", FormMethod::Get) => {
911 load_data
912 .headers
913 .typed_insert(ContentType::from(mime::APPLICATION_WWW_FORM_URLENCODED));
914 self.mutate_action_url(&mut form_data, load_data, encoding, target_window);
915 },
916 ("http", FormMethod::Post) | ("https", FormMethod::Post) => {
918 load_data.method = Method::POST;
919 self.submit_entity_body(
920 &mut form_data,
921 load_data,
922 enctype,
923 encoding,
924 target_window,
925 can_gc,
926 );
927 },
928 ("file", _) |
930 ("about", _) |
931 ("data", FormMethod::Post) |
932 ("ftp", _) |
933 ("javascript", _) => {
934 self.plan_to_navigate(load_data, target_window);
935 },
936 ("mailto", FormMethod::Post) => {
937 },
940 ("mailto", FormMethod::Get) => {
941 },
944 _ => (),
945 }
946 }
947
948 fn mutate_action_url(
950 &self,
951 form_data: &mut [FormDatum],
952 mut load_data: LoadData,
953 encoding: &'static Encoding,
954 target: &Window,
955 ) {
956 let charset = encoding.name();
957
958 self.set_url_query_pairs(
959 &mut load_data.url,
960 form_data
961 .iter()
962 .map(|field| (field.name.str(), field.replace_value(charset))),
963 );
964
965 self.plan_to_navigate(load_data, target);
966 }
967
968 fn submit_entity_body(
970 &self,
971 form_data: &mut [FormDatum],
972 mut load_data: LoadData,
973 enctype: FormEncType,
974 encoding: &'static Encoding,
975 target: &Window,
976 can_gc: CanGc,
977 ) {
978 let boundary = generate_boundary();
979 let bytes = match enctype {
980 FormEncType::UrlEncoded => {
981 let charset = encoding.name();
982 load_data
983 .headers
984 .typed_insert(ContentType::from(mime::APPLICATION_WWW_FORM_URLENCODED));
985
986 let mut url = load_data.url.clone();
987 self.set_url_query_pairs(
988 &mut url,
989 form_data
990 .iter()
991 .map(|field| (field.name.str(), field.replace_value(charset))),
992 );
993
994 url.query().unwrap_or("").to_string().into_bytes()
995 },
996 FormEncType::MultipartFormData => {
997 let mime: Mime = format!("multipart/form-data; boundary={}", boundary)
998 .parse()
999 .unwrap();
1000 load_data.headers.typed_insert(ContentType::from(mime));
1001 encode_multipart_form_data(form_data, boundary, encoding)
1002 },
1003 FormEncType::TextPlain => {
1004 load_data
1005 .headers
1006 .typed_insert(ContentType::from(mime::TEXT_PLAIN));
1007 self.encode_plaintext(form_data).into_bytes()
1008 },
1009 };
1010
1011 let global = self.global();
1012
1013 let request_body = bytes
1014 .extract(&global, false, can_gc)
1015 .expect("Couldn't extract body.")
1016 .into_net_request_body()
1017 .0;
1018 load_data.data = Some(request_body);
1019
1020 self.plan_to_navigate(load_data, target);
1021 }
1022
1023 fn set_url_query_pairs<T>(
1024 &self,
1025 url: &mut servo_url::ServoUrl,
1026 pairs: impl Iterator<Item = (T, String)>,
1027 ) where
1028 T: AsRef<str>,
1029 {
1030 let encoding = self.pick_encoding();
1031 url.as_mut_url()
1032 .query_pairs_mut()
1033 .encoding_override(Some(&|s| encoding.encode(s).0))
1034 .clear()
1035 .extend_pairs(pairs);
1036 }
1037
1038 fn plan_to_navigate(&self, mut load_data: LoadData, target: &Window) {
1040 let elem = self.upcast::<Element>();
1045 let referrer = match elem.get_attribute(&ns!(), &local_name!("rel")) {
1046 Some(ref link_types) if link_types.Value().contains("noreferrer") => {
1047 Referrer::NoReferrer
1048 },
1049 _ => target.as_global_scope().get_referrer(),
1050 };
1051
1052 self.planned_navigation
1055 .set(self.planned_navigation.get().wrapping_add(1));
1056 let planned_navigation = self.planned_navigation.get();
1057
1058 let ongoing_navigation = target.set_ongoing_navigation();
1074
1075 let referrer_policy = target.Document().get_referrer_policy();
1076 load_data.creator_pipeline_id = Some(target.pipeline_id());
1077 load_data.referrer = referrer;
1078 load_data.referrer_policy = referrer_policy;
1079
1080 if let Some(window_proxy) = target.undiscarded_window_proxy() {
1083 if let Some(frame) = window_proxy
1084 .frame_element()
1085 .and_then(|e| e.downcast::<HTMLIFrameElement>())
1086 {
1087 frame.note_pending_navigation()
1088 }
1089 }
1090
1091 let form = Trusted::new(self);
1094 let window = Trusted::new(target);
1095 let task = task!(navigate_to_form_planned_navigation: move || {
1096 if planned_navigation != form.root().planned_navigation.get() {
1100 return;
1101 }
1102
1103 if ongoing_navigation != window.root().ongoing_navigation() {
1106 return;
1107 }
1108
1109 window
1111 .root()
1112 .load_url(
1113 NavigationHistoryBehavior::Push,
1114 false,
1115 load_data,
1116 CanGc::note(),
1117 );
1118 });
1119
1120 target
1125 .global()
1126 .task_manager()
1127 .dom_manipulation_task_source()
1128 .queue(task)
1129 }
1130
1131 fn interactive_validation(&self, can_gc: CanGc) -> Result<(), ()> {
1134 let unhandled_invalid_controls = match self.static_validation(can_gc) {
1139 Ok(()) => return Ok(()),
1140 Err(err) => err,
1141 };
1142
1143 let mut first = true;
1146
1147 for elem in unhandled_invalid_controls {
1148 if let Some(validatable) = elem.as_maybe_validatable() {
1149 error!("Validation error: {}", validatable.validation_message());
1150 }
1151 if first {
1152 if let Some(html_elem) = elem.downcast::<HTMLElement>() {
1153 html_elem.Focus(&FocusOptions::default(), can_gc);
1160 first = false;
1161 }
1162 }
1163 }
1164
1165 Err(())
1169 }
1170
1171 fn static_validation(&self, can_gc: CanGc) -> Result<(), Vec<DomRoot<Element>>> {
1174 let invalid_controls = self
1176 .controls
1177 .borrow()
1178 .iter()
1179 .filter_map(|field| {
1180 if let Some(element) = field.downcast::<Element>() {
1181 if element.is_invalid(true, can_gc) {
1182 Some(DomRoot::from_ref(element))
1183 } else {
1184 None
1185 }
1186 } else {
1187 None
1188 }
1189 })
1190 .collect::<Vec<DomRoot<Element>>>();
1191 if invalid_controls.is_empty() {
1193 return Ok(());
1194 }
1195 let unhandled_invalid_controls = invalid_controls
1197 .into_iter()
1198 .filter_map(|field| {
1199 let not_canceled = field
1202 .upcast::<EventTarget>()
1203 .fire_cancelable_event(atom!("invalid"), can_gc);
1204 if not_canceled {
1206 return Some(field);
1207 }
1208 None
1209 })
1210 .collect::<Vec<DomRoot<Element>>>();
1211 Err(unhandled_invalid_controls)
1213 }
1214
1215 fn get_unclean_dataset(
1220 &self,
1221 submitter: Option<FormSubmitterElement>,
1222 encoding: Option<&'static Encoding>,
1223 can_gc: CanGc,
1224 ) -> Vec<FormDatum> {
1225 let mut data_set = Vec::new();
1226 for child in self.controls.borrow().iter() {
1227 if child.disabled_state() {
1229 continue;
1230 }
1231 let child = child.upcast::<Node>();
1232
1233 if child.ancestors().any(|a| a.is::<HTMLDataListElement>()) {
1235 continue;
1236 }
1237 if let NodeTypeId::Element(ElementTypeId::HTMLElement(element)) = child.type_id() {
1238 match element {
1239 HTMLElementTypeId::HTMLInputElement => {
1240 let input = child.downcast::<HTMLInputElement>().unwrap();
1241 data_set.append(&mut input.form_datums(submitter, encoding));
1242 },
1243 HTMLElementTypeId::HTMLButtonElement => {
1244 let button = child.downcast::<HTMLButtonElement>().unwrap();
1245 if let Some(datum) = button.form_datum(submitter) {
1246 data_set.push(datum);
1247 }
1248 },
1249 HTMLElementTypeId::HTMLObjectElement => {
1250 },
1252 HTMLElementTypeId::HTMLSelectElement => {
1253 let select = child.downcast::<HTMLSelectElement>().unwrap();
1254 select.push_form_data(&mut data_set);
1255 },
1256 HTMLElementTypeId::HTMLTextAreaElement => {
1257 let textarea = child.downcast::<HTMLTextAreaElement>().unwrap();
1258 let name = textarea.Name();
1259 if !name.is_empty() {
1260 data_set.push(FormDatum {
1261 ty: textarea.Type(),
1262 name,
1263 value: FormDatumValue::String(textarea.Value()),
1264 });
1265 }
1266 },
1267 HTMLElementTypeId::HTMLElement => {
1268 let custom = child.downcast::<HTMLElement>().unwrap();
1269 if custom.is_form_associated_custom_element() {
1270 let internals =
1272 custom.upcast::<Element>().ensure_element_internals(can_gc);
1273 internals.perform_entry_construction(&mut data_set);
1274 }
1276 },
1277 _ => (),
1278 }
1279 }
1280
1281 let child_element = child.downcast::<Element>().unwrap();
1285 let input_matches = child_element
1286 .downcast::<HTMLInputElement>()
1287 .is_some_and(|input| {
1288 matches!(input.input_type(), InputType::Text | InputType::Search)
1289 });
1290 let textarea_matches = child_element.is::<HTMLTextAreaElement>();
1291 let dirname = child_element.get_string_attribute(&local_name!("dirname"));
1292 if (input_matches || textarea_matches) && !dirname.is_empty() {
1293 let dir = DOMString::from(child_element.directionality());
1294 data_set.push(FormDatum {
1295 ty: DOMString::from("string"),
1296 name: dirname,
1297 value: FormDatumValue::String(dir),
1298 });
1299 }
1300 }
1301 data_set
1302 }
1303
1304 pub(crate) fn get_form_dataset(
1306 &self,
1307 submitter: Option<FormSubmitterElement>,
1308 encoding: Option<&'static Encoding>,
1309 can_gc: CanGc,
1310 ) -> Option<Vec<FormDatum>> {
1311 if self.constructing_entry_list.get() {
1313 return None;
1314 }
1315
1316 self.constructing_entry_list.set(true);
1318
1319 let ret = self.get_unclean_dataset(submitter, encoding, can_gc);
1321
1322 let window = self.owner_window();
1323
1324 let form_data = FormData::new(Some(ret), &window.global(), can_gc);
1326
1327 let event = FormDataEvent::new(
1329 &window,
1330 atom!("formdata"),
1331 EventBubbles::Bubbles,
1332 EventCancelable::NotCancelable,
1333 &form_data,
1334 can_gc,
1335 );
1336
1337 event
1338 .upcast::<Event>()
1339 .fire(self.upcast::<EventTarget>(), can_gc);
1340
1341 self.constructing_entry_list.set(false);
1343
1344 Some(form_data.datums())
1346 }
1347
1348 pub(crate) fn reset(&self, _reset_method_flag: ResetFrom, can_gc: CanGc) {
1350 if self.marked_for_reset.get() {
1352 return;
1353 } else {
1354 self.marked_for_reset.set(true);
1355 }
1356
1357 let reset = self
1361 .upcast::<EventTarget>()
1362 .fire_bubbling_cancelable_event(atom!("reset"), can_gc);
1363 if !reset {
1364 return;
1365 }
1366
1367 let controls: Vec<_> = self
1368 .controls
1369 .borrow()
1370 .iter()
1371 .map(|c| c.as_rooted())
1372 .collect();
1373
1374 for child in controls {
1375 child.reset(can_gc);
1376 }
1377 self.marked_for_reset.set(false);
1378 }
1379
1380 fn add_control<T: ?Sized + FormControl>(&self, control: &T, can_gc: CanGc) {
1381 {
1382 let root = self.upcast::<Element>().root_element();
1383 let root = root.upcast::<Node>();
1384 let mut controls = self.controls.borrow_mut();
1385 controls.insert_pre_order(control.to_element(), root);
1386 }
1387 self.update_validity(can_gc);
1388 }
1389
1390 fn remove_control<T: ?Sized + FormControl>(&self, control: &T, can_gc: CanGc) {
1391 {
1392 let control = control.to_element();
1393 let mut controls = self.controls.borrow_mut();
1394 controls
1395 .iter()
1396 .position(|c| &**c == control)
1397 .map(|idx| controls.remove(idx));
1398
1399 let mut past_names_map = self.past_names_map.borrow_mut();
1404 past_names_map.0.retain(|_k, v| v.0 != control);
1405 }
1406 self.update_validity(can_gc);
1407 }
1408}
1409
1410impl Element {
1411 pub(crate) fn is_resettable(&self) -> bool {
1412 let NodeTypeId::Element(ElementTypeId::HTMLElement(element_type)) =
1413 self.upcast::<Node>().type_id()
1414 else {
1415 return false;
1416 };
1417 matches!(
1418 element_type,
1419 HTMLElementTypeId::HTMLInputElement |
1420 HTMLElementTypeId::HTMLSelectElement |
1421 HTMLElementTypeId::HTMLTextAreaElement |
1422 HTMLElementTypeId::HTMLOutputElement |
1423 HTMLElementTypeId::HTMLElement
1424 )
1425 }
1426
1427 pub(crate) fn reset(&self, can_gc: CanGc) {
1428 if !self.is_resettable() {
1429 return;
1430 }
1431
1432 if let Some(input_element) = self.downcast::<HTMLInputElement>() {
1433 input_element.reset(can_gc);
1434 } else if let Some(select_element) = self.downcast::<HTMLSelectElement>() {
1435 select_element.reset();
1436 } else if let Some(textarea_element) = self.downcast::<HTMLTextAreaElement>() {
1437 textarea_element.reset(can_gc);
1438 } else if let Some(output_element) = self.downcast::<HTMLOutputElement>() {
1439 output_element.reset(can_gc);
1440 } else if let Some(html_element) = self.downcast::<HTMLElement>() {
1441 if html_element.is_form_associated_custom_element() {
1442 ScriptThread::enqueue_callback_reaction(
1443 html_element.upcast::<Element>(),
1444 CallbackReaction::FormReset,
1445 None,
1446 )
1447 }
1448 }
1449 }
1450}
1451
1452#[derive(Clone, JSTraceable, MallocSizeOf)]
1453pub(crate) enum FormDatumValue {
1454 File(DomRoot<File>),
1455 String(DOMString),
1456}
1457
1458#[derive(Clone, JSTraceable, MallocSizeOf)]
1459pub(crate) struct FormDatum {
1460 pub(crate) ty: DOMString,
1461 pub(crate) name: DOMString,
1462 pub(crate) value: FormDatumValue,
1463}
1464
1465impl FormDatum {
1466 pub(crate) fn replace_value(&self, charset: &str) -> String {
1467 if self.name.to_ascii_lowercase() == "_charset_" && self.ty == "hidden" {
1468 return charset.to_string();
1469 }
1470
1471 match self.value {
1472 FormDatumValue::File(ref f) => String::from(f.name().clone()),
1473 FormDatumValue::String(ref s) => String::from(s.clone()),
1474 }
1475 }
1476}
1477
1478#[derive(Clone, Copy, MallocSizeOf)]
1479pub(crate) enum FormEncType {
1480 TextPlain,
1481 UrlEncoded,
1482 MultipartFormData,
1483}
1484
1485#[derive(Clone, Copy, MallocSizeOf)]
1486pub(crate) enum FormMethod {
1487 Get,
1488 Post,
1489 Dialog,
1490}
1491
1492#[derive(Clone, Copy, MallocSizeOf)]
1494pub(crate) enum FormSubmitterElement<'a> {
1495 Form(&'a HTMLFormElement),
1496 Input(&'a HTMLInputElement),
1497 Button(&'a HTMLButtonElement),
1498 }
1501
1502impl FormSubmitterElement<'_> {
1503 fn action(&self) -> DOMString {
1504 match *self {
1505 FormSubmitterElement::Form(form) => form.Action(),
1506 FormSubmitterElement::Input(input_element) => input_element.get_form_attribute(
1507 &local_name!("formaction"),
1508 |i| i.FormAction(),
1509 |f| f.Action(),
1510 ),
1511 FormSubmitterElement::Button(button_element) => button_element.get_form_attribute(
1512 &local_name!("formaction"),
1513 |i| i.FormAction(),
1514 |f| f.Action(),
1515 ),
1516 }
1517 }
1518
1519 fn enctype(&self) -> FormEncType {
1520 let attr = match *self {
1521 FormSubmitterElement::Form(form) => form.Enctype(),
1522 FormSubmitterElement::Input(input_element) => input_element.get_form_attribute(
1523 &local_name!("formenctype"),
1524 |i| i.FormEnctype(),
1525 |f| f.Enctype(),
1526 ),
1527 FormSubmitterElement::Button(button_element) => button_element.get_form_attribute(
1528 &local_name!("formenctype"),
1529 |i| i.FormEnctype(),
1530 |f| f.Enctype(),
1531 ),
1532 };
1533 match_domstring_ascii!(attr,
1536 "multipart/form-data" => FormEncType::MultipartFormData,
1537 "text/plain" => FormEncType::TextPlain,
1538 _ => FormEncType::UrlEncoded,
1539 )
1540 }
1541
1542 fn method(&self) -> FormMethod {
1543 let attr = match *self {
1544 FormSubmitterElement::Form(form) => form.Method(),
1545 FormSubmitterElement::Input(input_element) => input_element.get_form_attribute(
1546 &local_name!("formmethod"),
1547 |i| i.FormMethod(),
1548 |f| f.Method(),
1549 ),
1550 FormSubmitterElement::Button(button_element) => button_element.get_form_attribute(
1551 &local_name!("formmethod"),
1552 |i| i.FormMethod(),
1553 |f| f.Method(),
1554 ),
1555 };
1556 match_domstring_ascii!(attr,
1557 "dialog" => FormMethod::Dialog,
1558 "post" => FormMethod::Post,
1559 _ => FormMethod::Get,
1560 )
1561 }
1562
1563 fn target(&self) -> DOMString {
1564 match *self {
1565 FormSubmitterElement::Form(form) => form.Target(),
1566 FormSubmitterElement::Input(input_element) => input_element.get_form_attribute(
1567 &local_name!("formtarget"),
1568 |i| i.FormTarget(),
1569 |f| f.Target(),
1570 ),
1571 FormSubmitterElement::Button(button_element) => button_element.get_form_attribute(
1572 &local_name!("formtarget"),
1573 |i| i.FormTarget(),
1574 |f| f.Target(),
1575 ),
1576 }
1577 }
1578
1579 fn no_validate(&self, _form_owner: &HTMLFormElement) -> bool {
1580 match *self {
1581 FormSubmitterElement::Form(form) => form.NoValidate(),
1582 FormSubmitterElement::Input(input_element) => input_element.get_form_boolean_attribute(
1583 &local_name!("formnovalidate"),
1584 |i| i.FormNoValidate(),
1585 |f| f.NoValidate(),
1586 ),
1587 FormSubmitterElement::Button(button_element) => button_element
1588 .get_form_boolean_attribute(
1589 &local_name!("formnovalidate"),
1590 |i| i.FormNoValidate(),
1591 |f| f.NoValidate(),
1592 ),
1593 }
1594 }
1595
1596 pub(crate) fn is_submit_button(&self) -> bool {
1598 match *self {
1599 FormSubmitterElement::Input(input_element) => input_element.is_submit_button(),
1602 FormSubmitterElement::Button(button_element) => button_element.is_submit_button(),
1604 _ => false,
1605 }
1606 }
1607
1608 pub(crate) fn form_owner(&self) -> Option<DomRoot<HTMLFormElement>> {
1610 match *self {
1611 FormSubmitterElement::Button(button_el) => button_el.form_owner(),
1612 FormSubmitterElement::Input(input_el) => input_el.form_owner(),
1613 _ => None,
1614 }
1615 }
1616}
1617
1618pub(crate) trait FormControl: DomObject<ReflectorType = ()> {
1619 fn form_owner(&self) -> Option<DomRoot<HTMLFormElement>>;
1620
1621 fn set_form_owner(&self, form: Option<&HTMLFormElement>);
1622
1623 fn to_element(&self) -> ∈
1624
1625 fn is_listed(&self) -> bool {
1626 true
1627 }
1628
1629 fn set_form_owner_from_parser(&self, form: &HTMLFormElement, can_gc: CanGc) {
1634 let elem = self.to_element();
1635 let node = elem.upcast::<Node>();
1636 node.set_flag(NodeFlags::PARSER_ASSOCIATED_FORM_OWNER, true);
1637 form.add_control(self, can_gc);
1638 self.set_form_owner(Some(form));
1639 }
1640
1641 fn reset_form_owner(&self, can_gc: CanGc) {
1643 let elem = self.to_element();
1644 let node = elem.upcast::<Node>();
1645 let old_owner = self.form_owner();
1646 let has_form_id = elem.has_attribute(&local_name!("form"));
1647 let nearest_form_ancestor = node
1648 .ancestors()
1649 .find_map(DomRoot::downcast::<HTMLFormElement>);
1650
1651 if old_owner.is_some() &&
1653 !(self.is_listed() && has_form_id) &&
1654 nearest_form_ancestor == old_owner
1655 {
1656 return;
1657 }
1658
1659 let new_owner = if self.is_listed() && has_form_id && elem.is_connected() {
1660 let doc = node.owner_document();
1662 let form_id = elem.get_string_attribute(&local_name!("form"));
1663 doc.GetElementById(form_id)
1664 .and_then(DomRoot::downcast::<HTMLFormElement>)
1665 } else {
1666 nearest_form_ancestor
1668 };
1669
1670 if old_owner != new_owner {
1671 if let Some(o) = old_owner {
1672 o.remove_control(self, can_gc);
1673 }
1674 if let Some(ref new_owner) = new_owner {
1675 new_owner.add_control(self, can_gc);
1676 }
1677 if let Some(html_elem) = elem.downcast::<HTMLElement>() {
1679 if html_elem.is_form_associated_custom_element() {
1680 ScriptThread::enqueue_callback_reaction(
1681 elem,
1682 CallbackReaction::FormAssociated(
1683 new_owner.as_ref().map(|form| DomRoot::from_ref(&**form)),
1684 ),
1685 None,
1686 )
1687 }
1688 }
1689 self.set_form_owner(new_owner.as_deref());
1690 }
1691 }
1692
1693 fn form_attribute_mutated(&self, mutation: AttributeMutation, can_gc: CanGc) {
1695 match mutation {
1696 AttributeMutation::Set(..) => {
1697 self.register_if_necessary();
1698 },
1699 AttributeMutation::Removed => {
1700 self.unregister_if_necessary();
1701 },
1702 }
1703
1704 self.reset_form_owner(can_gc);
1705 }
1706
1707 fn register_if_necessary(&self) {
1709 let elem = self.to_element();
1710 let form_id = elem.get_string_attribute(&local_name!("form"));
1711 let node = elem.upcast::<Node>();
1712
1713 if self.is_listed() && !form_id.is_empty() && node.is_connected() {
1714 node.owner_document()
1715 .register_form_id_listener(form_id, self);
1716 }
1717 }
1718
1719 fn unregister_if_necessary(&self) {
1720 let elem = self.to_element();
1721 let form_id = elem.get_string_attribute(&local_name!("form"));
1722
1723 if self.is_listed() && !form_id.is_empty() {
1724 elem.owner_document()
1725 .unregister_form_id_listener(form_id, self);
1726 }
1727 }
1728
1729 fn bind_form_control_to_tree(&self, can_gc: CanGc) {
1731 let elem = self.to_element();
1732 let node = elem.upcast::<Node>();
1733
1734 let must_skip_reset = node.get_flag(NodeFlags::PARSER_ASSOCIATED_FORM_OWNER);
1739 node.set_flag(NodeFlags::PARSER_ASSOCIATED_FORM_OWNER, false);
1740
1741 if !must_skip_reset {
1742 self.form_attribute_mutated(
1743 AttributeMutation::Set(None, AttributeMutationReason::Directly),
1744 can_gc,
1745 );
1746 }
1747 }
1748
1749 fn unbind_form_control_from_tree(&self, can_gc: CanGc) {
1751 let elem = self.to_element();
1752 let has_form_attr = elem.has_attribute(&local_name!("form"));
1753 let same_subtree = self
1754 .form_owner()
1755 .is_none_or(|form| elem.is_in_same_home_subtree(&*form));
1756
1757 self.unregister_if_necessary();
1758
1759 if !same_subtree || (self.is_listed() && has_form_attr) {
1765 self.reset_form_owner(can_gc);
1766 }
1767 }
1768
1769 fn get_form_attribute<InputFn, OwnerFn>(
1770 &self,
1771 attr: &LocalName,
1772 input: InputFn,
1773 owner: OwnerFn,
1774 ) -> DOMString
1775 where
1776 InputFn: Fn(&Self) -> DOMString,
1777 OwnerFn: Fn(&HTMLFormElement) -> DOMString,
1778 Self: Sized,
1779 {
1780 if self.to_element().has_attribute(attr) {
1781 input(self)
1782 } else {
1783 self.form_owner().map_or(DOMString::new(), |t| owner(&t))
1784 }
1785 }
1786
1787 fn get_form_boolean_attribute<InputFn, OwnerFn>(
1788 &self,
1789 attr: &LocalName,
1790 input: InputFn,
1791 owner: OwnerFn,
1792 ) -> bool
1793 where
1794 InputFn: Fn(&Self) -> bool,
1795 OwnerFn: Fn(&HTMLFormElement) -> bool,
1796 Self: Sized,
1797 {
1798 if self.to_element().has_attribute(attr) {
1799 input(self)
1800 } else {
1801 self.form_owner().is_some_and(|t| owner(&t))
1802 }
1803 }
1804
1805 fn is_candidate_for_constraint_validation(&self) -> bool {
1807 let element = self.to_element();
1808 let html_element = element.downcast::<HTMLElement>();
1809 if let Some(html_element) = html_element {
1810 html_element.is_submittable_element() || element.is_instance_validatable()
1811 } else {
1812 false
1813 }
1814 }
1815
1816 }
1819
1820impl VirtualMethods for HTMLFormElement {
1821 fn super_type(&self) -> Option<&dyn VirtualMethods> {
1822 Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods)
1823 }
1824
1825 fn unbind_from_tree(&self, context: &UnbindContext, can_gc: CanGc) {
1826 self.super_type().unwrap().unbind_from_tree(context, can_gc);
1827
1828 rooted_vec!(let mut to_reset);
1831 to_reset.extend(
1832 self.controls
1833 .borrow()
1834 .iter()
1835 .filter(|c| !c.is_in_same_home_subtree(self))
1836 .cloned(),
1837 );
1838
1839 for control in to_reset.iter() {
1840 control
1841 .as_maybe_form_control()
1842 .expect("Element must be a form control")
1843 .reset_form_owner(can_gc);
1844 }
1845 }
1846
1847 fn parse_plain_attribute(&self, name: &LocalName, value: DOMString) -> AttrValue {
1848 match name {
1849 &local_name!("rel") => AttrValue::from_serialized_tokenlist(value.into()),
1850 _ => self
1851 .super_type()
1852 .unwrap()
1853 .parse_plain_attribute(name, value),
1854 }
1855 }
1856
1857 fn attribute_mutated(&self, attr: &Attr, mutation: AttributeMutation, can_gc: CanGc) {
1858 self.super_type()
1859 .unwrap()
1860 .attribute_mutated(attr, mutation, can_gc);
1861
1862 match *attr.local_name() {
1863 local_name!("rel") | local_name!("rev") => {
1864 self.relations
1865 .set(LinkRelations::for_element(self.upcast()));
1866 },
1867 _ => {},
1868 }
1869 }
1870
1871 fn bind_to_tree(&self, context: &BindContext, can_gc: CanGc) {
1872 if let Some(s) = self.super_type() {
1873 s.bind_to_tree(context, can_gc);
1874 }
1875
1876 self.relations
1877 .set(LinkRelations::for_element(self.upcast()));
1878 }
1879}
1880
1881pub(crate) trait FormControlElementHelpers {
1882 fn as_maybe_form_control(&self) -> Option<&dyn FormControl>;
1883}
1884
1885impl FormControlElementHelpers for Element {
1886 fn as_maybe_form_control(&self) -> Option<&dyn FormControl> {
1887 let node = self.upcast::<Node>();
1888
1889 match node.type_id() {
1890 NodeTypeId::Element(ElementTypeId::HTMLElement(
1891 HTMLElementTypeId::HTMLButtonElement,
1892 )) => Some(self.downcast::<HTMLButtonElement>().unwrap() as &dyn FormControl),
1893 NodeTypeId::Element(ElementTypeId::HTMLElement(
1894 HTMLElementTypeId::HTMLFieldSetElement,
1895 )) => Some(self.downcast::<HTMLFieldSetElement>().unwrap() as &dyn FormControl),
1896 NodeTypeId::Element(ElementTypeId::HTMLElement(
1897 HTMLElementTypeId::HTMLImageElement,
1898 )) => Some(self.downcast::<HTMLImageElement>().unwrap() as &dyn FormControl),
1899 NodeTypeId::Element(ElementTypeId::HTMLElement(
1900 HTMLElementTypeId::HTMLInputElement,
1901 )) => Some(self.downcast::<HTMLInputElement>().unwrap() as &dyn FormControl),
1902 NodeTypeId::Element(ElementTypeId::HTMLElement(
1903 HTMLElementTypeId::HTMLLabelElement,
1904 )) => Some(self.downcast::<HTMLLabelElement>().unwrap() as &dyn FormControl),
1905 NodeTypeId::Element(ElementTypeId::HTMLElement(
1906 HTMLElementTypeId::HTMLLegendElement,
1907 )) => Some(self.downcast::<HTMLLegendElement>().unwrap() as &dyn FormControl),
1908 NodeTypeId::Element(ElementTypeId::HTMLElement(
1909 HTMLElementTypeId::HTMLObjectElement,
1910 )) => Some(self.downcast::<HTMLObjectElement>().unwrap() as &dyn FormControl),
1911 NodeTypeId::Element(ElementTypeId::HTMLElement(
1912 HTMLElementTypeId::HTMLOutputElement,
1913 )) => Some(self.downcast::<HTMLOutputElement>().unwrap() as &dyn FormControl),
1914 NodeTypeId::Element(ElementTypeId::HTMLElement(
1915 HTMLElementTypeId::HTMLSelectElement,
1916 )) => Some(self.downcast::<HTMLSelectElement>().unwrap() as &dyn FormControl),
1917 NodeTypeId::Element(ElementTypeId::HTMLElement(
1918 HTMLElementTypeId::HTMLTextAreaElement,
1919 )) => Some(self.downcast::<HTMLTextAreaElement>().unwrap() as &dyn FormControl),
1920 _ => self.downcast::<HTMLElement>().and_then(|elem| {
1921 if elem.is_form_associated_custom_element() {
1922 Some(elem as &dyn FormControl)
1923 } else {
1924 None
1925 }
1926 }),
1927 }
1928 }
1929}
1930
1931pub(crate) fn encode_multipart_form_data(
1933 form_data: &mut [FormDatum],
1934 boundary: String,
1935 encoding: &'static Encoding,
1936) -> Vec<u8> {
1937 let mut result = vec![];
1938
1939 fn clean_crlf(s: &DOMString) -> DOMString {
1941 let mut buf = "".to_owned();
1942 let mut prev = ' ';
1943 for ch in s.str().chars() {
1944 match ch {
1945 '\n' if prev != '\r' => {
1946 buf.push('\r');
1947 buf.push('\n');
1948 },
1949 '\n' => {
1950 buf.push('\n');
1951 },
1952 _ if prev == '\r' => {
1955 buf.push('\r');
1956 buf.push('\n');
1957 buf.push(ch);
1958 },
1959 _ => buf.push(ch),
1960 };
1961 prev = ch;
1962 }
1963 if prev == '\r' {
1965 buf.push('\n');
1966 }
1967 DOMString::from(buf)
1968 }
1969
1970 for entry in form_data.iter_mut() {
1971 entry.name = clean_crlf(&entry.name);
1973
1974 if let FormDatumValue::String(ref s) = entry.value {
1977 entry.value = FormDatumValue::String(clean_crlf(s));
1978 }
1979
1980 let mut boundary_bytes = format!("--{}\r\n", boundary).into_bytes();
1985 result.append(&mut boundary_bytes);
1986
1987 match entry.value {
1990 FormDatumValue::String(ref s) => {
1991 let content_disposition = format!("form-data; name=\"{}\"", entry.name);
1992 let mut bytes =
1993 format!("Content-Disposition: {content_disposition}\r\n\r\n{s}\r\n",)
1994 .into_bytes();
1995 result.append(&mut bytes);
1996 },
1997 FormDatumValue::File(ref f) => {
1998 let charset = encoding.name();
1999 let extra = if charset.to_lowercase() == "utf-8" {
2000 format!("filename=\"{}\"", String::from(f.name().str()))
2001 } else {
2002 format!(
2003 "filename*=\"{}\"''{}",
2004 charset,
2005 http_percent_encode(&f.name().as_bytes())
2006 )
2007 };
2008
2009 let content_disposition = format!("form-data; name=\"{}\"; {}", entry.name, extra);
2010 let content_type: Mime = f
2012 .upcast::<Blob>()
2013 .Type()
2014 .parse()
2015 .unwrap_or(mime::TEXT_PLAIN);
2016 let mut type_bytes = format!(
2017 "Content-Disposition: {}\r\nContent-Type: {}\r\n\r\n",
2018 content_disposition, content_type
2019 )
2020 .into_bytes();
2021 result.append(&mut type_bytes);
2022
2023 let mut bytes = f.upcast::<Blob>().get_bytes().unwrap_or(vec![]);
2024
2025 result.append(&mut bytes);
2026 result.extend(b"\r\n");
2027 },
2028 }
2029 }
2030
2031 let mut boundary_bytes = format!("--{boundary}--\r\n").into_bytes();
2032 result.append(&mut boundary_bytes);
2033
2034 result
2035}
2036
2037pub(crate) fn generate_boundary() -> String {
2039 let i1 = random::<u32>();
2040 let i2 = random::<u32>();
2041
2042 format!("---------------------------{0}{1}", i1, i2)
2043}