1use std::borrow::{Cow, ToOwned};
6use std::cell::Cell;
7
8use content_security_policy::sandboxing_directive::SandboxingFlagSet;
9use dom_struct::dom_struct;
10use encoding_rs::{Encoding, UTF_8};
11use headers::{ContentType, HeaderMapExt};
12use html5ever::{LocalName, Prefix, local_name};
13use http::Method;
14use js::context::{JSContext, NoGC};
15use js::rust::HandleObject;
16use mime::{self, Mime};
17use net_traits::request::Referrer;
18use rand::random;
19use rustc_hash::FxBuildHasher;
20use script_bindings::cell::DomRefCell;
21use script_bindings::codegen::GenericBindings::DocumentFragmentBinding::DocumentFragmentMethods;
22use script_bindings::dom::UnrootedDom;
23use script_bindings::match_domstring_ascii;
24use script_bindings::reflector::DomObject;
25use servo_constellation_traits::{LoadData, LoadOrigin, NavigationHistoryBehavior};
26use style::attr::AttrValue;
27use style::str::split_html_space_chars;
28use stylo_atoms::Atom;
29use stylo_dom::ElementState;
30
31use crate::body::Extractable;
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;
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::attributes::storage::AttrRef;
59use crate::dom::element::{AttributeMutation, AttributeMutationReason, Element};
60use crate::dom::event::{Event, EventBubbles, EventCancelable};
61use crate::dom::eventtarget::EventTarget;
62use crate::dom::file::File;
63use crate::dom::formdata::FormData;
64use crate::dom::formdataevent::FormDataEvent;
65use crate::dom::html::form_controls::htmlinputelement::HTMLInputElement;
66use crate::dom::html::form_controls::input_type::InputType;
67use crate::dom::html::htmlbuttonelement::HTMLButtonElement;
68use crate::dom::html::htmlcollection::CollectionFilter;
69use crate::dom::html::htmldatalistelement::HTMLDataListElement;
70use crate::dom::html::htmlelement::HTMLElement;
71use crate::dom::html::htmlfieldsetelement::HTMLFieldSetElement;
72use crate::dom::html::htmlformcontrolscollection::HTMLFormControlsCollection;
73use crate::dom::html::htmlimageelement::HTMLImageElement;
74use crate::dom::html::htmllegendelement::HTMLLegendElement;
75use crate::dom::html::htmlobjectelement::HTMLObjectElement;
76use crate::dom::html::htmloutputelement::HTMLOutputElement;
77use crate::dom::html::htmlselectelement::HTMLSelectElement;
78use crate::dom::html::htmltextareaelement::HTMLTextAreaElement;
79use crate::dom::node::virtualmethods::VirtualMethods;
80use crate::dom::node::{Node, NodeFlags, NodeTraits, UnbindContext, VecPreOrderInsertionHelper};
81use crate::dom::nodelist::{NodeList, RadioListMode};
82use crate::dom::radionodelist::RadioNodeList;
83use crate::dom::submitevent::SubmitEvent;
84use crate::dom::types::{DocumentFragment, HTMLIFrameElement};
85use crate::dom::window::Window;
86use crate::links::{LinkRelations, get_element_target, valid_navigable_target_name_or_keyword};
87use crate::navigation::navigate;
88use crate::script_thread::ScriptThread;
89
90#[dom_struct]
92pub(crate) struct HTMLFormElement {
93 htmlelement: HTMLElement,
94 marked_for_reset: Cell<bool>,
95 constructing_entry_list: Cell<bool>,
97 elements: DomOnceCell<HTMLFormControlsCollection>,
98 controls: DomRefCell<Vec<Dom<Element>>>,
99
100 #[expect(clippy::type_complexity)]
102 past_names_map:
103 DomRefCell<HashMapTracedValues<Atom, (Dom<Element>, NoTrace<usize>), FxBuildHasher>>,
104
105 current_name_generation: Cell<usize>,
107
108 firing_submission_events: Cell<bool>,
109 rel_list: MutNullableDom<DOMTokenList>,
110
111 planned_navigation: Cell<usize>,
113
114 #[no_trace]
116 relations: Cell<LinkRelations>,
117}
118
119impl HTMLFormElement {
120 fn new_inherited(
121 local_name: LocalName,
122 prefix: Option<Prefix>,
123 document: &Document,
124 ) -> HTMLFormElement {
125 HTMLFormElement {
126 htmlelement: HTMLElement::new_inherited_with_state(
127 ElementState::VALID,
128 local_name,
129 prefix,
130 document,
131 ),
132 marked_for_reset: Cell::new(false),
133 constructing_entry_list: Cell::new(false),
134 elements: Default::default(),
135 controls: DomRefCell::new(Vec::new()),
136 past_names_map: DomRefCell::new(HashMapTracedValues::new_fx()),
137 current_name_generation: Cell::new(0),
138 firing_submission_events: Cell::new(false),
139 rel_list: Default::default(),
140 planned_navigation: Default::default(),
141 relations: Cell::new(LinkRelations::empty()),
142 }
143 }
144
145 pub(crate) fn new(
146 cx: &mut JSContext,
147 local_name: LocalName,
148 prefix: Option<Prefix>,
149 document: &Document,
150 proto: Option<HandleObject>,
151 ) -> DomRoot<HTMLFormElement> {
152 Node::reflect_node_with_proto(
153 cx,
154 Box::new(HTMLFormElement::new_inherited(local_name, prefix, document)),
155 document,
156 proto,
157 )
158 }
159
160 fn filter_for_radio_list(mode: RadioListMode, child: &Element, name: &Atom) -> bool {
161 if let Some(child) = child.downcast::<Element>() {
162 match mode {
163 RadioListMode::ControlsExceptImageInputs => {
164 if child
165 .downcast::<HTMLElement>()
166 .is_some_and(|c| c.is_listed_element()) &&
167 (child.get_id().is_some_and(|i| i == *name) ||
168 child.get_name().is_some_and(|n| n == *name))
169 {
170 if let Some(inp) = child.downcast::<HTMLInputElement>() {
171 return !matches!(*inp.input_type(), InputType::Image(_));
173 } else {
174 return true;
176 }
177 }
178 return false;
179 },
180 RadioListMode::Images => {
181 return child.is::<HTMLImageElement>() &&
182 (child.get_id().is_some_and(|i| i == *name) ||
183 child.get_name().is_some_and(|n| n == *name));
184 },
185 }
186 }
187 false
188 }
189
190 pub(crate) fn nth_for_radio_list<'a>(
191 &self,
192 no_gc: &'a NoGC,
193 index: u32,
194 mode: RadioListMode,
195 name: &Atom,
196 ) -> Option<UnrootedDom<'a, 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| UnrootedDom::upcast(UnrootedDom::from_dom(n.clone(), no_gc)))
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, cx: &mut JSContext, value: DOMString) {
258 self.SetEnctype(cx, 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, cx: &mut JSContext) {
296 self.submit(
297 cx,
298 SubmittedFrom::FromForm,
299 FormSubmitterElement::Form(self),
300 );
301 }
302
303 fn RequestSubmit(&self, cx: &mut JSContext, submitter: Option<&HTMLElement>) -> 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(cx, SubmittedFrom::NotFromForm, submitter);
361 Ok(())
362 }
363
364 fn Reset(&self, cx: &mut JSContext) {
366 self.reset(cx, ResetFrom::FromForm);
367 }
368
369 fn Elements(&self, cx: &mut JSContext) -> DomRoot<HTMLFormControlsCollection> {
371 #[derive(JSTraceable, MallocSizeOf)]
372 #[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
373 struct ElementsFilter {
374 form: Dom<HTMLFormElement>,
375 }
376 impl CollectionFilter for ElementsFilter {
377 fn filter<'a>(&self, elem: &'a Element, _root: &'a Node) -> bool {
378 let form_owner = match elem.upcast::<Node>().type_id() {
379 NodeTypeId::Element(ElementTypeId::HTMLElement(t)) => match t {
380 HTMLElementTypeId::HTMLButtonElement => {
381 elem.downcast::<HTMLButtonElement>().unwrap().form_owner()
382 },
383 HTMLElementTypeId::HTMLFieldSetElement => {
384 elem.downcast::<HTMLFieldSetElement>().unwrap().form_owner()
385 },
386 HTMLElementTypeId::HTMLInputElement => {
387 let input_elem = elem.downcast::<HTMLInputElement>().unwrap();
388 if matches!(*input_elem.input_type(), InputType::Image(_)) {
389 return false;
390 }
391 input_elem.form_owner()
392 },
393 HTMLElementTypeId::HTMLObjectElement => {
394 elem.downcast::<HTMLObjectElement>().unwrap().form_owner()
395 },
396 HTMLElementTypeId::HTMLOutputElement => {
397 elem.downcast::<HTMLOutputElement>().unwrap().form_owner()
398 },
399 HTMLElementTypeId::HTMLSelectElement => {
400 elem.downcast::<HTMLSelectElement>().unwrap().form_owner()
401 },
402 HTMLElementTypeId::HTMLTextAreaElement => {
403 elem.downcast::<HTMLTextAreaElement>().unwrap().form_owner()
404 },
405 HTMLElementTypeId::HTMLElement => {
406 let html_element = elem.downcast::<HTMLElement>().unwrap();
407 if html_element.is_form_associated_custom_element() {
408 html_element.form_owner()
409 } else {
410 return false;
411 }
412 },
413 _ => {
414 debug_assert!(
415 !elem.downcast::<HTMLElement>().unwrap().is_listed_element()
416 );
417 return false;
418 },
419 },
420 _ => return false,
421 };
422
423 form_owner
424 .as_deref()
425 .is_some_and(|form_owner| self.form == form_owner)
426 }
427 }
428 DomRoot::from_ref(self.elements.init_once(|| {
429 let window = self.owner_window();
430 HTMLFormControlsCollection::new(
431 cx,
432 &window,
433 self,
434 Box::new(ElementsFilter {
435 form: Dom::from_ref(self),
436 }),
437 )
438 }))
439 }
440
441 fn Length(&self, cx: &mut JSContext) -> u32 {
443 self.Elements(cx).Length(cx)
444 }
445
446 fn IndexedGetter(&self, cx: &mut JSContext, index: u32) -> Option<DomRoot<Element>> {
448 let elements = self.Elements(cx);
449 elements.IndexedGetter(cx, index)
450 }
451
452 fn NamedGetter(&self, cx: &mut JSContext, name: DOMString) -> Option<RadioNodeListOrElement> {
454 let window = self.owner_window();
455
456 let name = Atom::from(name);
457
458 let mut candidates =
460 RadioNodeList::new_controls_except_image_inputs(cx, &window, self, &name);
461 let mut candidates_length = candidates.Length(cx);
462
463 if candidates_length == 0 {
465 candidates = RadioNodeList::new_images(cx, &window, self, &name);
466 candidates_length = candidates.Length(cx);
467 }
468
469 let mut past_names_map = self.past_names_map.borrow_mut();
470
471 if candidates_length == 0 {
473 if past_names_map.contains_key(&name) {
474 return Some(RadioNodeListOrElement::Element(DomRoot::from_ref(
475 &*past_names_map.get(&name).unwrap().0,
476 )));
477 }
478 return None;
479 }
480
481 if candidates_length > 1 {
483 return Some(RadioNodeListOrElement::RadioNodeList(candidates));
484 }
485
486 let element_node = candidates.upcast::<NodeList>().Item(cx, 0).unwrap();
489 past_names_map.insert(
490 name,
491 (
492 Dom::from_ref(element_node.downcast::<Element>().unwrap()),
493 NoTrace(self.current_name_generation.get() + 1),
494 ),
495 );
496 self.current_name_generation
497 .set(self.current_name_generation.get() + 1);
498
499 Some(RadioNodeListOrElement::Element(DomRoot::from_ref(
501 element_node.downcast::<Element>().unwrap(),
502 )))
503 }
504
505 fn SetRel(&self, cx: &mut JSContext, rel: DOMString) {
507 self.upcast::<Element>()
508 .set_tokenlist_attribute(cx, &local_name!("rel"), rel);
509 }
510
511 fn RelList(&self, cx: &mut JSContext) -> DomRoot<DOMTokenList> {
513 self.rel_list.or_init(|| {
514 DOMTokenList::new(
515 cx,
516 self.upcast(),
517 &local_name!("rel"),
518 Some(vec![
519 Atom::from("noopener"),
520 Atom::from("noreferrer"),
521 Atom::from("opener"),
522 ]),
523 )
524 })
525 }
526
527 fn SupportedPropertyNames(&self, _: &NoGC) -> Vec<DOMString> {
529 #[derive(Debug, Eq, Ord, PartialEq, PartialOrd)]
531 enum SourcedNameSource {
532 Id,
533 Name,
534 Past(usize),
535 }
536
537 impl SourcedNameSource {
538 fn is_past(&self) -> bool {
539 matches!(self, SourcedNameSource::Past(..))
540 }
541 }
542
543 struct SourcedName {
544 name: Atom,
545 element: DomRoot<Element>,
546 source: SourcedNameSource,
547 }
548
549 let mut sourced_names_vec: Vec<SourcedName> = Vec::new();
550
551 for child in self.controls.borrow().iter() {
553 if child
554 .downcast::<HTMLElement>()
555 .is_some_and(|c| c.is_listed_element())
556 {
557 if let Some(id_atom) = child.get_id() {
558 let entry = SourcedName {
559 name: id_atom,
560 element: DomRoot::from_ref(child),
561 source: SourcedNameSource::Id,
562 };
563 sourced_names_vec.push(entry);
564 }
565 if let Some(name_atom) = child.get_name() {
566 let entry = SourcedName {
567 name: name_atom,
568 element: DomRoot::from_ref(child),
569 source: SourcedNameSource::Name,
570 };
571 sourced_names_vec.push(entry);
572 }
573 }
574 }
575
576 for child in self.controls.borrow().iter() {
578 if child.is::<HTMLImageElement>() {
579 if let Some(id_atom) = child.get_id() {
580 let entry = SourcedName {
581 name: id_atom,
582 element: DomRoot::from_ref(child),
583 source: SourcedNameSource::Id,
584 };
585 sourced_names_vec.push(entry);
586 }
587 if let Some(name_atom) = child.get_name() {
588 let entry = SourcedName {
589 name: name_atom,
590 element: DomRoot::from_ref(child),
591 source: SourcedNameSource::Name,
592 };
593 sourced_names_vec.push(entry);
594 }
595 }
596 }
597
598 let past_names_map = self.past_names_map.borrow();
600 for (key, val) in past_names_map.iter() {
601 let entry = SourcedName {
602 name: key.clone(),
603 element: DomRoot::from_ref(&*val.0),
604 source: SourcedNameSource::Past(self.current_name_generation.get() - val.1.0),
605 };
606 sourced_names_vec.push(entry);
607 }
608
609 sourced_names_vec.sort_by(|a, b| {
621 if a.element
622 .upcast::<Node>()
623 .CompareDocumentPosition(b.element.upcast::<Node>()) ==
624 0
625 {
626 if a.source.is_past() && b.source.is_past() {
627 b.source.cmp(&a.source)
628 } else {
629 a.source.cmp(&b.source)
630 }
631 } else if a
632 .element
633 .upcast::<Node>()
634 .CompareDocumentPosition(b.element.upcast::<Node>()) &
635 NodeConstants::DOCUMENT_POSITION_FOLLOWING ==
636 NodeConstants::DOCUMENT_POSITION_FOLLOWING
637 {
638 std::cmp::Ordering::Less
639 } else {
640 std::cmp::Ordering::Greater
641 }
642 });
643
644 sourced_names_vec.retain(|sn| !sn.name.to_string().is_empty());
646
647 let mut names_vec: Vec<DOMString> = Vec::new();
649 for elem in sourced_names_vec.iter() {
650 if !names_vec.iter().any(|name| *name == *elem.name) {
651 names_vec.push(DOMString::from(&*elem.name));
652 }
653 }
654
655 names_vec
656 }
657
658 fn CheckValidity(&self, cx: &mut JSContext) -> bool {
660 self.static_validation(cx).is_ok()
661 }
662
663 fn ReportValidity(&self, cx: &mut JSContext) -> bool {
665 self.interactive_validation(cx).is_ok()
666 }
667}
668
669#[derive(Clone, Copy, MallocSizeOf, PartialEq)]
670pub(crate) enum SubmittedFrom {
671 FromForm,
672 NotFromForm,
673}
674
675#[derive(Clone, Copy, MallocSizeOf)]
676pub(crate) enum ResetFrom {
677 FromForm,
678 NotFromForm,
679}
680
681impl HTMLFormElement {
682 fn pick_encoding(&self) -> &'static Encoding {
684 if self
686 .upcast::<Element>()
687 .has_attribute(&local_name!("accept-charset"))
688 {
689 let input = self
691 .upcast::<Element>()
692 .get_string_attribute(&local_name!("accept-charset"));
693
694 let input = input.str();
696 let mut candidate_encodings =
697 split_html_space_chars(&input).filter_map(|c| Encoding::for_label(c.as_bytes()));
698
699 return candidate_encodings.next().unwrap_or(UTF_8);
701 }
702
703 self.owner_document().encoding()
705 }
706
707 pub(crate) fn update_validity(&self, cx: &mut JSContext) {
708 let is_any_invalid = self
709 .controls
710 .borrow()
711 .iter()
712 .any(|control| control.is_invalid(cx, false));
713
714 self.upcast::<Element>()
715 .set_state(ElementState::VALID, !is_any_invalid);
716 self.upcast::<Element>()
717 .set_state(ElementState::INVALID, is_any_invalid);
718 }
719
720 pub(crate) fn submit(
722 &self,
723 cx: &mut JSContext,
724 submit_method_flag: SubmittedFrom,
725 submitter: FormSubmitterElement,
726 ) {
727 if self.upcast::<Element>().cannot_navigate() {
729 return;
730 }
731
732 if self.constructing_entry_list.get() {
734 return;
735 }
736 let doc = self.owner_document();
738
739 if doc.has_active_sandboxing_flag(SandboxingFlagSet::SANDBOXED_FORMS_BROWSING_CONTEXT_FLAG)
742 {
743 return;
744 }
745
746 let base = doc.base_url();
747 if submit_method_flag == SubmittedFrom::NotFromForm {
750 if self.firing_submission_events.get() {
752 return;
753 }
754 self.firing_submission_events.set(true);
756 if !submitter.no_validate(self) && self.interactive_validation(cx).is_err() {
758 self.firing_submission_events.set(false);
759 return;
760 }
761 let submitter_button = match submitter {
765 FormSubmitterElement::Form(f) => {
766 if f == self {
767 None
768 } else {
769 Some(f.upcast::<HTMLElement>())
770 }
771 },
772 FormSubmitterElement::Input(i) => Some(i.upcast::<HTMLElement>()),
773 FormSubmitterElement::Button(b) => Some(b.upcast::<HTMLElement>()),
774 };
775
776 let event = SubmitEvent::new(
778 cx,
779 self.global().as_window(),
780 atom!("submit"),
781 true,
782 true,
783 submitter_button,
784 );
785 let event = event.upcast::<Event>();
786 event.fire(cx, self.upcast::<EventTarget>());
787
788 self.firing_submission_events.set(false);
790 if event.DefaultPrevented() {
792 return;
793 }
794 if self.upcast::<Element>().cannot_navigate() {
796 return;
797 }
798 }
799
800 let encoding = self.pick_encoding();
802
803 let mut form_data = match self.get_form_dataset(cx, Some(submitter), Some(encoding)) {
805 Some(form_data) => form_data,
806 None => return,
807 };
808
809 if self.upcast::<Element>().cannot_navigate() {
811 return;
812 }
813
814 let method = submitter.method();
816 let mut action = submitter.action();
821
822 if action.is_empty() {
824 action = DOMString::from(base.as_str());
825 }
826 let action_components = match doc.encoding_parse_a_url(&action.str()) {
828 Ok(url) => url,
829 Err(_) => return,
831 };
832 let scheme = action_components.scheme().to_owned();
834 let enctype = submitter.enctype();
836
837 let form_target_attribute = submitter.target();
840 let form_target = if submitter.is_submit_button() &&
841 valid_navigable_target_name_or_keyword(&form_target_attribute)
842 {
843 Some(form_target_attribute)
844 } else {
845 None
847 };
848 let form_owner = submitter.form_owner();
850 let form = form_owner.as_deref().unwrap_or(self);
851 let target = get_element_target(form.upcast::<Element>(), form_target);
852
853 let noopener = self.relations.get().get_element_noopener(target.as_ref());
855
856 let source = doc.browsing_context().unwrap();
859 let (maybe_chosen, _new) =
860 source.choose_browsing_context(cx, target.unwrap_or_default(), noopener);
861
862 let Some(chosen) = maybe_chosen else {
863 return;
865 };
866 let target_document = match chosen.document() {
867 Some(doc) => doc,
868 None => return,
869 };
870
871 let history_handling = if doc == target_document && !doc.completely_loaded() {
875 NavigationHistoryBehavior::Replace
876 } else {
877 NavigationHistoryBehavior::Auto
878 };
879
880 let target_window = target_document.window();
881 let mut load_data = LoadData::new(
882 LoadOrigin::Script(doc.origin().snapshot()),
883 action_components,
884 target_document.about_base_url(),
885 None,
886 target_window.as_global_scope().get_referrer(),
887 target_document.get_referrer_policy(),
888 Some(target_window.as_global_scope().is_secure_context()),
889 Some(target_document.insecure_requests_policy()),
890 target_document.has_trustworthy_ancestor_origin(),
891 target_document.creation_sandboxing_flag_set_considering_parent_iframe(),
892 );
893
894 match (&*scheme, method) {
898 (_, FormMethod::Dialog) => {
899 },
902 ("http", FormMethod::Get) | ("https", FormMethod::Get) | ("data", FormMethod::Get) => {
904 load_data
905 .headers
906 .typed_insert(ContentType::from(mime::APPLICATION_WWW_FORM_URLENCODED));
907 self.mutate_action_url(&mut form_data, load_data, target_window, history_handling);
908 },
909 ("http", FormMethod::Post) | ("https", FormMethod::Post) => {
911 load_data.method = Method::POST;
912 self.submit_entity_body(
913 cx,
914 &mut form_data,
915 load_data,
916 enctype,
917 encoding,
918 target_window,
919 history_handling,
920 );
921 },
922 ("file", _) |
924 ("about", _) |
925 ("data", FormMethod::Post) |
926 ("ftp", _) |
927 ("javascript", _) => {
928 self.plan_to_navigate(load_data, target_window, history_handling);
929 },
930 ("mailto", FormMethod::Post) => {
931 },
934 ("mailto", FormMethod::Get) => {
935 },
938 _ => (),
939 }
940 }
941
942 fn mutate_action_url(
944 &self,
945 form_data: &mut [FormDatum],
946 mut load_data: LoadData,
947 target: &Window,
948 history_handling: NavigationHistoryBehavior,
949 ) {
950 self.set_url_query_pairs(
951 &mut load_data.url,
952 form_data.iter().map(|field| {
953 (
954 field.name.normalize_crlf(),
955 field.replace_value().normalize_crlf(),
956 )
957 }),
958 );
959
960 self.plan_to_navigate(load_data, target, history_handling);
961 }
962
963 #[allow(clippy::too_many_arguments)]
965 fn submit_entity_body(
966 &self,
967 cx: &mut JSContext,
968 form_data: &mut [FormDatum],
969 mut load_data: LoadData,
970 enctype: FormEncType,
971 encoding: &'static Encoding,
972 target: &Window,
973 history_handling: NavigationHistoryBehavior,
974 ) {
975 let boundary = generate_boundary();
976 let bytes = match enctype {
977 FormEncType::UrlEncoded => {
978 load_data
979 .headers
980 .typed_insert(ContentType::from(mime::APPLICATION_WWW_FORM_URLENCODED));
981
982 let mut url = load_data.url.clone();
983 self.set_url_query_pairs(
984 &mut url,
985 form_data.iter().map(|field| {
989 (
990 field.name.normalize_crlf(),
991 field.replace_value().normalize_crlf(),
992 )
993 }),
994 );
995
996 url.query().unwrap_or("").to_string().into_bytes()
997 },
998 FormEncType::MultipartFormData => {
999 let mime: Mime = format!("multipart/form-data; boundary={}", boundary)
1000 .parse()
1001 .unwrap();
1002 load_data.headers.typed_insert(ContentType::from(mime));
1003 encode_multipart_form_data(form_data, boundary, encoding)
1004 },
1005 FormEncType::TextPlain => {
1006 load_data
1007 .headers
1008 .typed_insert(ContentType::from(mime::TEXT_PLAIN));
1009 let pairs = form_data.iter().map(|field| {
1013 (
1014 field.name.normalize_crlf(),
1015 field.replace_value().normalize_crlf(),
1016 )
1017 });
1018 let body = encode_plaintext(pairs);
1021 encode_with_html_fallback(&body, encoding).into_owned()
1023 },
1024 };
1025
1026 let global = self.global();
1027
1028 let request_body = bytes
1029 .extract(cx, &global, false)
1030 .expect("Couldn't extract body.")
1031 .into_net_request_body(cx)
1032 .0;
1033 load_data.data = Some(request_body);
1034
1035 self.plan_to_navigate(load_data, target, history_handling);
1036 }
1037
1038 fn set_url_query_pairs<T>(
1039 &self,
1040 url: &mut servo_url::ServoUrl,
1041 pairs: impl Iterator<Item = (T, String)>,
1042 ) where
1043 T: AsRef<str>,
1044 {
1045 let encoding = self.pick_encoding();
1046 url.as_mut_url()
1047 .query_pairs_mut()
1048 .encoding_override(Some(&|s| encode_with_html_fallback(s, encoding)))
1049 .clear()
1050 .extend_pairs(pairs);
1051 }
1052
1053 fn plan_to_navigate(
1055 &self,
1056 mut load_data: LoadData,
1057 target: &Window,
1058 history_handling: NavigationHistoryBehavior,
1059 ) {
1060 let elem = self.upcast::<Element>();
1065 let referrer = match elem.get_attribute_string_value(&local_name!("rel")) {
1066 Some(link_types) if link_types.contains("noreferrer") => Referrer::NoReferrer,
1067 _ => target.as_global_scope().get_referrer(),
1068 };
1069
1070 self.planned_navigation
1073 .set(self.planned_navigation.get().wrapping_add(1));
1074 let planned_navigation = self.planned_navigation.get();
1075
1076 let ongoing_navigation = target.set_ongoing_navigation();
1092
1093 let referrer_policy = target.Document().get_referrer_policy();
1094 load_data.creator_pipeline_id = Some(target.pipeline_id());
1095 load_data.referrer = referrer;
1096 load_data.referrer_policy = referrer_policy;
1097
1098 if let Some(window_proxy) = target.undiscarded_window_proxy() &&
1101 let Some(frame) = window_proxy
1102 .frame_element()
1103 .and_then(|e| e.downcast::<HTMLIFrameElement>())
1104 {
1105 frame.note_pending_navigation()
1106 }
1107
1108 let form = Trusted::new(self);
1111 let window = Trusted::new(target);
1112 let task = task!(navigate_to_form_planned_navigation: move |cx| {
1113 if planned_navigation != form.root().planned_navigation.get() {
1117 return;
1118 }
1119
1120 if ongoing_navigation != window.root().ongoing_navigation() {
1123 return;
1124 }
1125
1126 navigate(
1128 cx,
1129 &window.root(),
1130 history_handling,
1131 false,
1132 load_data,
1133 )
1134 });
1135
1136 target
1141 .global()
1142 .task_manager()
1143 .dom_manipulation_task_source()
1144 .queue(task)
1145 }
1146
1147 fn interactive_validation(&self, cx: &mut JSContext) -> Result<(), ()> {
1150 let unhandled_invalid_controls = match self.static_validation(cx) {
1155 Ok(()) => return Ok(()),
1156 Err(err) => err,
1157 };
1158
1159 let mut first = true;
1162
1163 for elem in unhandled_invalid_controls {
1164 if let Some(validatable) = elem.as_maybe_validatable() {
1165 error!("Validation error: {}", validatable.validation_message(cx));
1166 }
1167 if first && let Some(html_elem) = elem.downcast::<HTMLElement>() {
1168 html_elem.Focus(cx, &FocusOptions::default());
1175 first = false;
1176 }
1177 }
1178
1179 Err(())
1183 }
1184
1185 fn static_validation(&self, cx: &mut JSContext) -> Result<(), Vec<DomRoot<Element>>> {
1188 let invalid_controls = self
1190 .controls
1191 .borrow()
1192 .iter()
1193 .filter_map(|field| {
1194 if let Some(element) = field.downcast::<Element>() {
1195 if element.is_invalid(cx, true) {
1196 Some(DomRoot::from_ref(element))
1197 } else {
1198 None
1199 }
1200 } else {
1201 None
1202 }
1203 })
1204 .collect::<Vec<DomRoot<Element>>>();
1205 if invalid_controls.is_empty() {
1207 return Ok(());
1208 }
1209 let unhandled_invalid_controls = invalid_controls
1211 .into_iter()
1212 .filter_map(|field| {
1213 let not_canceled = field
1216 .upcast::<EventTarget>()
1217 .fire_cancelable_event(cx, atom!("invalid"));
1218 if not_canceled {
1220 return Some(field);
1221 }
1222 None
1223 })
1224 .collect::<Vec<DomRoot<Element>>>();
1225 Err(unhandled_invalid_controls)
1227 }
1228
1229 fn get_unclean_dataset(
1234 &self,
1235 cx: &mut JSContext,
1236 submitter: Option<FormSubmitterElement>,
1237 encoding: Option<&'static Encoding>,
1238 ) -> Vec<FormDatum> {
1239 let mut data_set = Vec::new();
1240 for child in self.controls.borrow().iter() {
1241 if child.disabled_state() {
1243 continue;
1244 }
1245 let child = child.upcast::<Node>();
1246
1247 if child.ancestors().any(|a| a.is::<HTMLDataListElement>()) {
1249 continue;
1250 }
1251 if let NodeTypeId::Element(ElementTypeId::HTMLElement(element)) = child.type_id() {
1252 match element {
1253 HTMLElementTypeId::HTMLInputElement => {
1254 let input = child.downcast::<HTMLInputElement>().unwrap();
1255 let (ref mut form_datums, should_continue) =
1256 input.form_datums(submitter, encoding);
1257 data_set.append(form_datums);
1258 if should_continue {
1259 continue;
1260 }
1261 },
1262 HTMLElementTypeId::HTMLButtonElement => {
1263 let button = child.downcast::<HTMLButtonElement>().unwrap();
1264 if let Some(datum) = button.form_datum(submitter) {
1265 data_set.push(datum);
1266 }
1267 },
1268 HTMLElementTypeId::HTMLObjectElement => {
1269 },
1271 HTMLElementTypeId::HTMLSelectElement => {
1272 let select = child.downcast::<HTMLSelectElement>().unwrap();
1273 select.push_form_data(cx.no_gc(), &mut data_set);
1274 },
1275 HTMLElementTypeId::HTMLTextAreaElement => {
1276 let textarea = child.downcast::<HTMLTextAreaElement>().unwrap();
1277 let name = textarea.Name();
1278 if !name.is_empty() {
1279 data_set.push(FormDatum {
1280 ty: textarea.Type(),
1281 name,
1282 value: FormDatumValue::String(textarea.Value()),
1283 });
1284 }
1285 },
1286 HTMLElementTypeId::HTMLElement => {
1287 let custom = child.downcast::<HTMLElement>().unwrap();
1288 if custom.is_form_associated_custom_element() {
1289 let internals = custom.upcast::<Element>().ensure_element_internals(cx);
1291 internals.perform_entry_construction(&mut data_set);
1292 }
1294 },
1295 _ => (),
1296 }
1297 }
1298
1299 let child_element = child.downcast::<Element>().unwrap();
1301 let dirname = child_element.get_string_attribute(&local_name!("dirname"));
1302
1303 let is_input_auto_directionality_form_associated_element = child_element
1309 .downcast::<HTMLInputElement>()
1310 .is_some_and(|input| input.is_auto_directionality_form_associated_element());
1311 let is_textarea_element = child_element.is::<HTMLTextAreaElement>();
1312 if !dirname.is_empty() &&
1313 (is_input_auto_directionality_form_associated_element || is_textarea_element)
1314 {
1315 let dir = DOMString::from(child_element.directionality());
1318
1319 data_set.push(FormDatum {
1321 ty: DOMString::from("string"),
1322 name: dirname,
1323 value: FormDatumValue::String(dir),
1324 });
1325 }
1326 }
1327 data_set
1328 }
1329
1330 pub(crate) fn get_form_dataset(
1332 &self,
1333 cx: &mut JSContext,
1334 submitter: Option<FormSubmitterElement>,
1335 encoding: Option<&'static Encoding>,
1336 ) -> Option<Vec<FormDatum>> {
1337 if self.constructing_entry_list.get() {
1339 return None;
1340 }
1341
1342 self.constructing_entry_list.set(true);
1344
1345 let ret = self.get_unclean_dataset(cx, submitter, encoding);
1347
1348 let window = self.owner_window();
1349
1350 let form_data = FormData::new(cx, Some(ret), &window.global());
1352
1353 let event = FormDataEvent::new(
1355 cx,
1356 &window,
1357 atom!("formdata"),
1358 EventBubbles::Bubbles,
1359 EventCancelable::NotCancelable,
1360 &form_data,
1361 );
1362
1363 event
1364 .upcast::<Event>()
1365 .fire(cx, self.upcast::<EventTarget>());
1366
1367 self.constructing_entry_list.set(false);
1369
1370 Some(form_data.datums())
1372 }
1373
1374 pub(crate) fn reset(&self, cx: &mut JSContext, _reset_method_flag: ResetFrom) {
1376 if self.marked_for_reset.get() {
1378 return;
1379 } else {
1380 self.marked_for_reset.set(true);
1381 }
1382
1383 let reset = self
1387 .upcast::<EventTarget>()
1388 .fire_bubbling_cancelable_event(cx, atom!("reset"));
1389 if !reset {
1390 return;
1391 }
1392
1393 let controls: Vec<_> = self
1394 .controls
1395 .borrow()
1396 .iter()
1397 .map(|c| c.as_rooted())
1398 .collect();
1399
1400 for child in controls {
1401 child.reset(cx);
1402 }
1403 self.marked_for_reset.set(false);
1404 }
1405
1406 fn add_control<T: ?Sized + FormControl>(&self, cx: &mut JSContext, control: &T) {
1407 {
1408 let root = self.upcast::<Element>().root_element();
1409 let root = root.upcast::<Node>();
1410 let mut controls = self.controls.borrow_mut();
1411
1412 let control_element = control.to_element();
1418 if control_element.upcast::<Node>().is_in_a_document_tree() {
1419 controls.insert_pre_order(control_element, root);
1420 } else {
1421 controls.push(Dom::from_ref(control_element));
1422 }
1423 }
1424 self.update_validity(cx);
1425 }
1426
1427 fn remove_control<T: ?Sized + FormControl>(&self, cx: &mut JSContext, control: &T) {
1428 {
1429 let control = control.to_element();
1430 let mut controls = self.controls.borrow_mut();
1431 controls
1432 .iter()
1433 .position(|c| &**c == control)
1434 .map(|idx| controls.remove(idx));
1435
1436 let mut past_names_map = self.past_names_map.borrow_mut();
1441 past_names_map.0.retain(|_k, v| v.0 != control);
1442 }
1443 self.update_validity(cx);
1444 }
1445}
1446
1447impl Element {
1448 pub(crate) fn is_resettable(&self) -> bool {
1449 let NodeTypeId::Element(ElementTypeId::HTMLElement(element_type)) =
1450 self.upcast::<Node>().type_id()
1451 else {
1452 return false;
1453 };
1454 matches!(
1455 element_type,
1456 HTMLElementTypeId::HTMLInputElement |
1457 HTMLElementTypeId::HTMLSelectElement |
1458 HTMLElementTypeId::HTMLTextAreaElement |
1459 HTMLElementTypeId::HTMLOutputElement |
1460 HTMLElementTypeId::HTMLElement
1461 )
1462 }
1463
1464 pub(crate) fn reset(&self, cx: &mut JSContext) {
1465 if !self.is_resettable() {
1466 return;
1467 }
1468
1469 if let Some(input_element) = self.downcast::<HTMLInputElement>() {
1470 input_element.reset(cx);
1471 } else if let Some(select_element) = self.downcast::<HTMLSelectElement>() {
1472 select_element.reset(cx);
1473 } else if let Some(textarea_element) = self.downcast::<HTMLTextAreaElement>() {
1474 textarea_element.reset(cx);
1475 } else if let Some(output_element) = self.downcast::<HTMLOutputElement>() {
1476 output_element.reset(cx);
1477 } else if let Some(html_element) = self.downcast::<HTMLElement>() &&
1478 html_element.is_form_associated_custom_element()
1479 {
1480 ScriptThread::enqueue_callback_reaction(
1481 cx,
1482 html_element.upcast::<Element>(),
1483 CallbackReaction::FormReset,
1484 None,
1485 )
1486 }
1487 }
1488}
1489
1490#[derive(JSTraceable, MallocSizeOf)]
1491#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
1492pub(crate) enum FormDatumValueUnrooted {
1493 File(Dom<File>),
1494 String(DOMString),
1495}
1496
1497#[derive(JSTraceable, MallocSizeOf)]
1498#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
1499pub(crate) struct FormDatumUnrooted {
1500 pub(crate) ty: DOMString,
1501 pub(crate) name: DOMString,
1502 pub(crate) value: FormDatumValueUnrooted,
1503}
1504
1505impl FormDatumUnrooted {
1506 pub(crate) fn root(&self) -> FormDatum {
1507 FormDatum {
1508 ty: self.ty.clone(),
1509 name: self.name.clone(),
1510 value: match &self.value {
1511 FormDatumValueUnrooted::File(file) => FormDatumValue::File(file.as_rooted()),
1512 FormDatumValueUnrooted::String(s) => FormDatumValue::String(s.clone()),
1513 },
1514 }
1515 }
1516}
1517
1518impl From<FormDatum> for FormDatumUnrooted {
1519 fn from(value: FormDatum) -> Self {
1520 Self {
1521 ty: value.ty,
1522 name: value.name,
1523 value: match value.value {
1524 FormDatumValue::File(file) => FormDatumValueUnrooted::File(file.as_traced()),
1525 FormDatumValue::String(s) => FormDatumValueUnrooted::String(s),
1526 },
1527 }
1528 }
1529}
1530
1531#[derive(JSTraceable, MallocSizeOf)]
1532pub(crate) enum FormDatumValue {
1533 File(DomRoot<File>),
1534 String(DOMString),
1535}
1536
1537#[derive(JSTraceable, MallocSizeOf)]
1538pub(crate) struct FormDatum {
1539 pub(crate) ty: DOMString,
1540 pub(crate) name: DOMString,
1541 pub(crate) value: FormDatumValue,
1542}
1543
1544impl FormDatum {
1545 pub(crate) fn replace_value(&self) -> &DOMString {
1546 match self.value {
1547 FormDatumValue::File(ref f) => f.name(),
1548 FormDatumValue::String(ref s) => s,
1549 }
1550 }
1551}
1552
1553#[derive(Clone, Copy, MallocSizeOf)]
1554pub(crate) enum FormEncType {
1555 TextPlain,
1556 UrlEncoded,
1557 MultipartFormData,
1558}
1559
1560#[derive(Clone, Copy, MallocSizeOf)]
1561pub(crate) enum FormMethod {
1562 Get,
1563 Post,
1564 Dialog,
1565}
1566
1567#[derive(Clone, Copy, MallocSizeOf)]
1569pub(crate) enum FormSubmitterElement<'a> {
1570 Form(&'a HTMLFormElement),
1571 Input(&'a HTMLInputElement),
1572 Button(&'a HTMLButtonElement),
1573 }
1576
1577impl FormSubmitterElement<'_> {
1578 fn action(&self) -> DOMString {
1580 match *self {
1581 FormSubmitterElement::Form(form) => form.Action(),
1582 FormSubmitterElement::Input(input_element) => input_element
1583 .to_element()
1584 .get_nullable_string_attribute(&local_name!("formaction"))
1585 .unwrap_or_else(|| {
1586 input_element
1587 .form_owner()
1588 .map(|form| form.Action())
1589 .unwrap_or_default()
1590 }),
1591 FormSubmitterElement::Button(button_element) => button_element
1592 .to_element()
1593 .get_nullable_string_attribute(&local_name!("formaction"))
1594 .unwrap_or_else(|| {
1595 button_element
1596 .form_owner()
1597 .map(|form| form.Action())
1598 .unwrap_or_default()
1599 }),
1600 }
1601 }
1602
1603 fn enctype(&self) -> FormEncType {
1604 let attr = match *self {
1605 FormSubmitterElement::Form(form) => form.Enctype(),
1606 FormSubmitterElement::Input(input_element) => input_element.get_form_attribute(
1607 &local_name!("formenctype"),
1608 |i| i.FormEnctype(),
1609 |f| f.Enctype(),
1610 ),
1611 FormSubmitterElement::Button(button_element) => button_element.get_form_attribute(
1612 &local_name!("formenctype"),
1613 |i| i.FormEnctype(),
1614 |f| f.Enctype(),
1615 ),
1616 };
1617 match_domstring_ascii!(attr,
1620 "multipart/form-data" => FormEncType::MultipartFormData,
1621 "text/plain" => FormEncType::TextPlain,
1622 _ => FormEncType::UrlEncoded,
1623 )
1624 }
1625
1626 fn method(&self) -> FormMethod {
1627 let attr = match *self {
1628 FormSubmitterElement::Form(form) => form.Method(),
1629 FormSubmitterElement::Input(input_element) => input_element.get_form_attribute(
1630 &local_name!("formmethod"),
1631 |i| i.FormMethod(),
1632 |f| f.Method(),
1633 ),
1634 FormSubmitterElement::Button(button_element) => button_element.get_form_attribute(
1635 &local_name!("formmethod"),
1636 |i| i.FormMethod(),
1637 |f| f.Method(),
1638 ),
1639 };
1640 match_domstring_ascii!(attr,
1641 "dialog" => FormMethod::Dialog,
1642 "post" => FormMethod::Post,
1643 _ => FormMethod::Get,
1644 )
1645 }
1646
1647 fn target(&self) -> DOMString {
1648 match *self {
1649 FormSubmitterElement::Form(form) => form.Target(),
1650 FormSubmitterElement::Input(input_element) => input_element.get_form_attribute(
1651 &local_name!("formtarget"),
1652 |i| i.FormTarget(),
1653 |f| f.Target(),
1654 ),
1655 FormSubmitterElement::Button(button_element) => button_element.get_form_attribute(
1656 &local_name!("formtarget"),
1657 |i| i.FormTarget(),
1658 |f| f.Target(),
1659 ),
1660 }
1661 }
1662
1663 fn no_validate(&self, _form_owner: &HTMLFormElement) -> bool {
1664 match *self {
1665 FormSubmitterElement::Form(form) => form.NoValidate(),
1666 FormSubmitterElement::Input(input_element) => input_element.get_form_boolean_attribute(
1667 &local_name!("formnovalidate"),
1668 |i| i.FormNoValidate(),
1669 |f| f.NoValidate(),
1670 ),
1671 FormSubmitterElement::Button(button_element) => button_element
1672 .get_form_boolean_attribute(
1673 &local_name!("formnovalidate"),
1674 |i| i.FormNoValidate(),
1675 |f| f.NoValidate(),
1676 ),
1677 }
1678 }
1679
1680 pub(crate) fn is_submit_button(&self) -> bool {
1682 match *self {
1683 FormSubmitterElement::Input(input_element) => input_element.is_submit_button(),
1686 FormSubmitterElement::Button(button_element) => button_element.is_submit_button(),
1688 _ => false,
1689 }
1690 }
1691
1692 pub(crate) fn form_owner(&self) -> Option<DomRoot<HTMLFormElement>> {
1694 match *self {
1695 FormSubmitterElement::Button(button_el) => button_el.form_owner(),
1696 FormSubmitterElement::Input(input_el) => input_el.form_owner(),
1697 _ => None,
1698 }
1699 }
1700}
1701
1702pub(crate) trait FormControl: DomObject<ReflectorType = ()> + NodeTraits {
1703 fn form_owner(&self) -> Option<DomRoot<HTMLFormElement>>;
1704 fn set_form_owner(&self, cx: &mut JSContext, form: Option<&HTMLFormElement>);
1705 fn to_html_element(&self) -> &HTMLElement;
1706
1707 fn to_element(&self) -> &Element {
1708 self.to_html_element().upcast::<Element>()
1709 }
1710
1711 fn is_listed(&self) -> bool {
1712 self.to_html_element().is_listed_element()
1713 }
1714
1715 fn set_form_owner_from_parser(&self, cx: &mut JSContext, form: &HTMLFormElement) {
1720 let elem = self.to_element();
1721 let node = elem.upcast::<Node>();
1722 node.set_flag(NodeFlags::PARSER_ASSOCIATED_FORM_OWNER, true);
1723 form.add_control(cx, self);
1724 self.set_form_owner(cx, Some(form));
1725 }
1726
1727 fn reset_form_owner(&self, cx: &mut JSContext) {
1729 let elem = self.to_element();
1730 let node = elem.upcast::<Node>();
1731 let old_owner = self.form_owner();
1732 let has_form_id = elem.has_attribute(&local_name!("form"));
1733 let nearest_form_ancestor = node
1734 .ancestors()
1735 .find_map(DomRoot::downcast::<HTMLFormElement>);
1736
1737 if old_owner.is_some() &&
1739 !(self.is_listed() && has_form_id) &&
1740 nearest_form_ancestor == old_owner
1741 {
1742 return;
1743 }
1744
1745 let new_owner = if self.is_listed() && has_form_id && elem.is_connected() {
1747 let form_id = elem.get_string_attribute(&local_name!("form"));
1751 let first_relevant_element = if let Some(shadow_root) = self.containing_shadow_root() {
1752 shadow_root
1753 .upcast::<DocumentFragment>()
1754 .GetElementById(cx, form_id)
1755 } else {
1756 node.owner_document().GetElementById(cx, form_id)
1757 };
1758
1759 first_relevant_element.and_then(DomRoot::downcast::<HTMLFormElement>)
1760 } else {
1761 nearest_form_ancestor
1763 };
1764
1765 if old_owner != new_owner {
1766 if let Some(o) = old_owner {
1767 o.remove_control(cx, self);
1768 }
1769 if let Some(ref new_owner) = new_owner {
1770 new_owner.add_control(cx, self);
1771 }
1772 if let Some(html_elem) = elem.downcast::<HTMLElement>() &&
1774 html_elem.is_form_associated_custom_element()
1775 {
1776 ScriptThread::enqueue_callback_reaction(
1777 cx,
1778 elem,
1779 CallbackReaction::FormAssociated(
1780 new_owner.as_ref().map(|form| DomRoot::from_ref(&**form)),
1781 ),
1782 None,
1783 )
1784 }
1785 self.set_form_owner(cx, new_owner.as_deref());
1786 }
1787 }
1788
1789 fn form_attribute_mutated(&self, cx: &mut JSContext, mutation: AttributeMutation) {
1791 match mutation {
1792 AttributeMutation::Set(..) => {
1793 self.register_if_necessary();
1794 },
1795 AttributeMutation::Removed => {
1796 self.unregister_if_necessary();
1797 },
1798 }
1799
1800 self.reset_form_owner(cx);
1801 }
1802
1803 fn register_if_necessary(&self) {
1805 let elem = self.to_element();
1806 let form_id = elem.get_string_attribute(&local_name!("form"));
1807 let node = elem.upcast::<Node>();
1808
1809 if self.is_listed() && !form_id.is_empty() && node.is_connected() {
1810 node.owner_document()
1811 .register_form_id_listener(form_id, self);
1812 }
1813 }
1814
1815 fn unregister_if_necessary(&self) {
1816 let elem = self.to_element();
1817 let form_id = elem.get_string_attribute(&local_name!("form"));
1818
1819 if self.is_listed() && !form_id.is_empty() {
1820 elem.owner_document()
1821 .unregister_form_id_listener(form_id, self);
1822 }
1823 }
1824
1825 fn bind_form_control_to_tree(&self, cx: &mut JSContext) {
1827 let elem = self.to_element();
1828 let node = elem.upcast::<Node>();
1829
1830 let must_skip_reset = node.get_flag(NodeFlags::PARSER_ASSOCIATED_FORM_OWNER);
1835 node.set_flag(NodeFlags::PARSER_ASSOCIATED_FORM_OWNER, false);
1836
1837 if !must_skip_reset {
1838 self.form_attribute_mutated(
1839 cx,
1840 AttributeMutation::Set(None, AttributeMutationReason::Directly),
1841 );
1842 }
1843 }
1844
1845 fn unbind_form_control_from_tree(&self, cx: &mut JSContext) {
1847 let elem = self.to_element();
1848 let has_form_attr = elem.has_attribute(&local_name!("form"));
1849 let same_subtree = self
1850 .form_owner()
1851 .is_none_or(|form| elem.is_in_same_home_subtree(&*form));
1852
1853 self.unregister_if_necessary();
1854
1855 if !same_subtree || (self.is_listed() && has_form_attr) {
1861 self.reset_form_owner(cx);
1862 }
1863 }
1864
1865 fn get_form_attribute<InputFn, OwnerFn>(
1866 &self,
1867 attr: &LocalName,
1868 input: InputFn,
1869 owner: OwnerFn,
1870 ) -> DOMString
1871 where
1872 InputFn: Fn(&Self) -> DOMString,
1873 OwnerFn: Fn(&HTMLFormElement) -> DOMString,
1874 Self: Sized,
1875 {
1876 if self.to_element().has_attribute(attr) {
1877 input(self)
1878 } else {
1879 self.form_owner().map_or(DOMString::new(), |t| owner(&t))
1880 }
1881 }
1882
1883 fn get_form_boolean_attribute<InputFn, OwnerFn>(
1884 &self,
1885 attr: &LocalName,
1886 input: InputFn,
1887 owner: OwnerFn,
1888 ) -> bool
1889 where
1890 InputFn: Fn(&Self) -> bool,
1891 OwnerFn: Fn(&HTMLFormElement) -> bool,
1892 Self: Sized,
1893 {
1894 if self.to_element().has_attribute(attr) {
1895 input(self)
1896 } else {
1897 self.form_owner().is_some_and(|t| owner(&t))
1898 }
1899 }
1900
1901 fn is_candidate_for_constraint_validation(&self) -> bool {
1903 let element = self.to_element();
1904 let html_element = element.downcast::<HTMLElement>();
1905 if let Some(html_element) = html_element {
1906 html_element.is_submittable_element() || element.is_instance_validatable()
1907 } else {
1908 false
1909 }
1910 }
1911
1912 fn moving_steps(&self, cx: &mut JSContext) {
1913 let same_subtree = self
1916 .form_owner()
1917 .is_none_or(|form| self.to_element().is_in_same_home_subtree(&*form));
1918 if !same_subtree {
1919 self.reset_form_owner(cx)
1920 }
1921 }
1922
1923 }
1926
1927impl VirtualMethods for HTMLFormElement {
1928 fn super_type(&self) -> Option<&dyn VirtualMethods> {
1929 Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods)
1930 }
1931
1932 fn unbind_from_tree(&self, cx: &mut JSContext, context: &UnbindContext) {
1933 self.super_type().unwrap().unbind_from_tree(cx, context);
1934
1935 rooted_vec!(let mut to_reset);
1938 to_reset.extend(
1939 self.controls
1940 .borrow()
1941 .iter()
1942 .filter(|c| !c.is_in_same_home_subtree(self))
1943 .cloned(),
1944 );
1945
1946 for control in to_reset.iter() {
1947 control
1948 .as_maybe_form_control()
1949 .expect("Element must be a form control")
1950 .reset_form_owner(cx);
1951 }
1952 }
1953
1954 fn parse_plain_attribute(&self, name: &LocalName, value: DOMString) -> AttrValue {
1955 match name {
1956 &local_name!("rel") => AttrValue::from_serialized_tokenlist(value.into()),
1957 _ => self
1958 .super_type()
1959 .unwrap()
1960 .parse_plain_attribute(name, value),
1961 }
1962 }
1963
1964 fn attribute_mutated(
1965 &self,
1966 cx: &mut JSContext,
1967 attr: AttrRef<'_>,
1968 mutation: AttributeMutation,
1969 ) {
1970 self.super_type()
1971 .unwrap()
1972 .attribute_mutated(cx, attr, mutation);
1973
1974 match *attr.local_name() {
1975 local_name!("rel") | local_name!("rev") => {
1976 self.relations
1977 .set(LinkRelations::for_element(self.upcast()));
1978 },
1979 _ => {},
1980 }
1981 }
1982}
1983
1984pub(crate) trait FormControlElementHelpers {
1985 fn as_maybe_form_control(&self) -> Option<&dyn FormControl>;
1986}
1987
1988impl FormControlElementHelpers for Element {
1989 fn as_maybe_form_control(&self) -> Option<&dyn FormControl> {
1990 let node = self.upcast::<Node>();
1991
1992 match node.type_id() {
1993 NodeTypeId::Element(ElementTypeId::HTMLElement(
1994 HTMLElementTypeId::HTMLButtonElement,
1995 )) => Some(self.downcast::<HTMLButtonElement>().unwrap() as &dyn FormControl),
1996 NodeTypeId::Element(ElementTypeId::HTMLElement(
1997 HTMLElementTypeId::HTMLFieldSetElement,
1998 )) => Some(self.downcast::<HTMLFieldSetElement>().unwrap() as &dyn FormControl),
1999 NodeTypeId::Element(ElementTypeId::HTMLElement(
2000 HTMLElementTypeId::HTMLImageElement,
2001 )) => Some(self.downcast::<HTMLImageElement>().unwrap() as &dyn FormControl),
2002 NodeTypeId::Element(ElementTypeId::HTMLElement(
2003 HTMLElementTypeId::HTMLInputElement,
2004 )) => Some(self.downcast::<HTMLInputElement>().unwrap() as &dyn FormControl),
2005 NodeTypeId::Element(ElementTypeId::HTMLElement(
2006 HTMLElementTypeId::HTMLLegendElement,
2007 )) => Some(self.downcast::<HTMLLegendElement>().unwrap() as &dyn FormControl),
2008 NodeTypeId::Element(ElementTypeId::HTMLElement(
2009 HTMLElementTypeId::HTMLObjectElement,
2010 )) => Some(self.downcast::<HTMLObjectElement>().unwrap() as &dyn FormControl),
2011 NodeTypeId::Element(ElementTypeId::HTMLElement(
2012 HTMLElementTypeId::HTMLOutputElement,
2013 )) => Some(self.downcast::<HTMLOutputElement>().unwrap() as &dyn FormControl),
2014 NodeTypeId::Element(ElementTypeId::HTMLElement(
2015 HTMLElementTypeId::HTMLSelectElement,
2016 )) => Some(self.downcast::<HTMLSelectElement>().unwrap() as &dyn FormControl),
2017 NodeTypeId::Element(ElementTypeId::HTMLElement(
2018 HTMLElementTypeId::HTMLTextAreaElement,
2019 )) => Some(self.downcast::<HTMLTextAreaElement>().unwrap() as &dyn FormControl),
2020 _ => self.downcast::<HTMLElement>().and_then(|elem| {
2021 if elem.is_form_associated_custom_element() {
2022 Some(elem as &dyn FormControl)
2023 } else {
2024 None
2025 }
2026 }),
2027 }
2028 }
2029}
2030
2031fn encode_plaintext(pairs: impl Iterator<Item = (String, String)>) -> String {
2033 let mut result = String::new();
2035 for (name, value) in pairs {
2037 result.push_str(&name);
2039 result.push('=');
2041 result.push_str(&value);
2043 result.push_str("\r\n");
2046 }
2047 result
2049}
2050
2051fn encode_with_html_fallback<'a>(input: &'a str, encoding: &'static Encoding) -> Cow<'a, [u8]> {
2057 encoding.encode(input).0
2058}
2059
2060pub(crate) fn encode_multipart_form_data(
2062 form_data: &mut [FormDatum],
2063 boundary: String,
2064 encoding: &'static Encoding,
2065) -> Vec<u8> {
2066 let mut result = vec![];
2067
2068 fn escape_header_bytes(input: &[u8]) -> Vec<u8> {
2075 let mut output = Vec::with_capacity(input.len());
2076 for &b in input {
2077 match b {
2078 b'\n' => output.extend(b"%0A"),
2079 b'\r' => output.extend(b"%0D"),
2080 b'"' => output.extend(b"%22"),
2081 _ => output.push(b),
2082 }
2083 }
2084 output
2085 }
2086
2087 for entry in form_data.iter_mut() {
2088 entry.name = entry.name.normalize_crlf().into();
2092
2093 if let FormDatumValue::String(ref s) = entry.value {
2098 entry.value = FormDatumValue::String(s.normalize_crlf().into());
2099 }
2100
2101 let mut boundary_bytes = format!("--{}\r\n", boundary).into_bytes();
2103 result.append(&mut boundary_bytes);
2104
2105 let name_str = &*entry.name.str();
2107 let encoded_name = encode_with_html_fallback(name_str, encoding);
2108 let escaped_name = escape_header_bytes(&encoded_name);
2110
2111 match entry.value {
2112 FormDatumValue::String(ref s) => {
2113 let value_str = &*s.str();
2115 let encoded_value = encode_with_html_fallback(value_str, encoding);
2116
2117 result.extend(b"Content-Disposition: form-data; name=\"");
2119 result.extend(&escaped_name);
2120 result.extend(b"\"\r\n\r\n");
2121 result.extend_from_slice(&encoded_value);
2122 result.extend(b"\r\n");
2123 },
2124 FormDatumValue::File(ref f) => {
2125 let filename_str = &*f.name().str();
2127 let encoded_filename = encode_with_html_fallback(filename_str, encoding);
2128 let escaped_filename = escape_header_bytes(&encoded_filename);
2130
2131 result.extend(b"Content-Disposition: form-data; name=\"");
2132 result.extend(&escaped_name);
2133 result.extend(b"\"; filename=\"");
2134 result.extend(&escaped_filename);
2135
2136 result.extend(b"\"\r\nContent-Type: ");
2138
2139 let content_type: Mime = f
2140 .upcast::<Blob>()
2141 .Type()
2142 .parse()
2143 .unwrap_or(mime::TEXT_PLAIN);
2144 result.extend(content_type.as_ref().as_bytes());
2145 result.extend(b"\r\n\r\n");
2146
2147 let mut bytes = f.upcast::<Blob>().get_bytes().unwrap_or_default();
2148
2149 result.append(&mut bytes);
2150 result.extend(b"\r\n");
2151 },
2152 }
2153 }
2154
2155 let mut boundary_bytes = format!("--{boundary}--\r\n").into_bytes();
2157 result.append(&mut boundary_bytes);
2158
2159 result
2160}
2161
2162pub(crate) fn generate_boundary() -> String {
2164 let i1 = random::<u32>();
2165 let i2 = random::<u32>();
2166
2167 format!("---------------------------{0}{1}", i1, i2)
2168}