1use std::borrow::ToOwned;
6use std::cell::Cell;
7
8use content_security_policy::sandboxing_directive::SandboxingFlagSet;
9use dom_struct::dom_struct;
10use encoding_rs::{Encoding, UTF_8};
11use headers::{ContentType, HeaderMapExt};
12use html5ever::{LocalName, Prefix, local_name};
13use http::Method;
14use js::context::JSContext;
15use js::rust::HandleObject;
16use mime::{self, Mime};
17use net_traits::http_percent_encode;
18use net_traits::request::Referrer;
19use rand::random;
20use rustc_hash::FxBuildHasher;
21use script_bindings::cell::DomRefCell;
22use script_bindings::codegen::GenericBindings::DocumentFragmentBinding::DocumentFragmentMethods;
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::AttrBinding::Attr_Binding::AttrMethods;
33use crate::dom::bindings::codegen::Bindings::BlobBinding::BlobMethods;
34use crate::dom::bindings::codegen::Bindings::DocumentBinding::DocumentMethods;
35use crate::dom::bindings::codegen::Bindings::EventBinding::EventMethods;
36use crate::dom::bindings::codegen::Bindings::HTMLButtonElementBinding::HTMLButtonElementMethods;
37use crate::dom::bindings::codegen::Bindings::HTMLElementBinding::HTMLElementMethods;
38use crate::dom::bindings::codegen::Bindings::HTMLFormControlsCollectionBinding::HTMLFormControlsCollectionMethods;
39use crate::dom::bindings::codegen::Bindings::HTMLFormElementBinding::HTMLFormElementMethods;
40use crate::dom::bindings::codegen::Bindings::HTMLInputElementBinding::HTMLInputElementMethods;
41use crate::dom::bindings::codegen::Bindings::HTMLOrSVGElementBinding::FocusOptions;
42use crate::dom::bindings::codegen::Bindings::HTMLTextAreaElementBinding::HTMLTextAreaElementMethods;
43use crate::dom::bindings::codegen::Bindings::NodeBinding::{NodeConstants, NodeMethods};
44use crate::dom::bindings::codegen::Bindings::NodeListBinding::NodeListMethods;
45use crate::dom::bindings::codegen::Bindings::RadioNodeListBinding::RadioNodeListMethods;
46use crate::dom::bindings::codegen::Bindings::WindowBinding::Window_Binding::WindowMethods;
47use crate::dom::bindings::codegen::UnionTypes::RadioNodeListOrElement;
48use crate::dom::bindings::error::{Error, Fallible};
49use crate::dom::bindings::inheritance::{Castable, ElementTypeId, HTMLElementTypeId, NodeTypeId};
50use crate::dom::bindings::refcounted::Trusted;
51use crate::dom::bindings::reflector::DomGlobal;
52use crate::dom::bindings::root::{Dom, DomOnceCell, DomRoot, MutNullableDom};
53use crate::dom::bindings::str::DOMString;
54use crate::dom::bindings::trace::{HashMapTracedValues, NoTrace};
55use crate::dom::blob::Blob;
56use crate::dom::customelementregistry::CallbackReaction;
57use crate::dom::document::Document;
58use crate::dom::domtokenlist::DOMTokenList;
59use crate::dom::element::attributes::storage::AttrRef;
60use crate::dom::element::{AttributeMutation, AttributeMutationReason, Element};
61use crate::dom::event::{Event, EventBubbles, EventCancelable};
62use crate::dom::eventtarget::EventTarget;
63use crate::dom::file::File;
64use crate::dom::formdata::FormData;
65use crate::dom::formdataevent::FormDataEvent;
66use crate::dom::html::htmlbuttonelement::HTMLButtonElement;
67use crate::dom::html::htmlcollection::CollectionFilter;
68use crate::dom::html::htmldatalistelement::HTMLDataListElement;
69use crate::dom::html::htmlelement::HTMLElement;
70use crate::dom::html::htmlfieldsetelement::HTMLFieldSetElement;
71use crate::dom::html::htmlformcontrolscollection::HTMLFormControlsCollection;
72use crate::dom::html::htmlimageelement::HTMLImageElement;
73use crate::dom::html::htmllabelelement::HTMLLabelElement;
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::html::input_element::HTMLInputElement;
80use crate::dom::input_element::input_type::InputType;
81use crate::dom::node::{Node, NodeFlags, NodeTraits, UnbindContext, VecPreOrderInsertionHelper};
82use crate::dom::nodelist::{NodeList, RadioListMode};
83use crate::dom::radionodelist::RadioNodeList;
84use crate::dom::submitevent::SubmitEvent;
85use crate::dom::types::{DocumentFragment, HTMLIFrameElement};
86use crate::dom::virtualmethods::VirtualMethods;
87use crate::dom::window::Window;
88use crate::links::{LinkRelations, get_element_target, valid_navigable_target_name_or_keyword};
89use crate::navigation::navigate;
90use crate::script_runtime::CanGc;
91use crate::script_thread::ScriptThread;
92
93#[dom_struct]
95pub(crate) struct HTMLFormElement {
96 htmlelement: HTMLElement,
97 marked_for_reset: Cell<bool>,
98 constructing_entry_list: Cell<bool>,
100 elements: DomOnceCell<HTMLFormControlsCollection>,
101 controls: DomRefCell<Vec<Dom<Element>>>,
102
103 #[expect(clippy::type_complexity)]
105 past_names_map:
106 DomRefCell<HashMapTracedValues<Atom, (Dom<Element>, NoTrace<usize>), FxBuildHasher>>,
107
108 current_name_generation: Cell<usize>,
110
111 firing_submission_events: Cell<bool>,
112 rel_list: MutNullableDom<DOMTokenList>,
113
114 planned_navigation: Cell<usize>,
116
117 #[no_trace]
119 relations: Cell<LinkRelations>,
120}
121
122impl HTMLFormElement {
123 fn new_inherited(
124 local_name: LocalName,
125 prefix: Option<Prefix>,
126 document: &Document,
127 ) -> HTMLFormElement {
128 HTMLFormElement {
129 htmlelement: HTMLElement::new_inherited_with_state(
130 ElementState::VALID,
131 local_name,
132 prefix,
133 document,
134 ),
135 marked_for_reset: Cell::new(false),
136 constructing_entry_list: Cell::new(false),
137 elements: Default::default(),
138 controls: DomRefCell::new(Vec::new()),
139 past_names_map: DomRefCell::new(HashMapTracedValues::new_fx()),
140 current_name_generation: Cell::new(0),
141 firing_submission_events: Cell::new(false),
142 rel_list: Default::default(),
143 planned_navigation: Default::default(),
144 relations: Cell::new(LinkRelations::empty()),
145 }
146 }
147
148 pub(crate) fn new(
149 cx: &mut JSContext,
150 local_name: LocalName,
151 prefix: Option<Prefix>,
152 document: &Document,
153 proto: Option<HandleObject>,
154 ) -> DomRoot<HTMLFormElement> {
155 Node::reflect_node_with_proto(
156 cx,
157 Box::new(HTMLFormElement::new_inherited(local_name, prefix, document)),
158 document,
159 proto,
160 )
161 }
162
163 fn filter_for_radio_list(mode: RadioListMode, child: &Element, name: &Atom) -> bool {
164 if let Some(child) = child.downcast::<Element>() {
165 match mode {
166 RadioListMode::ControlsExceptImageInputs => {
167 if child
168 .downcast::<HTMLElement>()
169 .is_some_and(|c| c.is_listed_element()) &&
170 (child.get_id().is_some_and(|i| i == *name) ||
171 child.get_name().is_some_and(|n| n == *name))
172 {
173 if let Some(inp) = child.downcast::<HTMLInputElement>() {
174 return !matches!(*inp.input_type(), InputType::Image(_));
176 } else {
177 return true;
179 }
180 }
181 return false;
182 },
183 RadioListMode::Images => {
184 return child.is::<HTMLImageElement>() &&
185 (child.get_id().is_some_and(|i| i == *name) ||
186 child.get_name().is_some_and(|n| n == *name));
187 },
188 }
189 }
190 false
191 }
192
193 pub(crate) fn nth_for_radio_list(
194 &self,
195 index: u32,
196 mode: RadioListMode,
197 name: &Atom,
198 ) -> Option<DomRoot<Node>> {
199 self.controls
200 .borrow()
201 .iter()
202 .filter(|n| HTMLFormElement::filter_for_radio_list(mode, n, name))
203 .nth(index as usize)
204 .map(|n| DomRoot::from_ref(n.upcast::<Node>()))
205 }
206
207 pub(crate) fn count_for_radio_list(&self, mode: RadioListMode, name: &Atom) -> u32 {
208 self.controls
209 .borrow()
210 .iter()
211 .filter(|n| HTMLFormElement::filter_for_radio_list(mode, n, name))
212 .count() as u32
213 }
214}
215
216impl HTMLFormElementMethods<crate::DomTypeHolder> for HTMLFormElement {
217 make_getter!(AcceptCharset, "accept-charset");
219
220 make_setter!(SetAcceptCharset, "accept-charset");
222
223 make_form_action_getter!(Action, "action");
225
226 make_setter!(SetAction, "action");
228
229 make_enumerated_getter!(
231 Autocomplete,
232 "autocomplete",
233 "on" | "off",
234 missing => "on",
235 invalid => "on"
236 );
237
238 make_setter!(SetAutocomplete, "autocomplete");
240
241 make_enumerated_getter!(
243 Enctype,
244 "enctype",
245 "application/x-www-form-urlencoded" | "text/plain" | "multipart/form-data",
246 missing => "application/x-www-form-urlencoded",
247 invalid => "application/x-www-form-urlencoded"
248 );
249
250 make_setter!(SetEnctype, "enctype");
252
253 fn Encoding(&self) -> DOMString {
255 self.Enctype()
256 }
257
258 fn SetEncoding(&self, cx: &mut JSContext, value: DOMString) {
260 self.SetEnctype(cx, value)
261 }
262
263 make_enumerated_getter!(
265 Method,
266 "method",
267 "get" | "post" | "dialog",
268 missing => "get",
269 invalid => "get"
270 );
271
272 make_setter!(SetMethod, "method");
274
275 make_getter!(Name, "name");
277
278 make_atomic_setter!(SetName, "name");
280
281 make_bool_getter!(NoValidate, "novalidate");
283
284 make_bool_setter!(SetNoValidate, "novalidate");
286
287 make_getter!(Target, "target");
289
290 make_setter!(SetTarget, "target");
292
293 make_getter!(Rel, "rel");
295
296 fn Submit(&self, cx: &mut JSContext) {
298 self.submit(
299 cx,
300 SubmittedFrom::FromForm,
301 FormSubmitterElement::Form(self),
302 );
303 }
304
305 fn RequestSubmit(&self, cx: &mut JSContext, submitter: Option<&HTMLElement>) -> Fallible<()> {
307 let submitter: FormSubmitterElement = match submitter {
308 Some(submitter_element) => {
309 let error_not_a_submit_button =
311 Err(Error::Type(c"submitter must be a submit button".to_owned()));
312
313 let element = match submitter_element.upcast::<Node>().type_id() {
314 NodeTypeId::Element(ElementTypeId::HTMLElement(element)) => element,
315 _ => {
316 return error_not_a_submit_button;
317 },
318 };
319
320 let submit_button = match element {
321 HTMLElementTypeId::HTMLInputElement => FormSubmitterElement::Input(
322 submitter_element
323 .downcast::<HTMLInputElement>()
324 .expect("Failed to downcast submitter elem to HTMLInputElement."),
325 ),
326 HTMLElementTypeId::HTMLButtonElement => FormSubmitterElement::Button(
327 submitter_element
328 .downcast::<HTMLButtonElement>()
329 .expect("Failed to downcast submitter elem to HTMLButtonElement."),
330 ),
331 _ => {
332 return error_not_a_submit_button;
333 },
334 };
335
336 if !submit_button.is_submit_button() {
337 return error_not_a_submit_button;
338 }
339
340 let submitters_owner = submit_button.form_owner();
341
342 let owner = match submitters_owner {
344 Some(owner) => owner,
345 None => {
346 return Err(Error::NotFound(None));
347 },
348 };
349
350 if *owner != *self {
351 return Err(Error::NotFound(None));
352 }
353
354 submit_button
355 },
356 None => {
357 FormSubmitterElement::Form(self)
359 },
360 };
361 self.submit(cx, SubmittedFrom::NotFromForm, submitter);
363 Ok(())
364 }
365
366 fn Reset(&self, cx: &mut JSContext) {
368 self.reset(cx, ResetFrom::FromForm);
369 }
370
371 fn Elements(&self, cx: &mut JSContext) -> DomRoot<HTMLFormControlsCollection> {
373 #[derive(JSTraceable, MallocSizeOf)]
374 struct ElementsFilter {
375 form: DomRoot<HTMLFormElement>,
376 }
377 impl CollectionFilter for ElementsFilter {
378 fn filter<'a>(&self, elem: &'a Element, _root: &'a Node) -> bool {
379 let form_owner = match elem.upcast::<Node>().type_id() {
380 NodeTypeId::Element(ElementTypeId::HTMLElement(t)) => match t {
381 HTMLElementTypeId::HTMLButtonElement => {
382 elem.downcast::<HTMLButtonElement>().unwrap().form_owner()
383 },
384 HTMLElementTypeId::HTMLFieldSetElement => {
385 elem.downcast::<HTMLFieldSetElement>().unwrap().form_owner()
386 },
387 HTMLElementTypeId::HTMLInputElement => {
388 let input_elem = elem.downcast::<HTMLInputElement>().unwrap();
389 if matches!(*input_elem.input_type(), InputType::Image(_)) {
390 return false;
391 }
392 input_elem.form_owner()
393 },
394 HTMLElementTypeId::HTMLObjectElement => {
395 elem.downcast::<HTMLObjectElement>().unwrap().form_owner()
396 },
397 HTMLElementTypeId::HTMLOutputElement => {
398 elem.downcast::<HTMLOutputElement>().unwrap().form_owner()
399 },
400 HTMLElementTypeId::HTMLSelectElement => {
401 elem.downcast::<HTMLSelectElement>().unwrap().form_owner()
402 },
403 HTMLElementTypeId::HTMLTextAreaElement => {
404 elem.downcast::<HTMLTextAreaElement>().unwrap().form_owner()
405 },
406 HTMLElementTypeId::HTMLElement => {
407 let html_element = elem.downcast::<HTMLElement>().unwrap();
408 if html_element.is_form_associated_custom_element() {
409 html_element.form_owner()
410 } else {
411 return false;
412 }
413 },
414 _ => {
415 debug_assert!(
416 !elem.downcast::<HTMLElement>().unwrap().is_listed_element()
417 );
418 return false;
419 },
420 },
421 _ => return false,
422 };
423
424 match form_owner {
425 Some(form_owner) => form_owner == self.form,
426 None => false,
427 }
428 }
429 }
430 DomRoot::from_ref(self.elements.init_once(|| {
431 let filter = Box::new(ElementsFilter {
432 form: DomRoot::from_ref(self),
433 });
434 let window = self.owner_window();
435 HTMLFormControlsCollection::new(cx, &window, self, filter)
436 }))
437 }
438
439 fn Length(&self, cx: &mut JSContext) -> u32 {
441 self.Elements(cx).Length()
442 }
443
444 fn IndexedGetter(&self, cx: &mut JSContext, index: u32) -> Option<DomRoot<Element>> {
446 let elements = self.Elements(cx);
447 elements.IndexedGetter(index)
448 }
449
450 fn NamedGetter(&self, cx: &mut JSContext, name: DOMString) -> Option<RadioNodeListOrElement> {
452 let window = self.owner_window();
453
454 let name = Atom::from(name);
455
456 let mut candidates =
458 RadioNodeList::new_controls_except_image_inputs(cx, &window, self, &name);
459 let mut candidates_length = candidates.Length();
460
461 if candidates_length == 0 {
463 candidates = RadioNodeList::new_images(cx, &window, self, &name);
464 candidates_length = candidates.Length();
465 }
466
467 let mut past_names_map = self.past_names_map.borrow_mut();
468
469 if candidates_length == 0 {
471 if past_names_map.contains_key(&name) {
472 return Some(RadioNodeListOrElement::Element(DomRoot::from_ref(
473 &*past_names_map.get(&name).unwrap().0,
474 )));
475 }
476 return None;
477 }
478
479 if candidates_length > 1 {
481 return Some(RadioNodeListOrElement::RadioNodeList(candidates));
482 }
483
484 let element_node = candidates.upcast::<NodeList>().Item(0).unwrap();
487 past_names_map.insert(
488 name,
489 (
490 Dom::from_ref(element_node.downcast::<Element>().unwrap()),
491 NoTrace(self.current_name_generation.get() + 1),
492 ),
493 );
494 self.current_name_generation
495 .set(self.current_name_generation.get() + 1);
496
497 Some(RadioNodeListOrElement::Element(DomRoot::from_ref(
499 element_node.downcast::<Element>().unwrap(),
500 )))
501 }
502
503 fn SetRel(&self, cx: &mut JSContext, rel: DOMString) {
505 self.upcast::<Element>()
506 .set_tokenlist_attribute(cx, &local_name!("rel"), rel);
507 }
508
509 fn RelList(&self, cx: &mut JSContext) -> DomRoot<DOMTokenList> {
511 self.rel_list.or_init(|| {
512 DOMTokenList::new(
513 cx,
514 self.upcast(),
515 &local_name!("rel"),
516 Some(vec![
517 Atom::from("noopener"),
518 Atom::from("noreferrer"),
519 Atom::from("opener"),
520 ]),
521 )
522 })
523 }
524
525 fn SupportedPropertyNames(&self) -> Vec<DOMString> {
527 #[derive(Debug, Eq, Ord, PartialEq, PartialOrd)]
529 enum SourcedNameSource {
530 Id,
531 Name,
532 Past(usize),
533 }
534
535 impl SourcedNameSource {
536 fn is_past(&self) -> bool {
537 matches!(self, SourcedNameSource::Past(..))
538 }
539 }
540
541 struct SourcedName {
542 name: Atom,
543 element: DomRoot<Element>,
544 source: SourcedNameSource,
545 }
546
547 let mut sourced_names_vec: Vec<SourcedName> = Vec::new();
548
549 for child in self.controls.borrow().iter() {
551 if child
552 .downcast::<HTMLElement>()
553 .is_some_and(|c| c.is_listed_element())
554 {
555 if let Some(id_atom) = child.get_id() {
556 let entry = SourcedName {
557 name: id_atom,
558 element: DomRoot::from_ref(child),
559 source: SourcedNameSource::Id,
560 };
561 sourced_names_vec.push(entry);
562 }
563 if let Some(name_atom) = child.get_name() {
564 let entry = SourcedName {
565 name: name_atom,
566 element: DomRoot::from_ref(child),
567 source: SourcedNameSource::Name,
568 };
569 sourced_names_vec.push(entry);
570 }
571 }
572 }
573
574 for child in self.controls.borrow().iter() {
576 if child.is::<HTMLImageElement>() {
577 if let Some(id_atom) = child.get_id() {
578 let entry = SourcedName {
579 name: id_atom,
580 element: DomRoot::from_ref(child),
581 source: SourcedNameSource::Id,
582 };
583 sourced_names_vec.push(entry);
584 }
585 if let Some(name_atom) = child.get_name() {
586 let entry = SourcedName {
587 name: name_atom,
588 element: DomRoot::from_ref(child),
589 source: SourcedNameSource::Name,
590 };
591 sourced_names_vec.push(entry);
592 }
593 }
594 }
595
596 let past_names_map = self.past_names_map.borrow();
598 for (key, val) in past_names_map.iter() {
599 let entry = SourcedName {
600 name: key.clone(),
601 element: DomRoot::from_ref(&*val.0),
602 source: SourcedNameSource::Past(self.current_name_generation.get() - val.1.0),
603 };
604 sourced_names_vec.push(entry);
605 }
606
607 sourced_names_vec.sort_by(|a, b| {
619 if a.element
620 .upcast::<Node>()
621 .CompareDocumentPosition(b.element.upcast::<Node>()) ==
622 0
623 {
624 if a.source.is_past() && b.source.is_past() {
625 b.source.cmp(&a.source)
626 } else {
627 a.source.cmp(&b.source)
628 }
629 } else if a
630 .element
631 .upcast::<Node>()
632 .CompareDocumentPosition(b.element.upcast::<Node>()) &
633 NodeConstants::DOCUMENT_POSITION_FOLLOWING ==
634 NodeConstants::DOCUMENT_POSITION_FOLLOWING
635 {
636 std::cmp::Ordering::Less
637 } else {
638 std::cmp::Ordering::Greater
639 }
640 });
641
642 sourced_names_vec.retain(|sn| !sn.name.to_string().is_empty());
644
645 let mut names_vec: Vec<DOMString> = Vec::new();
647 for elem in sourced_names_vec.iter() {
648 if !names_vec.iter().any(|name| *name == *elem.name) {
649 names_vec.push(DOMString::from(&*elem.name));
650 }
651 }
652
653 names_vec
654 }
655
656 fn CheckValidity(&self, cx: &mut JSContext) -> bool {
658 self.static_validation(cx).is_ok()
659 }
660
661 fn ReportValidity(&self, cx: &mut JSContext) -> bool {
663 self.interactive_validation(cx).is_ok()
664 }
665}
666
667#[derive(Clone, Copy, MallocSizeOf, PartialEq)]
668pub(crate) enum SubmittedFrom {
669 FromForm,
670 NotFromForm,
671}
672
673#[derive(Clone, Copy, MallocSizeOf)]
674pub(crate) enum ResetFrom {
675 FromForm,
676 NotFromForm,
677}
678
679impl HTMLFormElement {
680 fn pick_encoding(&self) -> &'static Encoding {
682 if self
684 .upcast::<Element>()
685 .has_attribute(&local_name!("accept-charset"))
686 {
687 let input = self
689 .upcast::<Element>()
690 .get_string_attribute(&local_name!("accept-charset"));
691
692 let input = input.str();
694 let mut candidate_encodings =
695 split_html_space_chars(&input).filter_map(|c| Encoding::for_label(c.as_bytes()));
696
697 return candidate_encodings.next().unwrap_or(UTF_8);
699 }
700
701 self.owner_document().encoding()
703 }
704
705 fn encode_plaintext(&self, form_data: &mut [FormDatum]) -> String {
707 let mut result = String::new();
709
710 for entry in form_data.iter() {
712 let value = match &entry.value {
713 FormDatumValue::File(f) => f.name(),
714 FormDatumValue::String(s) => s,
715 };
716 result.push_str(&format!("{}={}\r\n", entry.name, value));
717 }
718
719 result
721 }
722
723 pub(crate) fn update_validity(&self, can_gc: CanGc) {
724 let is_any_invalid = self
725 .controls
726 .borrow()
727 .iter()
728 .any(|control| control.is_invalid(false, can_gc));
729
730 self.upcast::<Element>()
731 .set_state(ElementState::VALID, !is_any_invalid);
732 self.upcast::<Element>()
733 .set_state(ElementState::INVALID, is_any_invalid);
734 }
735
736 pub(crate) fn submit(
738 &self,
739 cx: &mut JSContext,
740 submit_method_flag: SubmittedFrom,
741 submitter: FormSubmitterElement,
742 ) {
743 if self.upcast::<Element>().cannot_navigate() {
745 return;
746 }
747
748 if self.constructing_entry_list.get() {
750 return;
751 }
752 let doc = self.owner_document();
754
755 if doc.has_active_sandboxing_flag(SandboxingFlagSet::SANDBOXED_FORMS_BROWSING_CONTEXT_FLAG)
758 {
759 return;
760 }
761
762 let base = doc.base_url();
763 if submit_method_flag == SubmittedFrom::NotFromForm {
766 if self.firing_submission_events.get() {
768 return;
769 }
770 self.firing_submission_events.set(true);
772 if !submitter.no_validate(self) && self.interactive_validation(cx).is_err() {
774 self.firing_submission_events.set(false);
775 return;
776 }
777 let submitter_button = match submitter {
781 FormSubmitterElement::Form(f) => {
782 if f == self {
783 None
784 } else {
785 Some(f.upcast::<HTMLElement>())
786 }
787 },
788 FormSubmitterElement::Input(i) => Some(i.upcast::<HTMLElement>()),
789 FormSubmitterElement::Button(b) => Some(b.upcast::<HTMLElement>()),
790 };
791
792 let event = SubmitEvent::new(
794 self.global().as_window(),
795 atom!("submit"),
796 true,
797 true,
798 submitter_button.map(DomRoot::from_ref),
799 CanGc::from_cx(cx),
800 );
801 let event = event.upcast::<Event>();
802 event.fire(self.upcast::<EventTarget>(), CanGc::from_cx(cx));
803
804 self.firing_submission_events.set(false);
806 if event.DefaultPrevented() {
808 return;
809 }
810 if self.upcast::<Element>().cannot_navigate() {
812 return;
813 }
814 }
815
816 let encoding = self.pick_encoding();
818
819 let mut form_data =
821 match self.get_form_dataset(Some(submitter), Some(encoding), CanGc::from_cx(cx)) {
822 Some(form_data) => form_data,
823 None => return,
824 };
825
826 if self.upcast::<Element>().cannot_navigate() {
828 return;
829 }
830
831 let method = submitter.method();
833 let mut action = submitter.action();
838
839 if action.is_empty() {
841 action = DOMString::from(base.as_str());
842 }
843 let action_components = match doc.encoding_parse_a_url(&action.str()) {
845 Ok(url) => url,
846 Err(_) => return,
848 };
849 let scheme = action_components.scheme().to_owned();
851 let enctype = submitter.enctype();
853
854 let form_target_attribute = submitter.target();
857 let form_target = if submitter.is_submit_button() &&
858 valid_navigable_target_name_or_keyword(&form_target_attribute)
859 {
860 Some(form_target_attribute)
861 } else {
862 None
864 };
865 let form_owner = submitter.form_owner();
867 let form = form_owner.as_deref().unwrap_or(self);
868 let target = get_element_target(form.upcast::<Element>(), form_target);
869
870 let noopener = self.relations.get().get_element_noopener(target.as_ref());
872
873 let source = doc.browsing_context().unwrap();
876 let (maybe_chosen, _new) =
877 source.choose_browsing_context(cx, target.unwrap_or_default(), noopener);
878
879 let Some(chosen) = maybe_chosen else {
880 return;
882 };
883 let target_document = match chosen.document() {
884 Some(doc) => doc,
885 None => return,
886 };
887
888 let history_handling = if doc == target_document && !doc.completely_loaded() {
892 NavigationHistoryBehavior::Replace
893 } else {
894 NavigationHistoryBehavior::Auto
895 };
896
897 let target_window = target_document.window();
898 let mut load_data = LoadData::new(
899 LoadOrigin::Script(doc.origin().snapshot()),
900 action_components,
901 target_document.about_base_url(),
902 None,
903 target_window.as_global_scope().get_referrer(),
904 target_document.get_referrer_policy(),
905 Some(target_window.as_global_scope().is_secure_context()),
906 Some(target_document.insecure_requests_policy()),
907 target_document.has_trustworthy_ancestor_origin(),
908 target_document.creation_sandboxing_flag_set_considering_parent_iframe(),
909 );
910
911 match (&*scheme, method) {
915 (_, FormMethod::Dialog) => {
916 },
919 ("http", FormMethod::Get) | ("https", FormMethod::Get) | ("data", FormMethod::Get) => {
921 load_data
922 .headers
923 .typed_insert(ContentType::from(mime::APPLICATION_WWW_FORM_URLENCODED));
924 self.mutate_action_url(&mut form_data, load_data, target_window, history_handling);
925 },
926 ("http", FormMethod::Post) | ("https", FormMethod::Post) => {
928 load_data.method = Method::POST;
929 self.submit_entity_body(
930 cx,
931 &mut form_data,
932 load_data,
933 enctype,
934 encoding,
935 target_window,
936 history_handling,
937 );
938 },
939 ("file", _) |
941 ("about", _) |
942 ("data", FormMethod::Post) |
943 ("ftp", _) |
944 ("javascript", _) => {
945 self.plan_to_navigate(load_data, target_window, history_handling);
946 },
947 ("mailto", FormMethod::Post) => {
948 },
951 ("mailto", FormMethod::Get) => {
952 },
955 _ => (),
956 }
957 }
958
959 fn mutate_action_url(
961 &self,
962 form_data: &mut [FormDatum],
963 mut load_data: LoadData,
964 target: &Window,
965 history_handling: NavigationHistoryBehavior,
966 ) {
967 self.set_url_query_pairs(
968 &mut load_data.url,
969 form_data.iter().map(|field| {
970 (
971 field.name.normalize_crlf(),
972 field.replace_value().normalize_crlf(),
973 )
974 }),
975 );
976
977 self.plan_to_navigate(load_data, target, history_handling);
978 }
979
980 #[allow(clippy::too_many_arguments)]
982 fn submit_entity_body(
983 &self,
984 cx: &mut JSContext,
985 form_data: &mut [FormDatum],
986 mut load_data: LoadData,
987 enctype: FormEncType,
988 encoding: &'static Encoding,
989 target: &Window,
990 history_handling: NavigationHistoryBehavior,
991 ) {
992 let boundary = generate_boundary();
993 let bytes = match enctype {
994 FormEncType::UrlEncoded => {
995 load_data
996 .headers
997 .typed_insert(ContentType::from(mime::APPLICATION_WWW_FORM_URLENCODED));
998
999 let mut url = load_data.url.clone();
1000 self.set_url_query_pairs(
1001 &mut url,
1002 form_data.iter().map(|field| {
1006 (
1007 field.name.normalize_crlf(),
1008 field.replace_value().normalize_crlf(),
1009 )
1010 }),
1011 );
1012
1013 url.query().unwrap_or("").to_string().into_bytes()
1014 },
1015 FormEncType::MultipartFormData => {
1016 let mime: Mime = format!("multipart/form-data; boundary={}", boundary)
1017 .parse()
1018 .unwrap();
1019 load_data.headers.typed_insert(ContentType::from(mime));
1020 encode_multipart_form_data(form_data, boundary, encoding)
1021 },
1022 FormEncType::TextPlain => {
1023 load_data
1024 .headers
1025 .typed_insert(ContentType::from(mime::TEXT_PLAIN));
1026 self.encode_plaintext(form_data).into_bytes()
1027 },
1028 };
1029
1030 let global = self.global();
1031
1032 let request_body = bytes
1033 .extract(cx, &global, false)
1034 .expect("Couldn't extract body.")
1035 .into_net_request_body()
1036 .0;
1037 load_data.data = Some(request_body);
1038
1039 self.plan_to_navigate(load_data, target, history_handling);
1040 }
1041
1042 fn set_url_query_pairs<T>(
1043 &self,
1044 url: &mut servo_url::ServoUrl,
1045 pairs: impl Iterator<Item = (T, String)>,
1046 ) where
1047 T: AsRef<str>,
1048 {
1049 let encoding = self.pick_encoding();
1050 url.as_mut_url()
1051 .query_pairs_mut()
1052 .encoding_override(Some(&|s| encoding.encode(s).0))
1053 .clear()
1054 .extend_pairs(pairs);
1055 }
1056
1057 fn plan_to_navigate(
1059 &self,
1060 mut load_data: LoadData,
1061 target: &Window,
1062 history_handling: NavigationHistoryBehavior,
1063 ) {
1064 let elem = self.upcast::<Element>();
1069 let referrer = match elem.get_attribute(&local_name!("rel")) {
1070 Some(ref link_types) if link_types.Value().contains("noreferrer") => {
1071 Referrer::NoReferrer
1072 },
1073 _ => target.as_global_scope().get_referrer(),
1074 };
1075
1076 self.planned_navigation
1079 .set(self.planned_navigation.get().wrapping_add(1));
1080 let planned_navigation = self.planned_navigation.get();
1081
1082 let ongoing_navigation = target.set_ongoing_navigation();
1098
1099 let referrer_policy = target.Document().get_referrer_policy();
1100 load_data.creator_pipeline_id = Some(target.pipeline_id());
1101 load_data.referrer = referrer;
1102 load_data.referrer_policy = referrer_policy;
1103
1104 if let Some(window_proxy) = target.undiscarded_window_proxy() &&
1107 let Some(frame) = window_proxy
1108 .frame_element()
1109 .and_then(|e| e.downcast::<HTMLIFrameElement>())
1110 {
1111 frame.note_pending_navigation()
1112 }
1113
1114 let form = Trusted::new(self);
1117 let window = Trusted::new(target);
1118 let task = task!(navigate_to_form_planned_navigation: move |cx| {
1119 if planned_navigation != form.root().planned_navigation.get() {
1123 return;
1124 }
1125
1126 if ongoing_navigation != window.root().ongoing_navigation() {
1129 return;
1130 }
1131
1132 navigate(
1134 cx,
1135 &window.root(),
1136 history_handling,
1137 false,
1138 load_data,
1139 )
1140 });
1141
1142 target
1147 .global()
1148 .task_manager()
1149 .dom_manipulation_task_source()
1150 .queue(task)
1151 }
1152
1153 fn interactive_validation(&self, cx: &mut JSContext) -> Result<(), ()> {
1156 let unhandled_invalid_controls = match self.static_validation(cx) {
1161 Ok(()) => return Ok(()),
1162 Err(err) => err,
1163 };
1164
1165 let mut first = true;
1168
1169 for elem in unhandled_invalid_controls {
1170 if let Some(validatable) = elem.as_maybe_validatable() {
1171 error!("Validation error: {}", validatable.validation_message());
1172 }
1173 if first && let Some(html_elem) = elem.downcast::<HTMLElement>() {
1174 html_elem.Focus(cx, &FocusOptions::default());
1181 first = false;
1182 }
1183 }
1184
1185 Err(())
1189 }
1190
1191 fn static_validation(&self, cx: &mut JSContext) -> Result<(), Vec<DomRoot<Element>>> {
1194 let invalid_controls = self
1196 .controls
1197 .borrow()
1198 .iter()
1199 .filter_map(|field| {
1200 if let Some(element) = field.downcast::<Element>() {
1201 if element.is_invalid(true, CanGc::from_cx(cx)) {
1202 Some(DomRoot::from_ref(element))
1203 } else {
1204 None
1205 }
1206 } else {
1207 None
1208 }
1209 })
1210 .collect::<Vec<DomRoot<Element>>>();
1211 if invalid_controls.is_empty() {
1213 return Ok(());
1214 }
1215 let unhandled_invalid_controls = invalid_controls
1217 .into_iter()
1218 .filter_map(|field| {
1219 let not_canceled = field
1222 .upcast::<EventTarget>()
1223 .fire_cancelable_event(cx, atom!("invalid"));
1224 if not_canceled {
1226 return Some(field);
1227 }
1228 None
1229 })
1230 .collect::<Vec<DomRoot<Element>>>();
1231 Err(unhandled_invalid_controls)
1233 }
1234
1235 fn get_unclean_dataset(
1240 &self,
1241 submitter: Option<FormSubmitterElement>,
1242 encoding: Option<&'static Encoding>,
1243 can_gc: CanGc,
1244 ) -> Vec<FormDatum> {
1245 let mut data_set = Vec::new();
1246 for child in self.controls.borrow().iter() {
1247 if child.disabled_state() {
1249 continue;
1250 }
1251 let child = child.upcast::<Node>();
1252
1253 if child.ancestors().any(|a| a.is::<HTMLDataListElement>()) {
1255 continue;
1256 }
1257 if let NodeTypeId::Element(ElementTypeId::HTMLElement(element)) = child.type_id() {
1258 match element {
1259 HTMLElementTypeId::HTMLInputElement => {
1260 let input = child.downcast::<HTMLInputElement>().unwrap();
1261 data_set.append(&mut input.form_datums(submitter, encoding));
1262 },
1263 HTMLElementTypeId::HTMLButtonElement => {
1264 let button = child.downcast::<HTMLButtonElement>().unwrap();
1265 if let Some(datum) = button.form_datum(submitter) {
1266 data_set.push(datum);
1267 }
1268 },
1269 HTMLElementTypeId::HTMLObjectElement => {
1270 },
1272 HTMLElementTypeId::HTMLSelectElement => {
1273 let select = child.downcast::<HTMLSelectElement>().unwrap();
1274 select.push_form_data(&mut data_set);
1275 },
1276 HTMLElementTypeId::HTMLTextAreaElement => {
1277 let textarea = child.downcast::<HTMLTextAreaElement>().unwrap();
1278 let name = textarea.Name();
1279 if !name.is_empty() {
1280 data_set.push(FormDatum {
1281 ty: textarea.Type(),
1282 name,
1283 value: FormDatumValue::String(textarea.Value()),
1284 });
1285 }
1286 },
1287 HTMLElementTypeId::HTMLElement => {
1288 let custom = child.downcast::<HTMLElement>().unwrap();
1289 if custom.is_form_associated_custom_element() {
1290 let internals =
1292 custom.upcast::<Element>().ensure_element_internals(can_gc);
1293 internals.perform_entry_construction(&mut data_set);
1294 }
1296 },
1297 _ => (),
1298 }
1299 }
1300
1301 let child_element = child.downcast::<Element>().unwrap();
1305 let input_matches = child_element
1306 .downcast::<HTMLInputElement>()
1307 .is_some_and(|input| {
1308 matches!(
1309 *input.input_type(),
1310 InputType::Text(_) | InputType::Search(_)
1311 )
1312 });
1313 let textarea_matches = child_element.is::<HTMLTextAreaElement>();
1314 let dirname = child_element.get_string_attribute(&local_name!("dirname"));
1315 if (input_matches || textarea_matches) && !dirname.is_empty() {
1316 let dir = DOMString::from(child_element.directionality());
1317 data_set.push(FormDatum {
1318 ty: DOMString::from("string"),
1319 name: dirname,
1320 value: FormDatumValue::String(dir),
1321 });
1322 }
1323 }
1324 data_set
1325 }
1326
1327 pub(crate) fn get_form_dataset(
1329 &self,
1330 submitter: Option<FormSubmitterElement>,
1331 encoding: Option<&'static Encoding>,
1332 can_gc: CanGc,
1333 ) -> Option<Vec<FormDatum>> {
1334 if self.constructing_entry_list.get() {
1336 return None;
1337 }
1338
1339 self.constructing_entry_list.set(true);
1341
1342 let ret = self.get_unclean_dataset(submitter, encoding, can_gc);
1344
1345 let window = self.owner_window();
1346
1347 let form_data = FormData::new(Some(ret), &window.global(), can_gc);
1349
1350 let event = FormDataEvent::new(
1352 &window,
1353 atom!("formdata"),
1354 EventBubbles::Bubbles,
1355 EventCancelable::NotCancelable,
1356 &form_data,
1357 can_gc,
1358 );
1359
1360 event
1361 .upcast::<Event>()
1362 .fire(self.upcast::<EventTarget>(), can_gc);
1363
1364 self.constructing_entry_list.set(false);
1366
1367 Some(form_data.datums())
1369 }
1370
1371 pub(crate) fn reset(&self, cx: &mut JSContext, _reset_method_flag: ResetFrom) {
1373 if self.marked_for_reset.get() {
1375 return;
1376 } else {
1377 self.marked_for_reset.set(true);
1378 }
1379
1380 let reset = self
1384 .upcast::<EventTarget>()
1385 .fire_bubbling_cancelable_event(cx, atom!("reset"));
1386 if !reset {
1387 return;
1388 }
1389
1390 let controls: Vec<_> = self
1391 .controls
1392 .borrow()
1393 .iter()
1394 .map(|c| c.as_rooted())
1395 .collect();
1396
1397 for child in controls {
1398 child.reset(cx);
1399 }
1400 self.marked_for_reset.set(false);
1401 }
1402
1403 fn add_control<T: ?Sized + FormControl>(&self, control: &T, can_gc: CanGc) {
1404 {
1405 let root = self.upcast::<Element>().root_element();
1406 let root = root.upcast::<Node>();
1407 let mut controls = self.controls.borrow_mut();
1408
1409 let control_element = control.to_element();
1415 if control_element.upcast::<Node>().has_parent() {
1416 controls.insert_pre_order(control_element, root);
1417 } else {
1418 controls.push(Dom::from_ref(control_element));
1419 }
1420 }
1421 self.update_validity(can_gc);
1422 }
1423
1424 fn remove_control<T: ?Sized + FormControl>(&self, control: &T, can_gc: CanGc) {
1425 {
1426 let control = control.to_element();
1427 let mut controls = self.controls.borrow_mut();
1428 controls
1429 .iter()
1430 .position(|c| &**c == control)
1431 .map(|idx| controls.remove(idx));
1432
1433 let mut past_names_map = self.past_names_map.borrow_mut();
1438 past_names_map.0.retain(|_k, v| v.0 != control);
1439 }
1440 self.update_validity(can_gc);
1441 }
1442}
1443
1444impl Element {
1445 pub(crate) fn is_resettable(&self) -> bool {
1446 let NodeTypeId::Element(ElementTypeId::HTMLElement(element_type)) =
1447 self.upcast::<Node>().type_id()
1448 else {
1449 return false;
1450 };
1451 matches!(
1452 element_type,
1453 HTMLElementTypeId::HTMLInputElement |
1454 HTMLElementTypeId::HTMLSelectElement |
1455 HTMLElementTypeId::HTMLTextAreaElement |
1456 HTMLElementTypeId::HTMLOutputElement |
1457 HTMLElementTypeId::HTMLElement
1458 )
1459 }
1460
1461 pub(crate) fn reset(&self, cx: &mut JSContext) {
1462 if !self.is_resettable() {
1463 return;
1464 }
1465
1466 if let Some(input_element) = self.downcast::<HTMLInputElement>() {
1467 input_element.reset(cx);
1468 } else if let Some(select_element) = self.downcast::<HTMLSelectElement>() {
1469 select_element.reset();
1470 } else if let Some(textarea_element) = self.downcast::<HTMLTextAreaElement>() {
1471 textarea_element.reset(cx);
1472 } else if let Some(output_element) = self.downcast::<HTMLOutputElement>() {
1473 output_element.reset(cx);
1474 } else if let Some(html_element) = self.downcast::<HTMLElement>() &&
1475 html_element.is_form_associated_custom_element()
1476 {
1477 ScriptThread::enqueue_callback_reaction(
1478 html_element.upcast::<Element>(),
1479 CallbackReaction::FormReset,
1480 None,
1481 )
1482 }
1483 }
1484}
1485
1486#[derive(Clone, JSTraceable, MallocSizeOf)]
1487pub(crate) enum FormDatumValue {
1488 File(DomRoot<File>),
1489 String(DOMString),
1490}
1491
1492#[derive(Clone, JSTraceable, MallocSizeOf)]
1493pub(crate) struct FormDatum {
1494 pub(crate) ty: DOMString,
1495 pub(crate) name: DOMString,
1496 pub(crate) value: FormDatumValue,
1497}
1498
1499impl FormDatum {
1500 pub(crate) fn replace_value(&self) -> &DOMString {
1501 match self.value {
1502 FormDatumValue::File(ref f) => f.name(),
1503 FormDatumValue::String(ref s) => s,
1504 }
1505 }
1506}
1507
1508#[derive(Clone, Copy, MallocSizeOf)]
1509pub(crate) enum FormEncType {
1510 TextPlain,
1511 UrlEncoded,
1512 MultipartFormData,
1513}
1514
1515#[derive(Clone, Copy, MallocSizeOf)]
1516pub(crate) enum FormMethod {
1517 Get,
1518 Post,
1519 Dialog,
1520}
1521
1522#[derive(Clone, Copy, MallocSizeOf)]
1524pub(crate) enum FormSubmitterElement<'a> {
1525 Form(&'a HTMLFormElement),
1526 Input(&'a HTMLInputElement),
1527 Button(&'a HTMLButtonElement),
1528 }
1531
1532impl FormSubmitterElement<'_> {
1533 fn action(&self) -> DOMString {
1534 match *self {
1535 FormSubmitterElement::Form(form) => form.Action(),
1536 FormSubmitterElement::Input(input_element) => input_element.get_form_attribute(
1537 &local_name!("formaction"),
1538 |i| i.FormAction(),
1539 |f| f.Action(),
1540 ),
1541 FormSubmitterElement::Button(button_element) => button_element.get_form_attribute(
1542 &local_name!("formaction"),
1543 |i| i.FormAction(),
1544 |f| f.Action(),
1545 ),
1546 }
1547 }
1548
1549 fn enctype(&self) -> FormEncType {
1550 let attr = match *self {
1551 FormSubmitterElement::Form(form) => form.Enctype(),
1552 FormSubmitterElement::Input(input_element) => input_element.get_form_attribute(
1553 &local_name!("formenctype"),
1554 |i| i.FormEnctype(),
1555 |f| f.Enctype(),
1556 ),
1557 FormSubmitterElement::Button(button_element) => button_element.get_form_attribute(
1558 &local_name!("formenctype"),
1559 |i| i.FormEnctype(),
1560 |f| f.Enctype(),
1561 ),
1562 };
1563 match_domstring_ascii!(attr,
1566 "multipart/form-data" => FormEncType::MultipartFormData,
1567 "text/plain" => FormEncType::TextPlain,
1568 _ => FormEncType::UrlEncoded,
1569 )
1570 }
1571
1572 fn method(&self) -> FormMethod {
1573 let attr = match *self {
1574 FormSubmitterElement::Form(form) => form.Method(),
1575 FormSubmitterElement::Input(input_element) => input_element.get_form_attribute(
1576 &local_name!("formmethod"),
1577 |i| i.FormMethod(),
1578 |f| f.Method(),
1579 ),
1580 FormSubmitterElement::Button(button_element) => button_element.get_form_attribute(
1581 &local_name!("formmethod"),
1582 |i| i.FormMethod(),
1583 |f| f.Method(),
1584 ),
1585 };
1586 match_domstring_ascii!(attr,
1587 "dialog" => FormMethod::Dialog,
1588 "post" => FormMethod::Post,
1589 _ => FormMethod::Get,
1590 )
1591 }
1592
1593 fn target(&self) -> DOMString {
1594 match *self {
1595 FormSubmitterElement::Form(form) => form.Target(),
1596 FormSubmitterElement::Input(input_element) => input_element.get_form_attribute(
1597 &local_name!("formtarget"),
1598 |i| i.FormTarget(),
1599 |f| f.Target(),
1600 ),
1601 FormSubmitterElement::Button(button_element) => button_element.get_form_attribute(
1602 &local_name!("formtarget"),
1603 |i| i.FormTarget(),
1604 |f| f.Target(),
1605 ),
1606 }
1607 }
1608
1609 fn no_validate(&self, _form_owner: &HTMLFormElement) -> bool {
1610 match *self {
1611 FormSubmitterElement::Form(form) => form.NoValidate(),
1612 FormSubmitterElement::Input(input_element) => input_element.get_form_boolean_attribute(
1613 &local_name!("formnovalidate"),
1614 |i| i.FormNoValidate(),
1615 |f| f.NoValidate(),
1616 ),
1617 FormSubmitterElement::Button(button_element) => button_element
1618 .get_form_boolean_attribute(
1619 &local_name!("formnovalidate"),
1620 |i| i.FormNoValidate(),
1621 |f| f.NoValidate(),
1622 ),
1623 }
1624 }
1625
1626 pub(crate) fn is_submit_button(&self) -> bool {
1628 match *self {
1629 FormSubmitterElement::Input(input_element) => input_element.is_submit_button(),
1632 FormSubmitterElement::Button(button_element) => button_element.is_submit_button(),
1634 _ => false,
1635 }
1636 }
1637
1638 pub(crate) fn form_owner(&self) -> Option<DomRoot<HTMLFormElement>> {
1640 match *self {
1641 FormSubmitterElement::Button(button_el) => button_el.form_owner(),
1642 FormSubmitterElement::Input(input_el) => input_el.form_owner(),
1643 _ => None,
1644 }
1645 }
1646}
1647
1648pub(crate) trait FormControl: DomObject<ReflectorType = ()> + NodeTraits {
1649 fn form_owner(&self) -> Option<DomRoot<HTMLFormElement>>;
1650
1651 fn set_form_owner(&self, form: Option<&HTMLFormElement>);
1652
1653 fn to_element(&self) -> ∈
1654
1655 fn is_listed(&self) -> bool {
1656 true
1657 }
1658
1659 fn set_form_owner_from_parser(&self, form: &HTMLFormElement, can_gc: CanGc) {
1664 let elem = self.to_element();
1665 let node = elem.upcast::<Node>();
1666 node.set_flag(NodeFlags::PARSER_ASSOCIATED_FORM_OWNER, true);
1667 form.add_control(self, can_gc);
1668 self.set_form_owner(Some(form));
1669 }
1670
1671 fn reset_form_owner(&self, can_gc: CanGc) {
1673 let elem = self.to_element();
1674 let node = elem.upcast::<Node>();
1675 let old_owner = self.form_owner();
1676 let has_form_id = elem.has_attribute(&local_name!("form"));
1677 let nearest_form_ancestor = node
1678 .ancestors()
1679 .find_map(DomRoot::downcast::<HTMLFormElement>);
1680
1681 if old_owner.is_some() &&
1683 !(self.is_listed() && has_form_id) &&
1684 nearest_form_ancestor == old_owner
1685 {
1686 return;
1687 }
1688
1689 let new_owner = if self.is_listed() && has_form_id && elem.is_connected() {
1691 let form_id = elem.get_string_attribute(&local_name!("form"));
1695 let first_relevant_element = if let Some(shadow_root) = self.containing_shadow_root() {
1696 shadow_root
1697 .upcast::<DocumentFragment>()
1698 .GetElementById(form_id)
1699 } else {
1700 node.owner_document().GetElementById(form_id)
1701 };
1702
1703 first_relevant_element.and_then(DomRoot::downcast::<HTMLFormElement>)
1704 } else {
1705 nearest_form_ancestor
1707 };
1708
1709 if old_owner != new_owner {
1710 if let Some(o) = old_owner {
1711 o.remove_control(self, can_gc);
1712 }
1713 if let Some(ref new_owner) = new_owner {
1714 new_owner.add_control(self, can_gc);
1715 }
1716 if let Some(html_elem) = elem.downcast::<HTMLElement>() &&
1718 html_elem.is_form_associated_custom_element()
1719 {
1720 ScriptThread::enqueue_callback_reaction(
1721 elem,
1722 CallbackReaction::FormAssociated(
1723 new_owner.as_ref().map(|form| DomRoot::from_ref(&**form)),
1724 ),
1725 None,
1726 )
1727 }
1728 self.set_form_owner(new_owner.as_deref());
1729 }
1730 }
1731
1732 fn form_attribute_mutated(&self, mutation: AttributeMutation, can_gc: CanGc) {
1734 match mutation {
1735 AttributeMutation::Set(..) => {
1736 self.register_if_necessary();
1737 },
1738 AttributeMutation::Removed => {
1739 self.unregister_if_necessary();
1740 },
1741 }
1742
1743 self.reset_form_owner(can_gc);
1744 }
1745
1746 fn register_if_necessary(&self) {
1748 let elem = self.to_element();
1749 let form_id = elem.get_string_attribute(&local_name!("form"));
1750 let node = elem.upcast::<Node>();
1751
1752 if self.is_listed() && !form_id.is_empty() && node.is_connected() {
1753 node.owner_document()
1754 .register_form_id_listener(form_id, self);
1755 }
1756 }
1757
1758 fn unregister_if_necessary(&self) {
1759 let elem = self.to_element();
1760 let form_id = elem.get_string_attribute(&local_name!("form"));
1761
1762 if self.is_listed() && !form_id.is_empty() {
1763 elem.owner_document()
1764 .unregister_form_id_listener(form_id, self);
1765 }
1766 }
1767
1768 fn bind_form_control_to_tree(&self, can_gc: CanGc) {
1770 let elem = self.to_element();
1771 let node = elem.upcast::<Node>();
1772
1773 let must_skip_reset = node.get_flag(NodeFlags::PARSER_ASSOCIATED_FORM_OWNER);
1778 node.set_flag(NodeFlags::PARSER_ASSOCIATED_FORM_OWNER, false);
1779
1780 if !must_skip_reset {
1781 self.form_attribute_mutated(
1782 AttributeMutation::Set(None, AttributeMutationReason::Directly),
1783 can_gc,
1784 );
1785 }
1786 }
1787
1788 fn unbind_form_control_from_tree(&self, can_gc: CanGc) {
1790 let elem = self.to_element();
1791 let has_form_attr = elem.has_attribute(&local_name!("form"));
1792 let same_subtree = self
1793 .form_owner()
1794 .is_none_or(|form| elem.is_in_same_home_subtree(&*form));
1795
1796 self.unregister_if_necessary();
1797
1798 if !same_subtree || (self.is_listed() && has_form_attr) {
1804 self.reset_form_owner(can_gc);
1805 }
1806 }
1807
1808 fn get_form_attribute<InputFn, OwnerFn>(
1809 &self,
1810 attr: &LocalName,
1811 input: InputFn,
1812 owner: OwnerFn,
1813 ) -> DOMString
1814 where
1815 InputFn: Fn(&Self) -> DOMString,
1816 OwnerFn: Fn(&HTMLFormElement) -> DOMString,
1817 Self: Sized,
1818 {
1819 if self.to_element().has_attribute(attr) {
1820 input(self)
1821 } else {
1822 self.form_owner().map_or(DOMString::new(), |t| owner(&t))
1823 }
1824 }
1825
1826 fn get_form_boolean_attribute<InputFn, OwnerFn>(
1827 &self,
1828 attr: &LocalName,
1829 input: InputFn,
1830 owner: OwnerFn,
1831 ) -> bool
1832 where
1833 InputFn: Fn(&Self) -> bool,
1834 OwnerFn: Fn(&HTMLFormElement) -> bool,
1835 Self: Sized,
1836 {
1837 if self.to_element().has_attribute(attr) {
1838 input(self)
1839 } else {
1840 self.form_owner().is_some_and(|t| owner(&t))
1841 }
1842 }
1843
1844 fn is_candidate_for_constraint_validation(&self) -> bool {
1846 let element = self.to_element();
1847 let html_element = element.downcast::<HTMLElement>();
1848 if let Some(html_element) = html_element {
1849 html_element.is_submittable_element() || element.is_instance_validatable()
1850 } else {
1851 false
1852 }
1853 }
1854
1855 fn moving_steps(&self, cx: &mut JSContext) {
1856 let same_subtree = self
1859 .form_owner()
1860 .is_none_or(|form| self.to_element().is_in_same_home_subtree(&*form));
1861 if !same_subtree {
1862 self.reset_form_owner(CanGc::from_cx(cx))
1863 }
1864 }
1865
1866 }
1869
1870impl VirtualMethods for HTMLFormElement {
1871 fn super_type(&self) -> Option<&dyn VirtualMethods> {
1872 Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods)
1873 }
1874
1875 fn unbind_from_tree(&self, cx: &mut JSContext, context: &UnbindContext) {
1876 self.super_type().unwrap().unbind_from_tree(cx, context);
1877
1878 rooted_vec!(let mut to_reset);
1881 to_reset.extend(
1882 self.controls
1883 .borrow()
1884 .iter()
1885 .filter(|c| !c.is_in_same_home_subtree(self))
1886 .cloned(),
1887 );
1888
1889 for control in to_reset.iter() {
1890 control
1891 .as_maybe_form_control()
1892 .expect("Element must be a form control")
1893 .reset_form_owner(CanGc::from_cx(cx));
1894 }
1895 }
1896
1897 fn parse_plain_attribute(&self, name: &LocalName, value: DOMString) -> AttrValue {
1898 match name {
1899 &local_name!("rel") => AttrValue::from_serialized_tokenlist(value.into()),
1900 _ => self
1901 .super_type()
1902 .unwrap()
1903 .parse_plain_attribute(name, value),
1904 }
1905 }
1906
1907 fn attribute_mutated(
1908 &self,
1909 cx: &mut JSContext,
1910 attr: AttrRef<'_>,
1911 mutation: AttributeMutation,
1912 ) {
1913 self.super_type()
1914 .unwrap()
1915 .attribute_mutated(cx, attr, mutation);
1916
1917 match *attr.local_name() {
1918 local_name!("rel") | local_name!("rev") => {
1919 self.relations
1920 .set(LinkRelations::for_element(self.upcast()));
1921 },
1922 _ => {},
1923 }
1924 }
1925}
1926
1927pub(crate) trait FormControlElementHelpers {
1928 fn as_maybe_form_control(&self) -> Option<&dyn FormControl>;
1929}
1930
1931impl FormControlElementHelpers for Element {
1932 fn as_maybe_form_control(&self) -> Option<&dyn FormControl> {
1933 let node = self.upcast::<Node>();
1934
1935 match node.type_id() {
1936 NodeTypeId::Element(ElementTypeId::HTMLElement(
1937 HTMLElementTypeId::HTMLButtonElement,
1938 )) => Some(self.downcast::<HTMLButtonElement>().unwrap() as &dyn FormControl),
1939 NodeTypeId::Element(ElementTypeId::HTMLElement(
1940 HTMLElementTypeId::HTMLFieldSetElement,
1941 )) => Some(self.downcast::<HTMLFieldSetElement>().unwrap() as &dyn FormControl),
1942 NodeTypeId::Element(ElementTypeId::HTMLElement(
1943 HTMLElementTypeId::HTMLImageElement,
1944 )) => Some(self.downcast::<HTMLImageElement>().unwrap() as &dyn FormControl),
1945 NodeTypeId::Element(ElementTypeId::HTMLElement(
1946 HTMLElementTypeId::HTMLInputElement,
1947 )) => Some(self.downcast::<HTMLInputElement>().unwrap() as &dyn FormControl),
1948 NodeTypeId::Element(ElementTypeId::HTMLElement(
1949 HTMLElementTypeId::HTMLLabelElement,
1950 )) => Some(self.downcast::<HTMLLabelElement>().unwrap() as &dyn FormControl),
1951 NodeTypeId::Element(ElementTypeId::HTMLElement(
1952 HTMLElementTypeId::HTMLLegendElement,
1953 )) => Some(self.downcast::<HTMLLegendElement>().unwrap() as &dyn FormControl),
1954 NodeTypeId::Element(ElementTypeId::HTMLElement(
1955 HTMLElementTypeId::HTMLObjectElement,
1956 )) => Some(self.downcast::<HTMLObjectElement>().unwrap() as &dyn FormControl),
1957 NodeTypeId::Element(ElementTypeId::HTMLElement(
1958 HTMLElementTypeId::HTMLOutputElement,
1959 )) => Some(self.downcast::<HTMLOutputElement>().unwrap() as &dyn FormControl),
1960 NodeTypeId::Element(ElementTypeId::HTMLElement(
1961 HTMLElementTypeId::HTMLSelectElement,
1962 )) => Some(self.downcast::<HTMLSelectElement>().unwrap() as &dyn FormControl),
1963 NodeTypeId::Element(ElementTypeId::HTMLElement(
1964 HTMLElementTypeId::HTMLTextAreaElement,
1965 )) => Some(self.downcast::<HTMLTextAreaElement>().unwrap() as &dyn FormControl),
1966 _ => self.downcast::<HTMLElement>().and_then(|elem| {
1967 if elem.is_form_associated_custom_element() {
1968 Some(elem as &dyn FormControl)
1969 } else {
1970 None
1971 }
1972 }),
1973 }
1974 }
1975}
1976
1977pub(crate) fn encode_multipart_form_data(
1979 form_data: &mut [FormDatum],
1980 boundary: String,
1981 encoding: &'static Encoding,
1982) -> Vec<u8> {
1983 let mut result = vec![];
1984
1985 fn escape_for_header(s: &DOMString) -> String {
1992 s.str()
1993 .replace('\n', "%0A")
1994 .replace('\r', "%0D")
1995 .replace('"', "%22")
1996 }
1997
1998 for entry in form_data.iter_mut() {
1999 entry.name = entry.name.normalize_crlf().into();
2001
2002 if let FormDatumValue::String(ref s) = entry.value {
2005 entry.value = FormDatumValue::String(s.normalize_crlf().into());
2006 }
2007
2008 let mut boundary_bytes = format!("--{}\r\n", boundary).into_bytes();
2013 result.append(&mut boundary_bytes);
2014
2015 let escaped_name = escape_for_header(&entry.name);
2016
2017 match entry.value {
2020 FormDatumValue::String(ref s) => {
2021 let content_disposition = format!("form-data; name=\"{}\"", escaped_name);
2023 let mut bytes =
2024 format!("Content-Disposition: {content_disposition}\r\n\r\n{s}\r\n",)
2025 .into_bytes();
2026 result.append(&mut bytes);
2027 },
2028 FormDatumValue::File(ref f) => {
2029 let charset = encoding.name();
2030 let extra = if charset.to_lowercase() == "utf-8" {
2038 let escaped = escape_for_header(f.name());
2039 format!("filename=\"{}\"", escaped)
2040 } else {
2041 format!(
2042 "filename*=\"{}\"''{}",
2043 charset,
2044 http_percent_encode(&f.name().as_bytes())
2045 )
2046 };
2047
2048 let content_disposition =
2049 format!("form-data; name=\"{}\"; {}", escaped_name, extra);
2050 let content_type: Mime = f
2052 .upcast::<Blob>()
2053 .Type()
2054 .parse()
2055 .unwrap_or(mime::TEXT_PLAIN);
2056 let mut type_bytes = format!(
2057 "Content-Disposition: {}\r\nContent-Type: {}\r\n\r\n",
2058 content_disposition, content_type
2059 )
2060 .into_bytes();
2061 result.append(&mut type_bytes);
2062
2063 let mut bytes = f.upcast::<Blob>().get_bytes().unwrap_or(vec![]);
2064
2065 result.append(&mut bytes);
2066 result.extend(b"\r\n");
2067 },
2068 }
2069 }
2070
2071 let mut boundary_bytes = format!("--{boundary}--\r\n").into_bytes();
2072 result.append(&mut boundary_bytes);
2073
2074 result
2075}
2076
2077pub(crate) fn generate_boundary() -> String {
2079 let i1 = random::<u32>();
2080 let i2 = random::<u32>();
2081
2082 format!("---------------------------{0}{1}", i1, i2)
2083}