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::codegen::GenericBindings::DocumentFragmentBinding::DocumentFragmentMethods;
22use script_bindings::match_domstring_ascii;
23use servo_constellation_traits::{LoadData, LoadOrigin, NavigationHistoryBehavior};
24use style::attr::AttrValue;
25use style::str::split_html_space_chars;
26use stylo_atoms::Atom;
27use stylo_dom::ElementState;
28
29use crate::body::Extractable;
30use crate::dom::attr::Attr;
31use crate::dom::bindings::cell::DomRefCell;
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, DomObject};
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::{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::htmlbuttonelement::HTMLButtonElement;
66use crate::dom::html::htmlcollection::CollectionFilter;
67use crate::dom::html::htmldatalistelement::HTMLDataListElement;
68use crate::dom::html::htmlelement::HTMLElement;
69use crate::dom::html::htmlfieldsetelement::HTMLFieldSetElement;
70use crate::dom::html::htmlformcontrolscollection::HTMLFormControlsCollection;
71use crate::dom::html::htmlimageelement::HTMLImageElement;
72use crate::dom::html::htmllabelelement::HTMLLabelElement;
73use crate::dom::html::htmllegendelement::HTMLLegendElement;
74use crate::dom::html::htmlobjectelement::HTMLObjectElement;
75use crate::dom::html::htmloutputelement::HTMLOutputElement;
76use crate::dom::html::htmlselectelement::HTMLSelectElement;
77use crate::dom::html::htmltextareaelement::HTMLTextAreaElement;
78use crate::dom::html::input_element::HTMLInputElement;
79use crate::dom::input_element::input_type::InputType;
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::virtualmethods::VirtualMethods;
86use crate::dom::window::Window;
87use crate::links::{LinkRelations, get_element_target, valid_navigable_target_name_or_keyword};
88use crate::navigation::navigate;
89use crate::script_runtime::CanGc;
90use crate::script_thread::ScriptThread;
91
92#[dom_struct]
94pub(crate) struct HTMLFormElement {
95 htmlelement: HTMLElement,
96 marked_for_reset: Cell<bool>,
97 constructing_entry_list: Cell<bool>,
99 elements: DomOnceCell<HTMLFormControlsCollection>,
100 controls: DomRefCell<Vec<Dom<Element>>>,
101
102 #[expect(clippy::type_complexity)]
104 past_names_map:
105 DomRefCell<HashMapTracedValues<Atom, (Dom<Element>, NoTrace<usize>), FxBuildHasher>>,
106
107 current_name_generation: Cell<usize>,
109
110 firing_submission_events: Cell<bool>,
111 rel_list: MutNullableDom<DOMTokenList>,
112
113 planned_navigation: Cell<usize>,
115
116 #[no_trace]
118 relations: Cell<LinkRelations>,
119}
120
121impl HTMLFormElement {
122 fn new_inherited(
123 local_name: LocalName,
124 prefix: Option<Prefix>,
125 document: &Document,
126 ) -> HTMLFormElement {
127 HTMLFormElement {
128 htmlelement: HTMLElement::new_inherited_with_state(
129 ElementState::VALID,
130 local_name,
131 prefix,
132 document,
133 ),
134 marked_for_reset: Cell::new(false),
135 constructing_entry_list: Cell::new(false),
136 elements: Default::default(),
137 controls: DomRefCell::new(Vec::new()),
138 past_names_map: DomRefCell::new(HashMapTracedValues::new_fx()),
139 current_name_generation: Cell::new(0),
140 firing_submission_events: Cell::new(false),
141 rel_list: Default::default(),
142 planned_navigation: Default::default(),
143 relations: Cell::new(LinkRelations::empty()),
144 }
145 }
146
147 pub(crate) fn new(
148 cx: &mut js::context::JSContext,
149 local_name: LocalName,
150 prefix: Option<Prefix>,
151 document: &Document,
152 proto: Option<HandleObject>,
153 ) -> DomRoot<HTMLFormElement> {
154 Node::reflect_node_with_proto(
155 cx,
156 Box::new(HTMLFormElement::new_inherited(local_name, prefix, document)),
157 document,
158 proto,
159 )
160 }
161
162 fn filter_for_radio_list(mode: RadioListMode, child: &Element, name: &Atom) -> bool {
163 if let Some(child) = child.downcast::<Element>() {
164 match mode {
165 RadioListMode::ControlsExceptImageInputs => {
166 if child
167 .downcast::<HTMLElement>()
168 .is_some_and(|c| c.is_listed_element()) &&
169 (child.get_id().is_some_and(|i| i == *name) ||
170 child.get_name().is_some_and(|n| n == *name))
171 {
172 if let Some(inp) = child.downcast::<HTMLInputElement>() {
173 return !matches!(*inp.input_type(), InputType::Image(_));
175 } else {
176 return true;
178 }
179 }
180 return false;
181 },
182 RadioListMode::Images => {
183 return child.is::<HTMLImageElement>() &&
184 (child.get_id().is_some_and(|i| i == *name) ||
185 child.get_name().is_some_and(|n| n == *name));
186 },
187 }
188 }
189 false
190 }
191
192 pub(crate) fn nth_for_radio_list(
193 &self,
194 index: u32,
195 mode: RadioListMode,
196 name: &Atom,
197 ) -> Option<DomRoot<Node>> {
198 self.controls
199 .borrow()
200 .iter()
201 .filter(|n| HTMLFormElement::filter_for_radio_list(mode, n, name))
202 .nth(index as usize)
203 .map(|n| DomRoot::from_ref(n.upcast::<Node>()))
204 }
205
206 pub(crate) fn count_for_radio_list(&self, mode: RadioListMode, name: &Atom) -> u32 {
207 self.controls
208 .borrow()
209 .iter()
210 .filter(|n| HTMLFormElement::filter_for_radio_list(mode, n, name))
211 .count() as u32
212 }
213}
214
215impl HTMLFormElementMethods<crate::DomTypeHolder> for HTMLFormElement {
216 make_getter!(AcceptCharset, "accept-charset");
218
219 make_setter!(SetAcceptCharset, "accept-charset");
221
222 make_form_action_getter!(Action, "action");
224
225 make_setter!(SetAction, "action");
227
228 make_enumerated_getter!(
230 Autocomplete,
231 "autocomplete",
232 "on" | "off",
233 missing => "on",
234 invalid => "on"
235 );
236
237 make_setter!(SetAutocomplete, "autocomplete");
239
240 make_enumerated_getter!(
242 Enctype,
243 "enctype",
244 "application/x-www-form-urlencoded" | "text/plain" | "multipart/form-data",
245 missing => "application/x-www-form-urlencoded",
246 invalid => "application/x-www-form-urlencoded"
247 );
248
249 make_setter!(SetEnctype, "enctype");
251
252 fn Encoding(&self) -> DOMString {
254 self.Enctype()
255 }
256
257 fn SetEncoding(&self, cx: &mut JSContext, value: DOMString) {
259 self.SetEnctype(cx, value)
260 }
261
262 make_enumerated_getter!(
264 Method,
265 "method",
266 "get" | "post" | "dialog",
267 missing => "get",
268 invalid => "get"
269 );
270
271 make_setter!(SetMethod, "method");
273
274 make_getter!(Name, "name");
276
277 make_atomic_setter!(SetName, "name");
279
280 make_bool_getter!(NoValidate, "novalidate");
282
283 make_bool_setter!(SetNoValidate, "novalidate");
285
286 make_getter!(Target, "target");
288
289 make_setter!(SetTarget, "target");
291
292 make_getter!(Rel, "rel");
294
295 fn Submit(&self, cx: &mut js::context::JSContext) {
297 self.submit(
298 cx,
299 SubmittedFrom::FromForm,
300 FormSubmitterElement::Form(self),
301 );
302 }
303
304 fn RequestSubmit(
306 &self,
307 cx: &mut js::context::JSContext,
308 submitter: Option<&HTMLElement>,
309 ) -> Fallible<()> {
310 let submitter: FormSubmitterElement = match submitter {
311 Some(submitter_element) => {
312 let error_not_a_submit_button =
314 Err(Error::Type(c"submitter must be a submit button".to_owned()));
315
316 let element = match submitter_element.upcast::<Node>().type_id() {
317 NodeTypeId::Element(ElementTypeId::HTMLElement(element)) => element,
318 _ => {
319 return error_not_a_submit_button;
320 },
321 };
322
323 let submit_button = match element {
324 HTMLElementTypeId::HTMLInputElement => FormSubmitterElement::Input(
325 submitter_element
326 .downcast::<HTMLInputElement>()
327 .expect("Failed to downcast submitter elem to HTMLInputElement."),
328 ),
329 HTMLElementTypeId::HTMLButtonElement => FormSubmitterElement::Button(
330 submitter_element
331 .downcast::<HTMLButtonElement>()
332 .expect("Failed to downcast submitter elem to HTMLButtonElement."),
333 ),
334 _ => {
335 return error_not_a_submit_button;
336 },
337 };
338
339 if !submit_button.is_submit_button() {
340 return error_not_a_submit_button;
341 }
342
343 let submitters_owner = submit_button.form_owner();
344
345 let owner = match submitters_owner {
347 Some(owner) => owner,
348 None => {
349 return Err(Error::NotFound(None));
350 },
351 };
352
353 if *owner != *self {
354 return Err(Error::NotFound(None));
355 }
356
357 submit_button
358 },
359 None => {
360 FormSubmitterElement::Form(self)
362 },
363 };
364 self.submit(cx, SubmittedFrom::NotFromForm, submitter);
366 Ok(())
367 }
368
369 fn Reset(&self, cx: &mut js::context::JSContext) {
371 self.reset(cx, ResetFrom::FromForm);
372 }
373
374 fn Elements(&self, can_gc: CanGc) -> DomRoot<HTMLFormControlsCollection> {
376 #[derive(JSTraceable, MallocSizeOf)]
377 struct ElementsFilter {
378 form: DomRoot<HTMLFormElement>,
379 }
380 impl CollectionFilter for ElementsFilter {
381 fn filter<'a>(&self, elem: &'a Element, _root: &'a Node) -> bool {
382 let form_owner = match elem.upcast::<Node>().type_id() {
383 NodeTypeId::Element(ElementTypeId::HTMLElement(t)) => match t {
384 HTMLElementTypeId::HTMLButtonElement => {
385 elem.downcast::<HTMLButtonElement>().unwrap().form_owner()
386 },
387 HTMLElementTypeId::HTMLFieldSetElement => {
388 elem.downcast::<HTMLFieldSetElement>().unwrap().form_owner()
389 },
390 HTMLElementTypeId::HTMLInputElement => {
391 let input_elem = elem.downcast::<HTMLInputElement>().unwrap();
392 if matches!(*input_elem.input_type(), InputType::Image(_)) {
393 return false;
394 }
395 input_elem.form_owner()
396 },
397 HTMLElementTypeId::HTMLObjectElement => {
398 elem.downcast::<HTMLObjectElement>().unwrap().form_owner()
399 },
400 HTMLElementTypeId::HTMLOutputElement => {
401 elem.downcast::<HTMLOutputElement>().unwrap().form_owner()
402 },
403 HTMLElementTypeId::HTMLSelectElement => {
404 elem.downcast::<HTMLSelectElement>().unwrap().form_owner()
405 },
406 HTMLElementTypeId::HTMLTextAreaElement => {
407 elem.downcast::<HTMLTextAreaElement>().unwrap().form_owner()
408 },
409 HTMLElementTypeId::HTMLElement => {
410 let html_element = elem.downcast::<HTMLElement>().unwrap();
411 if html_element.is_form_associated_custom_element() {
412 html_element.form_owner()
413 } else {
414 return false;
415 }
416 },
417 _ => {
418 debug_assert!(
419 !elem.downcast::<HTMLElement>().unwrap().is_listed_element()
420 );
421 return false;
422 },
423 },
424 _ => return false,
425 };
426
427 match form_owner {
428 Some(form_owner) => form_owner == self.form,
429 None => false,
430 }
431 }
432 }
433 DomRoot::from_ref(self.elements.init_once(|| {
434 let filter = Box::new(ElementsFilter {
435 form: DomRoot::from_ref(self),
436 });
437 let window = self.owner_window();
438 HTMLFormControlsCollection::new(&window, self, filter, can_gc)
439 }))
440 }
441
442 fn Length(&self) -> u32 {
444 self.Elements(CanGc::deprecated_note()).Length()
445 }
446
447 fn IndexedGetter(&self, index: u32, can_gc: CanGc) -> Option<DomRoot<Element>> {
449 let elements = self.Elements(can_gc);
450 elements.IndexedGetter(index)
451 }
452
453 fn NamedGetter(&self, name: DOMString, can_gc: CanGc) -> Option<RadioNodeListOrElement> {
455 let window = self.owner_window();
456
457 let name = Atom::from(name);
458
459 let mut candidates =
461 RadioNodeList::new_controls_except_image_inputs(&window, self, &name, can_gc);
462 let mut candidates_length = candidates.Length();
463
464 if candidates_length == 0 {
466 candidates = RadioNodeList::new_images(&window, self, &name, can_gc);
467 candidates_length = candidates.Length();
468 }
469
470 let mut past_names_map = self.past_names_map.borrow_mut();
471
472 if candidates_length == 0 {
474 if past_names_map.contains_key(&name) {
475 return Some(RadioNodeListOrElement::Element(DomRoot::from_ref(
476 &*past_names_map.get(&name).unwrap().0,
477 )));
478 }
479 return None;
480 }
481
482 if candidates_length > 1 {
484 return Some(RadioNodeListOrElement::RadioNodeList(candidates));
485 }
486
487 let element_node = candidates.upcast::<NodeList>().Item(0).unwrap();
490 past_names_map.insert(
491 name,
492 (
493 Dom::from_ref(element_node.downcast::<Element>().unwrap()),
494 NoTrace(self.current_name_generation.get() + 1),
495 ),
496 );
497 self.current_name_generation
498 .set(self.current_name_generation.get() + 1);
499
500 Some(RadioNodeListOrElement::Element(DomRoot::from_ref(
502 element_node.downcast::<Element>().unwrap(),
503 )))
504 }
505
506 fn SetRel(&self, cx: &mut JSContext, rel: DOMString) {
508 self.upcast::<Element>().set_tokenlist_attribute(
509 &local_name!("rel"),
510 rel,
511 CanGc::from_cx(cx),
512 );
513 }
514
515 fn RelList(&self, can_gc: CanGc) -> DomRoot<DOMTokenList> {
517 self.rel_list.or_init(|| {
518 DOMTokenList::new(
519 self.upcast(),
520 &local_name!("rel"),
521 Some(vec![
522 Atom::from("noopener"),
523 Atom::from("noreferrer"),
524 Atom::from("opener"),
525 ]),
526 can_gc,
527 )
528 })
529 }
530
531 fn SupportedPropertyNames(&self) -> Vec<DOMString> {
533 #[derive(Debug, Eq, Ord, PartialEq, PartialOrd)]
535 enum SourcedNameSource {
536 Id,
537 Name,
538 Past(usize),
539 }
540
541 impl SourcedNameSource {
542 fn is_past(&self) -> bool {
543 matches!(self, SourcedNameSource::Past(..))
544 }
545 }
546
547 struct SourcedName {
548 name: Atom,
549 element: DomRoot<Element>,
550 source: SourcedNameSource,
551 }
552
553 let mut sourced_names_vec: Vec<SourcedName> = Vec::new();
554
555 for child in self.controls.borrow().iter() {
557 if child
558 .downcast::<HTMLElement>()
559 .is_some_and(|c| c.is_listed_element())
560 {
561 if let Some(id_atom) = child.get_id() {
562 let entry = SourcedName {
563 name: id_atom,
564 element: DomRoot::from_ref(child),
565 source: SourcedNameSource::Id,
566 };
567 sourced_names_vec.push(entry);
568 }
569 if let Some(name_atom) = child.get_name() {
570 let entry = SourcedName {
571 name: name_atom,
572 element: DomRoot::from_ref(child),
573 source: SourcedNameSource::Name,
574 };
575 sourced_names_vec.push(entry);
576 }
577 }
578 }
579
580 for child in self.controls.borrow().iter() {
582 if child.is::<HTMLImageElement>() {
583 if let Some(id_atom) = child.get_id() {
584 let entry = SourcedName {
585 name: id_atom,
586 element: DomRoot::from_ref(child),
587 source: SourcedNameSource::Id,
588 };
589 sourced_names_vec.push(entry);
590 }
591 if let Some(name_atom) = child.get_name() {
592 let entry = SourcedName {
593 name: name_atom,
594 element: DomRoot::from_ref(child),
595 source: SourcedNameSource::Name,
596 };
597 sourced_names_vec.push(entry);
598 }
599 }
600 }
601
602 let past_names_map = self.past_names_map.borrow();
604 for (key, val) in past_names_map.iter() {
605 let entry = SourcedName {
606 name: key.clone(),
607 element: DomRoot::from_ref(&*val.0),
608 source: SourcedNameSource::Past(self.current_name_generation.get() - val.1.0),
609 };
610 sourced_names_vec.push(entry);
611 }
612
613 sourced_names_vec.sort_by(|a, b| {
625 if a.element
626 .upcast::<Node>()
627 .CompareDocumentPosition(b.element.upcast::<Node>()) ==
628 0
629 {
630 if a.source.is_past() && b.source.is_past() {
631 b.source.cmp(&a.source)
632 } else {
633 a.source.cmp(&b.source)
634 }
635 } else if a
636 .element
637 .upcast::<Node>()
638 .CompareDocumentPosition(b.element.upcast::<Node>()) &
639 NodeConstants::DOCUMENT_POSITION_FOLLOWING ==
640 NodeConstants::DOCUMENT_POSITION_FOLLOWING
641 {
642 std::cmp::Ordering::Less
643 } else {
644 std::cmp::Ordering::Greater
645 }
646 });
647
648 sourced_names_vec.retain(|sn| !sn.name.to_string().is_empty());
650
651 let mut names_vec: Vec<DOMString> = Vec::new();
653 for elem in sourced_names_vec.iter() {
654 if !names_vec.iter().any(|name| *name == *elem.name) {
655 names_vec.push(DOMString::from(&*elem.name));
656 }
657 }
658
659 names_vec
660 }
661
662 fn CheckValidity(&self, cx: &mut JSContext) -> bool {
664 self.static_validation(cx).is_ok()
665 }
666
667 fn ReportValidity(&self, cx: &mut JSContext) -> bool {
669 self.interactive_validation(cx).is_ok()
670 }
671}
672
673#[derive(Clone, Copy, MallocSizeOf, PartialEq)]
674pub(crate) enum SubmittedFrom {
675 FromForm,
676 NotFromForm,
677}
678
679#[derive(Clone, Copy, MallocSizeOf)]
680pub(crate) enum ResetFrom {
681 FromForm,
682 NotFromForm,
683}
684
685impl HTMLFormElement {
686 fn pick_encoding(&self) -> &'static Encoding {
688 if self
690 .upcast::<Element>()
691 .has_attribute(&local_name!("accept-charset"))
692 {
693 let input = self
695 .upcast::<Element>()
696 .get_string_attribute(&local_name!("accept-charset"));
697
698 let input = input.str();
700 let mut candidate_encodings =
701 split_html_space_chars(&input).filter_map(|c| Encoding::for_label(c.as_bytes()));
702
703 return candidate_encodings.next().unwrap_or(UTF_8);
705 }
706
707 self.owner_document().encoding()
709 }
710
711 fn encode_plaintext(&self, form_data: &mut [FormDatum]) -> String {
713 let mut result = String::new();
715
716 for entry in form_data.iter() {
718 let value = match &entry.value {
719 FormDatumValue::File(f) => f.name(),
720 FormDatumValue::String(s) => s,
721 };
722 result.push_str(&format!("{}={}\r\n", entry.name, value));
723 }
724
725 result
727 }
728
729 pub(crate) fn update_validity(&self, can_gc: CanGc) {
730 let is_any_invalid = self
731 .controls
732 .borrow()
733 .iter()
734 .any(|control| control.is_invalid(false, can_gc));
735
736 self.upcast::<Element>()
737 .set_state(ElementState::VALID, !is_any_invalid);
738 self.upcast::<Element>()
739 .set_state(ElementState::INVALID, is_any_invalid);
740 }
741
742 pub(crate) fn submit(
744 &self,
745 cx: &mut js::context::JSContext,
746 submit_method_flag: SubmittedFrom,
747 submitter: FormSubmitterElement,
748 ) {
749 if self.upcast::<Element>().cannot_navigate() {
751 return;
752 }
753
754 if self.constructing_entry_list.get() {
756 return;
757 }
758 let doc = self.owner_document();
760
761 if doc.has_active_sandboxing_flag(SandboxingFlagSet::SANDBOXED_FORMS_BROWSING_CONTEXT_FLAG)
764 {
765 return;
766 }
767
768 let base = doc.base_url();
769 if submit_method_flag == SubmittedFrom::NotFromForm {
772 if self.firing_submission_events.get() {
774 return;
775 }
776 self.firing_submission_events.set(true);
778 if !submitter.no_validate(self) && self.interactive_validation(cx).is_err() {
780 self.firing_submission_events.set(false);
781 return;
782 }
783 let submitter_button = match submitter {
787 FormSubmitterElement::Form(f) => {
788 if f == self {
789 None
790 } else {
791 Some(f.upcast::<HTMLElement>())
792 }
793 },
794 FormSubmitterElement::Input(i) => Some(i.upcast::<HTMLElement>()),
795 FormSubmitterElement::Button(b) => Some(b.upcast::<HTMLElement>()),
796 };
797
798 let event = SubmitEvent::new(
800 self.global().as_window(),
801 atom!("submit"),
802 true,
803 true,
804 submitter_button.map(DomRoot::from_ref),
805 CanGc::from_cx(cx),
806 );
807 let event = event.upcast::<Event>();
808 event.fire(self.upcast::<EventTarget>(), CanGc::from_cx(cx));
809
810 self.firing_submission_events.set(false);
812 if event.DefaultPrevented() {
814 return;
815 }
816 if self.upcast::<Element>().cannot_navigate() {
818 return;
819 }
820 }
821
822 let encoding = self.pick_encoding();
824
825 let mut form_data =
827 match self.get_form_dataset(Some(submitter), Some(encoding), CanGc::from_cx(cx)) {
828 Some(form_data) => form_data,
829 None => return,
830 };
831
832 if self.upcast::<Element>().cannot_navigate() {
834 return;
835 }
836
837 let method = submitter.method();
839 let mut action = submitter.action();
844
845 if action.is_empty() {
847 action = DOMString::from(base.as_str());
848 }
849 let action_components = match doc.encoding_parse_a_url(&action.str()) {
851 Ok(url) => url,
852 Err(_) => return,
854 };
855 let scheme = action_components.scheme().to_owned();
857 let enctype = submitter.enctype();
859
860 let form_target_attribute = submitter.target();
863 let form_target = if submitter.is_submit_button() &&
864 valid_navigable_target_name_or_keyword(&form_target_attribute)
865 {
866 Some(form_target_attribute)
867 } else {
868 None
870 };
871 let form_owner = submitter.form_owner();
873 let form = form_owner.as_deref().unwrap_or(self);
874 let target = get_element_target(form.upcast::<Element>(), form_target);
875
876 let noopener = self.relations.get().get_element_noopener(target.as_ref());
878
879 let source = doc.browsing_context().unwrap();
882 let (maybe_chosen, _new) =
883 source.choose_browsing_context(target.unwrap_or_default(), noopener);
884
885 let Some(chosen) = maybe_chosen else {
886 return;
888 };
889 let target_document = match chosen.document() {
890 Some(doc) => doc,
891 None => return,
892 };
893
894 let history_handling = if doc == target_document && !doc.completely_loaded() {
898 NavigationHistoryBehavior::Replace
899 } else {
900 NavigationHistoryBehavior::Auto
901 };
902
903 let target_window = target_document.window();
904 let mut load_data = LoadData::new(
905 LoadOrigin::Script(doc.origin().snapshot()),
906 action_components,
907 target_document.about_base_url(),
908 None,
909 target_window.as_global_scope().get_referrer(),
910 target_document.get_referrer_policy(),
911 Some(target_window.as_global_scope().is_secure_context()),
912 Some(target_document.insecure_requests_policy()),
913 target_document.has_trustworthy_ancestor_origin(),
914 target_document.creation_sandboxing_flag_set_considering_parent_iframe(),
915 );
916
917 match (&*scheme, method) {
921 (_, FormMethod::Dialog) => {
922 },
925 ("http", FormMethod::Get) | ("https", FormMethod::Get) | ("data", FormMethod::Get) => {
927 load_data
928 .headers
929 .typed_insert(ContentType::from(mime::APPLICATION_WWW_FORM_URLENCODED));
930 self.mutate_action_url(
931 &mut form_data,
932 load_data,
933 encoding,
934 target_window,
935 history_handling,
936 );
937 },
938 ("http", FormMethod::Post) | ("https", FormMethod::Post) => {
940 load_data.method = Method::POST;
941 self.submit_entity_body(
942 cx,
943 &mut form_data,
944 load_data,
945 enctype,
946 encoding,
947 target_window,
948 history_handling,
949 );
950 },
951 ("file", _) |
953 ("about", _) |
954 ("data", FormMethod::Post) |
955 ("ftp", _) |
956 ("javascript", _) => {
957 self.plan_to_navigate(load_data, target_window, history_handling);
958 },
959 ("mailto", FormMethod::Post) => {
960 },
963 ("mailto", FormMethod::Get) => {
964 },
967 _ => (),
968 }
969 }
970
971 fn mutate_action_url(
973 &self,
974 form_data: &mut [FormDatum],
975 mut load_data: LoadData,
976 encoding: &'static Encoding,
977 target: &Window,
978 history_handling: NavigationHistoryBehavior,
979 ) {
980 let charset = encoding.name();
981
982 self.set_url_query_pairs(
983 &mut load_data.url,
984 form_data
985 .iter()
986 .map(|field| (field.name.str(), field.replace_value(charset))),
987 );
988
989 self.plan_to_navigate(load_data, target, history_handling);
990 }
991
992 #[allow(clippy::too_many_arguments)]
994 fn submit_entity_body(
995 &self,
996 cx: &mut js::context::JSContext,
997 form_data: &mut [FormDatum],
998 mut load_data: LoadData,
999 enctype: FormEncType,
1000 encoding: &'static Encoding,
1001 target: &Window,
1002 history_handling: NavigationHistoryBehavior,
1003 ) {
1004 let boundary = generate_boundary();
1005 let bytes = match enctype {
1006 FormEncType::UrlEncoded => {
1007 let charset = encoding.name();
1008 load_data
1009 .headers
1010 .typed_insert(ContentType::from(mime::APPLICATION_WWW_FORM_URLENCODED));
1011
1012 let mut url = load_data.url.clone();
1013 self.set_url_query_pairs(
1014 &mut url,
1015 form_data
1016 .iter()
1017 .map(|field| (field.name.str(), field.replace_value(charset))),
1018 );
1019
1020 url.query().unwrap_or("").to_string().into_bytes()
1021 },
1022 FormEncType::MultipartFormData => {
1023 let mime: Mime = format!("multipart/form-data; boundary={}", boundary)
1024 .parse()
1025 .unwrap();
1026 load_data.headers.typed_insert(ContentType::from(mime));
1027 encode_multipart_form_data(form_data, boundary, encoding)
1028 },
1029 FormEncType::TextPlain => {
1030 load_data
1031 .headers
1032 .typed_insert(ContentType::from(mime::TEXT_PLAIN));
1033 self.encode_plaintext(form_data).into_bytes()
1034 },
1035 };
1036
1037 let global = self.global();
1038
1039 let request_body = bytes
1040 .extract(cx, &global, false)
1041 .expect("Couldn't extract body.")
1042 .into_net_request_body()
1043 .0;
1044 load_data.data = Some(request_body);
1045
1046 self.plan_to_navigate(load_data, target, history_handling);
1047 }
1048
1049 fn set_url_query_pairs<T>(
1050 &self,
1051 url: &mut servo_url::ServoUrl,
1052 pairs: impl Iterator<Item = (T, String)>,
1053 ) where
1054 T: AsRef<str>,
1055 {
1056 let encoding = self.pick_encoding();
1057 url.as_mut_url()
1058 .query_pairs_mut()
1059 .encoding_override(Some(&|s| encoding.encode(s).0))
1060 .clear()
1061 .extend_pairs(pairs);
1062 }
1063
1064 fn plan_to_navigate(
1066 &self,
1067 mut load_data: LoadData,
1068 target: &Window,
1069 history_handling: NavigationHistoryBehavior,
1070 ) {
1071 let elem = self.upcast::<Element>();
1076 let referrer = match elem.get_attribute(&local_name!("rel")) {
1077 Some(ref link_types) if link_types.Value().contains("noreferrer") => {
1078 Referrer::NoReferrer
1079 },
1080 _ => target.as_global_scope().get_referrer(),
1081 };
1082
1083 self.planned_navigation
1086 .set(self.planned_navigation.get().wrapping_add(1));
1087 let planned_navigation = self.planned_navigation.get();
1088
1089 let ongoing_navigation = target.set_ongoing_navigation();
1105
1106 let referrer_policy = target.Document().get_referrer_policy();
1107 load_data.creator_pipeline_id = Some(target.pipeline_id());
1108 load_data.referrer = referrer;
1109 load_data.referrer_policy = referrer_policy;
1110
1111 if let Some(window_proxy) = target.undiscarded_window_proxy() {
1114 if let Some(frame) = window_proxy
1115 .frame_element()
1116 .and_then(|e| e.downcast::<HTMLIFrameElement>())
1117 {
1118 frame.note_pending_navigation()
1119 }
1120 }
1121
1122 let form = Trusted::new(self);
1125 let window = Trusted::new(target);
1126 let task = task!(navigate_to_form_planned_navigation: move |cx| {
1127 if planned_navigation != form.root().planned_navigation.get() {
1131 return;
1132 }
1133
1134 if ongoing_navigation != window.root().ongoing_navigation() {
1137 return;
1138 }
1139
1140 navigate(
1142 cx,
1143 &window.root(),
1144 history_handling,
1145 false,
1146 load_data,
1147 )
1148 });
1149
1150 target
1155 .global()
1156 .task_manager()
1157 .dom_manipulation_task_source()
1158 .queue(task)
1159 }
1160
1161 fn interactive_validation(&self, cx: &mut js::context::JSContext) -> Result<(), ()> {
1164 let unhandled_invalid_controls = match self.static_validation(cx) {
1169 Ok(()) => return Ok(()),
1170 Err(err) => err,
1171 };
1172
1173 let mut first = true;
1176
1177 for elem in unhandled_invalid_controls {
1178 if let Some(validatable) = elem.as_maybe_validatable() {
1179 error!("Validation error: {}", validatable.validation_message());
1180 }
1181 if first {
1182 if let Some(html_elem) = elem.downcast::<HTMLElement>() {
1183 html_elem.Focus(cx, &FocusOptions::default());
1190 first = false;
1191 }
1192 }
1193 }
1194
1195 Err(())
1199 }
1200
1201 fn static_validation(
1204 &self,
1205 cx: &mut js::context::JSContext,
1206 ) -> Result<(), Vec<DomRoot<Element>>> {
1207 let invalid_controls = self
1209 .controls
1210 .borrow()
1211 .iter()
1212 .filter_map(|field| {
1213 if let Some(element) = field.downcast::<Element>() {
1214 if element.is_invalid(true, CanGc::from_cx(cx)) {
1215 Some(DomRoot::from_ref(element))
1216 } else {
1217 None
1218 }
1219 } else {
1220 None
1221 }
1222 })
1223 .collect::<Vec<DomRoot<Element>>>();
1224 if invalid_controls.is_empty() {
1226 return Ok(());
1227 }
1228 let unhandled_invalid_controls = invalid_controls
1230 .into_iter()
1231 .filter_map(|field| {
1232 let not_canceled = field
1235 .upcast::<EventTarget>()
1236 .fire_cancelable_event(cx, atom!("invalid"));
1237 if not_canceled {
1239 return Some(field);
1240 }
1241 None
1242 })
1243 .collect::<Vec<DomRoot<Element>>>();
1244 Err(unhandled_invalid_controls)
1246 }
1247
1248 fn get_unclean_dataset(
1253 &self,
1254 submitter: Option<FormSubmitterElement>,
1255 encoding: Option<&'static Encoding>,
1256 can_gc: CanGc,
1257 ) -> Vec<FormDatum> {
1258 let mut data_set = Vec::new();
1259 for child in self.controls.borrow().iter() {
1260 if child.disabled_state() {
1262 continue;
1263 }
1264 let child = child.upcast::<Node>();
1265
1266 if child.ancestors().any(|a| a.is::<HTMLDataListElement>()) {
1268 continue;
1269 }
1270 if let NodeTypeId::Element(ElementTypeId::HTMLElement(element)) = child.type_id() {
1271 match element {
1272 HTMLElementTypeId::HTMLInputElement => {
1273 let input = child.downcast::<HTMLInputElement>().unwrap();
1274 data_set.append(&mut input.form_datums(submitter, encoding));
1275 },
1276 HTMLElementTypeId::HTMLButtonElement => {
1277 let button = child.downcast::<HTMLButtonElement>().unwrap();
1278 if let Some(datum) = button.form_datum(submitter) {
1279 data_set.push(datum);
1280 }
1281 },
1282 HTMLElementTypeId::HTMLObjectElement => {
1283 },
1285 HTMLElementTypeId::HTMLSelectElement => {
1286 let select = child.downcast::<HTMLSelectElement>().unwrap();
1287 select.push_form_data(&mut data_set);
1288 },
1289 HTMLElementTypeId::HTMLTextAreaElement => {
1290 let textarea = child.downcast::<HTMLTextAreaElement>().unwrap();
1291 let name = textarea.Name();
1292 if !name.is_empty() {
1293 data_set.push(FormDatum {
1294 ty: textarea.Type(),
1295 name,
1296 value: FormDatumValue::String(textarea.Value()),
1297 });
1298 }
1299 },
1300 HTMLElementTypeId::HTMLElement => {
1301 let custom = child.downcast::<HTMLElement>().unwrap();
1302 if custom.is_form_associated_custom_element() {
1303 let internals =
1305 custom.upcast::<Element>().ensure_element_internals(can_gc);
1306 internals.perform_entry_construction(&mut data_set);
1307 }
1309 },
1310 _ => (),
1311 }
1312 }
1313
1314 let child_element = child.downcast::<Element>().unwrap();
1318 let input_matches = child_element
1319 .downcast::<HTMLInputElement>()
1320 .is_some_and(|input| {
1321 matches!(
1322 *input.input_type(),
1323 InputType::Text(_) | InputType::Search(_)
1324 )
1325 });
1326 let textarea_matches = child_element.is::<HTMLTextAreaElement>();
1327 let dirname = child_element.get_string_attribute(&local_name!("dirname"));
1328 if (input_matches || textarea_matches) && !dirname.is_empty() {
1329 let dir = DOMString::from(child_element.directionality());
1330 data_set.push(FormDatum {
1331 ty: DOMString::from("string"),
1332 name: dirname,
1333 value: FormDatumValue::String(dir),
1334 });
1335 }
1336 }
1337 data_set
1338 }
1339
1340 pub(crate) fn get_form_dataset(
1342 &self,
1343 submitter: Option<FormSubmitterElement>,
1344 encoding: Option<&'static Encoding>,
1345 can_gc: CanGc,
1346 ) -> Option<Vec<FormDatum>> {
1347 if self.constructing_entry_list.get() {
1349 return None;
1350 }
1351
1352 self.constructing_entry_list.set(true);
1354
1355 let ret = self.get_unclean_dataset(submitter, encoding, can_gc);
1357
1358 let window = self.owner_window();
1359
1360 let form_data = FormData::new(Some(ret), &window.global(), can_gc);
1362
1363 let event = FormDataEvent::new(
1365 &window,
1366 atom!("formdata"),
1367 EventBubbles::Bubbles,
1368 EventCancelable::NotCancelable,
1369 &form_data,
1370 can_gc,
1371 );
1372
1373 event
1374 .upcast::<Event>()
1375 .fire(self.upcast::<EventTarget>(), can_gc);
1376
1377 self.constructing_entry_list.set(false);
1379
1380 Some(form_data.datums())
1382 }
1383
1384 pub(crate) fn reset(&self, cx: &mut js::context::JSContext, _reset_method_flag: ResetFrom) {
1386 if self.marked_for_reset.get() {
1388 return;
1389 } else {
1390 self.marked_for_reset.set(true);
1391 }
1392
1393 let reset = self
1397 .upcast::<EventTarget>()
1398 .fire_bubbling_cancelable_event(cx, atom!("reset"));
1399 if !reset {
1400 return;
1401 }
1402
1403 let controls: Vec<_> = self
1404 .controls
1405 .borrow()
1406 .iter()
1407 .map(|c| c.as_rooted())
1408 .collect();
1409
1410 for child in controls {
1411 child.reset(CanGc::from_cx(cx));
1412 }
1413 self.marked_for_reset.set(false);
1414 }
1415
1416 fn add_control<T: ?Sized + FormControl>(&self, control: &T, can_gc: CanGc) {
1417 {
1418 let root = self.upcast::<Element>().root_element();
1419 let root = root.upcast::<Node>();
1420 let mut controls = self.controls.borrow_mut();
1421
1422 let control_element = control.to_element();
1428 if control_element.upcast::<Node>().has_parent() {
1429 controls.insert_pre_order(control_element, root);
1430 } else {
1431 controls.push(Dom::from_ref(control_element));
1432 }
1433 }
1434 self.update_validity(can_gc);
1435 }
1436
1437 fn remove_control<T: ?Sized + FormControl>(&self, control: &T, can_gc: CanGc) {
1438 {
1439 let control = control.to_element();
1440 let mut controls = self.controls.borrow_mut();
1441 controls
1442 .iter()
1443 .position(|c| &**c == control)
1444 .map(|idx| controls.remove(idx));
1445
1446 let mut past_names_map = self.past_names_map.borrow_mut();
1451 past_names_map.0.retain(|_k, v| v.0 != control);
1452 }
1453 self.update_validity(can_gc);
1454 }
1455}
1456
1457impl Element {
1458 pub(crate) fn is_resettable(&self) -> bool {
1459 let NodeTypeId::Element(ElementTypeId::HTMLElement(element_type)) =
1460 self.upcast::<Node>().type_id()
1461 else {
1462 return false;
1463 };
1464 matches!(
1465 element_type,
1466 HTMLElementTypeId::HTMLInputElement |
1467 HTMLElementTypeId::HTMLSelectElement |
1468 HTMLElementTypeId::HTMLTextAreaElement |
1469 HTMLElementTypeId::HTMLOutputElement |
1470 HTMLElementTypeId::HTMLElement
1471 )
1472 }
1473
1474 #[expect(unsafe_code)]
1475 pub(crate) fn reset(&self, _can_gc: CanGc) {
1476 let mut cx = unsafe { script_bindings::script_runtime::temp_cx() };
1478 let cx = &mut cx;
1479
1480 if !self.is_resettable() {
1481 return;
1482 }
1483
1484 if let Some(input_element) = self.downcast::<HTMLInputElement>() {
1485 input_element.reset(cx);
1486 } else if let Some(select_element) = self.downcast::<HTMLSelectElement>() {
1487 select_element.reset();
1488 } else if let Some(textarea_element) = self.downcast::<HTMLTextAreaElement>() {
1489 textarea_element.reset(CanGc::from_cx(cx));
1490 } else if let Some(output_element) = self.downcast::<HTMLOutputElement>() {
1491 output_element.reset(cx);
1492 } else if let Some(html_element) = self.downcast::<HTMLElement>() {
1493 if html_element.is_form_associated_custom_element() {
1494 ScriptThread::enqueue_callback_reaction(
1495 html_element.upcast::<Element>(),
1496 CallbackReaction::FormReset,
1497 None,
1498 )
1499 }
1500 }
1501 }
1502}
1503
1504#[derive(Clone, JSTraceable, MallocSizeOf)]
1505pub(crate) enum FormDatumValue {
1506 File(DomRoot<File>),
1507 String(DOMString),
1508}
1509
1510#[derive(Clone, JSTraceable, MallocSizeOf)]
1511pub(crate) struct FormDatum {
1512 pub(crate) ty: DOMString,
1513 pub(crate) name: DOMString,
1514 pub(crate) value: FormDatumValue,
1515}
1516
1517impl FormDatum {
1518 pub(crate) fn replace_value(&self, charset: &str) -> String {
1519 if self.name.to_ascii_lowercase() == "_charset_" && self.ty == "hidden" {
1520 return charset.to_string();
1521 }
1522
1523 match self.value {
1524 FormDatumValue::File(ref f) => String::from(f.name().clone()),
1525 FormDatumValue::String(ref s) => String::from(s.clone()),
1526 }
1527 }
1528}
1529
1530#[derive(Clone, Copy, MallocSizeOf)]
1531pub(crate) enum FormEncType {
1532 TextPlain,
1533 UrlEncoded,
1534 MultipartFormData,
1535}
1536
1537#[derive(Clone, Copy, MallocSizeOf)]
1538pub(crate) enum FormMethod {
1539 Get,
1540 Post,
1541 Dialog,
1542}
1543
1544#[derive(Clone, Copy, MallocSizeOf)]
1546pub(crate) enum FormSubmitterElement<'a> {
1547 Form(&'a HTMLFormElement),
1548 Input(&'a HTMLInputElement),
1549 Button(&'a HTMLButtonElement),
1550 }
1553
1554impl FormSubmitterElement<'_> {
1555 fn action(&self) -> DOMString {
1556 match *self {
1557 FormSubmitterElement::Form(form) => form.Action(),
1558 FormSubmitterElement::Input(input_element) => input_element.get_form_attribute(
1559 &local_name!("formaction"),
1560 |i| i.FormAction(),
1561 |f| f.Action(),
1562 ),
1563 FormSubmitterElement::Button(button_element) => button_element.get_form_attribute(
1564 &local_name!("formaction"),
1565 |i| i.FormAction(),
1566 |f| f.Action(),
1567 ),
1568 }
1569 }
1570
1571 fn enctype(&self) -> FormEncType {
1572 let attr = match *self {
1573 FormSubmitterElement::Form(form) => form.Enctype(),
1574 FormSubmitterElement::Input(input_element) => input_element.get_form_attribute(
1575 &local_name!("formenctype"),
1576 |i| i.FormEnctype(),
1577 |f| f.Enctype(),
1578 ),
1579 FormSubmitterElement::Button(button_element) => button_element.get_form_attribute(
1580 &local_name!("formenctype"),
1581 |i| i.FormEnctype(),
1582 |f| f.Enctype(),
1583 ),
1584 };
1585 match_domstring_ascii!(attr,
1588 "multipart/form-data" => FormEncType::MultipartFormData,
1589 "text/plain" => FormEncType::TextPlain,
1590 _ => FormEncType::UrlEncoded,
1591 )
1592 }
1593
1594 fn method(&self) -> FormMethod {
1595 let attr = match *self {
1596 FormSubmitterElement::Form(form) => form.Method(),
1597 FormSubmitterElement::Input(input_element) => input_element.get_form_attribute(
1598 &local_name!("formmethod"),
1599 |i| i.FormMethod(),
1600 |f| f.Method(),
1601 ),
1602 FormSubmitterElement::Button(button_element) => button_element.get_form_attribute(
1603 &local_name!("formmethod"),
1604 |i| i.FormMethod(),
1605 |f| f.Method(),
1606 ),
1607 };
1608 match_domstring_ascii!(attr,
1609 "dialog" => FormMethod::Dialog,
1610 "post" => FormMethod::Post,
1611 _ => FormMethod::Get,
1612 )
1613 }
1614
1615 fn target(&self) -> DOMString {
1616 match *self {
1617 FormSubmitterElement::Form(form) => form.Target(),
1618 FormSubmitterElement::Input(input_element) => input_element.get_form_attribute(
1619 &local_name!("formtarget"),
1620 |i| i.FormTarget(),
1621 |f| f.Target(),
1622 ),
1623 FormSubmitterElement::Button(button_element) => button_element.get_form_attribute(
1624 &local_name!("formtarget"),
1625 |i| i.FormTarget(),
1626 |f| f.Target(),
1627 ),
1628 }
1629 }
1630
1631 fn no_validate(&self, _form_owner: &HTMLFormElement) -> bool {
1632 match *self {
1633 FormSubmitterElement::Form(form) => form.NoValidate(),
1634 FormSubmitterElement::Input(input_element) => input_element.get_form_boolean_attribute(
1635 &local_name!("formnovalidate"),
1636 |i| i.FormNoValidate(),
1637 |f| f.NoValidate(),
1638 ),
1639 FormSubmitterElement::Button(button_element) => button_element
1640 .get_form_boolean_attribute(
1641 &local_name!("formnovalidate"),
1642 |i| i.FormNoValidate(),
1643 |f| f.NoValidate(),
1644 ),
1645 }
1646 }
1647
1648 pub(crate) fn is_submit_button(&self) -> bool {
1650 match *self {
1651 FormSubmitterElement::Input(input_element) => input_element.is_submit_button(),
1654 FormSubmitterElement::Button(button_element) => button_element.is_submit_button(),
1656 _ => false,
1657 }
1658 }
1659
1660 pub(crate) fn form_owner(&self) -> Option<DomRoot<HTMLFormElement>> {
1662 match *self {
1663 FormSubmitterElement::Button(button_el) => button_el.form_owner(),
1664 FormSubmitterElement::Input(input_el) => input_el.form_owner(),
1665 _ => None,
1666 }
1667 }
1668}
1669
1670pub(crate) trait FormControl: DomObject<ReflectorType = ()> + NodeTraits {
1671 fn form_owner(&self) -> Option<DomRoot<HTMLFormElement>>;
1672
1673 fn set_form_owner(&self, form: Option<&HTMLFormElement>);
1674
1675 fn to_element(&self) -> ∈
1676
1677 fn is_listed(&self) -> bool {
1678 true
1679 }
1680
1681 fn set_form_owner_from_parser(&self, form: &HTMLFormElement, can_gc: CanGc) {
1686 let elem = self.to_element();
1687 let node = elem.upcast::<Node>();
1688 node.set_flag(NodeFlags::PARSER_ASSOCIATED_FORM_OWNER, true);
1689 form.add_control(self, can_gc);
1690 self.set_form_owner(Some(form));
1691 }
1692
1693 fn reset_form_owner(&self, can_gc: CanGc) {
1695 let elem = self.to_element();
1696 let node = elem.upcast::<Node>();
1697 let old_owner = self.form_owner();
1698 let has_form_id = elem.has_attribute(&local_name!("form"));
1699 let nearest_form_ancestor = node
1700 .ancestors()
1701 .find_map(DomRoot::downcast::<HTMLFormElement>);
1702
1703 if old_owner.is_some() &&
1705 !(self.is_listed() && has_form_id) &&
1706 nearest_form_ancestor == old_owner
1707 {
1708 return;
1709 }
1710
1711 let new_owner = if self.is_listed() && has_form_id && elem.is_connected() {
1713 let form_id = elem.get_string_attribute(&local_name!("form"));
1717 let first_relevant_element = if let Some(shadow_root) = self.containing_shadow_root() {
1718 shadow_root
1719 .upcast::<DocumentFragment>()
1720 .GetElementById(form_id)
1721 } else {
1722 node.owner_document().GetElementById(form_id)
1723 };
1724
1725 first_relevant_element.and_then(DomRoot::downcast::<HTMLFormElement>)
1726 } else {
1727 nearest_form_ancestor
1729 };
1730
1731 if old_owner != new_owner {
1732 if let Some(o) = old_owner {
1733 o.remove_control(self, can_gc);
1734 }
1735 if let Some(ref new_owner) = new_owner {
1736 new_owner.add_control(self, can_gc);
1737 }
1738 if let Some(html_elem) = elem.downcast::<HTMLElement>() {
1740 if html_elem.is_form_associated_custom_element() {
1741 ScriptThread::enqueue_callback_reaction(
1742 elem,
1743 CallbackReaction::FormAssociated(
1744 new_owner.as_ref().map(|form| DomRoot::from_ref(&**form)),
1745 ),
1746 None,
1747 )
1748 }
1749 }
1750 self.set_form_owner(new_owner.as_deref());
1751 }
1752 }
1753
1754 fn form_attribute_mutated(&self, mutation: AttributeMutation, can_gc: CanGc) {
1756 match mutation {
1757 AttributeMutation::Set(..) => {
1758 self.register_if_necessary();
1759 },
1760 AttributeMutation::Removed => {
1761 self.unregister_if_necessary();
1762 },
1763 }
1764
1765 self.reset_form_owner(can_gc);
1766 }
1767
1768 fn register_if_necessary(&self) {
1770 let elem = self.to_element();
1771 let form_id = elem.get_string_attribute(&local_name!("form"));
1772 let node = elem.upcast::<Node>();
1773
1774 if self.is_listed() && !form_id.is_empty() && node.is_connected() {
1775 node.owner_document()
1776 .register_form_id_listener(form_id, self);
1777 }
1778 }
1779
1780 fn unregister_if_necessary(&self) {
1781 let elem = self.to_element();
1782 let form_id = elem.get_string_attribute(&local_name!("form"));
1783
1784 if self.is_listed() && !form_id.is_empty() {
1785 elem.owner_document()
1786 .unregister_form_id_listener(form_id, self);
1787 }
1788 }
1789
1790 fn bind_form_control_to_tree(&self, can_gc: CanGc) {
1792 let elem = self.to_element();
1793 let node = elem.upcast::<Node>();
1794
1795 let must_skip_reset = node.get_flag(NodeFlags::PARSER_ASSOCIATED_FORM_OWNER);
1800 node.set_flag(NodeFlags::PARSER_ASSOCIATED_FORM_OWNER, false);
1801
1802 if !must_skip_reset {
1803 self.form_attribute_mutated(
1804 AttributeMutation::Set(None, AttributeMutationReason::Directly),
1805 can_gc,
1806 );
1807 }
1808 }
1809
1810 fn unbind_form_control_from_tree(&self, can_gc: CanGc) {
1812 let elem = self.to_element();
1813 let has_form_attr = elem.has_attribute(&local_name!("form"));
1814 let same_subtree = self
1815 .form_owner()
1816 .is_none_or(|form| elem.is_in_same_home_subtree(&*form));
1817
1818 self.unregister_if_necessary();
1819
1820 if !same_subtree || (self.is_listed() && has_form_attr) {
1826 self.reset_form_owner(can_gc);
1827 }
1828 }
1829
1830 fn get_form_attribute<InputFn, OwnerFn>(
1831 &self,
1832 attr: &LocalName,
1833 input: InputFn,
1834 owner: OwnerFn,
1835 ) -> DOMString
1836 where
1837 InputFn: Fn(&Self) -> DOMString,
1838 OwnerFn: Fn(&HTMLFormElement) -> DOMString,
1839 Self: Sized,
1840 {
1841 if self.to_element().has_attribute(attr) {
1842 input(self)
1843 } else {
1844 self.form_owner().map_or(DOMString::new(), |t| owner(&t))
1845 }
1846 }
1847
1848 fn get_form_boolean_attribute<InputFn, OwnerFn>(
1849 &self,
1850 attr: &LocalName,
1851 input: InputFn,
1852 owner: OwnerFn,
1853 ) -> bool
1854 where
1855 InputFn: Fn(&Self) -> bool,
1856 OwnerFn: Fn(&HTMLFormElement) -> bool,
1857 Self: Sized,
1858 {
1859 if self.to_element().has_attribute(attr) {
1860 input(self)
1861 } else {
1862 self.form_owner().is_some_and(|t| owner(&t))
1863 }
1864 }
1865
1866 fn is_candidate_for_constraint_validation(&self) -> bool {
1868 let element = self.to_element();
1869 let html_element = element.downcast::<HTMLElement>();
1870 if let Some(html_element) = html_element {
1871 html_element.is_submittable_element() || element.is_instance_validatable()
1872 } else {
1873 false
1874 }
1875 }
1876
1877 fn moving_steps(&self, cx: &mut JSContext) {
1878 let same_subtree = self
1881 .form_owner()
1882 .is_none_or(|form| self.to_element().is_in_same_home_subtree(&*form));
1883 if !same_subtree {
1884 self.reset_form_owner(CanGc::from_cx(cx))
1885 }
1886 }
1887
1888 }
1891
1892impl VirtualMethods for HTMLFormElement {
1893 fn super_type(&self) -> Option<&dyn VirtualMethods> {
1894 Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods)
1895 }
1896
1897 fn unbind_from_tree(&self, cx: &mut js::context::JSContext, context: &UnbindContext) {
1898 self.super_type().unwrap().unbind_from_tree(cx, context);
1899
1900 rooted_vec!(let mut to_reset);
1903 to_reset.extend(
1904 self.controls
1905 .borrow()
1906 .iter()
1907 .filter(|c| !c.is_in_same_home_subtree(self))
1908 .cloned(),
1909 );
1910
1911 for control in to_reset.iter() {
1912 control
1913 .as_maybe_form_control()
1914 .expect("Element must be a form control")
1915 .reset_form_owner(CanGc::from_cx(cx));
1916 }
1917 }
1918
1919 fn parse_plain_attribute(&self, name: &LocalName, value: DOMString) -> AttrValue {
1920 match name {
1921 &local_name!("rel") => AttrValue::from_serialized_tokenlist(value.into()),
1922 _ => self
1923 .super_type()
1924 .unwrap()
1925 .parse_plain_attribute(name, value),
1926 }
1927 }
1928
1929 fn attribute_mutated(
1930 &self,
1931 cx: &mut js::context::JSContext,
1932 attr: &Attr,
1933 mutation: AttributeMutation,
1934 ) {
1935 self.super_type()
1936 .unwrap()
1937 .attribute_mutated(cx, attr, mutation);
1938
1939 match *attr.local_name() {
1940 local_name!("rel") | local_name!("rev") => {
1941 self.relations
1942 .set(LinkRelations::for_element(self.upcast()));
1943 },
1944 _ => {},
1945 }
1946 }
1947}
1948
1949pub(crate) trait FormControlElementHelpers {
1950 fn as_maybe_form_control(&self) -> Option<&dyn FormControl>;
1951}
1952
1953impl FormControlElementHelpers for Element {
1954 fn as_maybe_form_control(&self) -> Option<&dyn FormControl> {
1955 let node = self.upcast::<Node>();
1956
1957 match node.type_id() {
1958 NodeTypeId::Element(ElementTypeId::HTMLElement(
1959 HTMLElementTypeId::HTMLButtonElement,
1960 )) => Some(self.downcast::<HTMLButtonElement>().unwrap() as &dyn FormControl),
1961 NodeTypeId::Element(ElementTypeId::HTMLElement(
1962 HTMLElementTypeId::HTMLFieldSetElement,
1963 )) => Some(self.downcast::<HTMLFieldSetElement>().unwrap() as &dyn FormControl),
1964 NodeTypeId::Element(ElementTypeId::HTMLElement(
1965 HTMLElementTypeId::HTMLImageElement,
1966 )) => Some(self.downcast::<HTMLImageElement>().unwrap() as &dyn FormControl),
1967 NodeTypeId::Element(ElementTypeId::HTMLElement(
1968 HTMLElementTypeId::HTMLInputElement,
1969 )) => Some(self.downcast::<HTMLInputElement>().unwrap() as &dyn FormControl),
1970 NodeTypeId::Element(ElementTypeId::HTMLElement(
1971 HTMLElementTypeId::HTMLLabelElement,
1972 )) => Some(self.downcast::<HTMLLabelElement>().unwrap() as &dyn FormControl),
1973 NodeTypeId::Element(ElementTypeId::HTMLElement(
1974 HTMLElementTypeId::HTMLLegendElement,
1975 )) => Some(self.downcast::<HTMLLegendElement>().unwrap() as &dyn FormControl),
1976 NodeTypeId::Element(ElementTypeId::HTMLElement(
1977 HTMLElementTypeId::HTMLObjectElement,
1978 )) => Some(self.downcast::<HTMLObjectElement>().unwrap() as &dyn FormControl),
1979 NodeTypeId::Element(ElementTypeId::HTMLElement(
1980 HTMLElementTypeId::HTMLOutputElement,
1981 )) => Some(self.downcast::<HTMLOutputElement>().unwrap() as &dyn FormControl),
1982 NodeTypeId::Element(ElementTypeId::HTMLElement(
1983 HTMLElementTypeId::HTMLSelectElement,
1984 )) => Some(self.downcast::<HTMLSelectElement>().unwrap() as &dyn FormControl),
1985 NodeTypeId::Element(ElementTypeId::HTMLElement(
1986 HTMLElementTypeId::HTMLTextAreaElement,
1987 )) => Some(self.downcast::<HTMLTextAreaElement>().unwrap() as &dyn FormControl),
1988 _ => self.downcast::<HTMLElement>().and_then(|elem| {
1989 if elem.is_form_associated_custom_element() {
1990 Some(elem as &dyn FormControl)
1991 } else {
1992 None
1993 }
1994 }),
1995 }
1996 }
1997}
1998
1999pub(crate) fn encode_multipart_form_data(
2001 form_data: &mut [FormDatum],
2002 boundary: String,
2003 encoding: &'static Encoding,
2004) -> Vec<u8> {
2005 let mut result = vec![];
2006
2007 fn clean_crlf(s: &DOMString) -> DOMString {
2009 let mut buf = "".to_owned();
2010 let mut prev = ' ';
2011 for ch in s.str().chars() {
2012 match ch {
2013 '\n' if prev != '\r' => {
2014 buf.push('\r');
2015 buf.push('\n');
2016 },
2017 '\n' => {
2018 buf.push('\n');
2019 },
2020 _ if prev == '\r' => {
2023 buf.push('\r');
2024 buf.push('\n');
2025 buf.push(ch);
2026 },
2027 _ => buf.push(ch),
2028 };
2029 prev = ch;
2030 }
2031 if prev == '\r' {
2033 buf.push('\n');
2034 }
2035 DOMString::from(buf)
2036 }
2037
2038 for entry in form_data.iter_mut() {
2039 entry.name = clean_crlf(&entry.name);
2041
2042 if let FormDatumValue::String(ref s) = entry.value {
2045 entry.value = FormDatumValue::String(clean_crlf(s));
2046 }
2047
2048 let mut boundary_bytes = format!("--{}\r\n", boundary).into_bytes();
2053 result.append(&mut boundary_bytes);
2054
2055 match entry.value {
2058 FormDatumValue::String(ref s) => {
2059 let content_disposition = format!("form-data; name=\"{}\"", entry.name);
2060 let mut bytes =
2061 format!("Content-Disposition: {content_disposition}\r\n\r\n{s}\r\n",)
2062 .into_bytes();
2063 result.append(&mut bytes);
2064 },
2065 FormDatumValue::File(ref f) => {
2066 let charset = encoding.name();
2067 let extra = if charset.to_lowercase() == "utf-8" {
2068 format!("filename=\"{}\"", String::from(f.name().str()))
2069 } else {
2070 format!(
2071 "filename*=\"{}\"''{}",
2072 charset,
2073 http_percent_encode(&f.name().as_bytes())
2074 )
2075 };
2076
2077 let content_disposition = format!("form-data; name=\"{}\"; {}", entry.name, extra);
2078 let content_type: Mime = f
2080 .upcast::<Blob>()
2081 .Type()
2082 .parse()
2083 .unwrap_or(mime::TEXT_PLAIN);
2084 let mut type_bytes = format!(
2085 "Content-Disposition: {}\r\nContent-Type: {}\r\n\r\n",
2086 content_disposition, content_type
2087 )
2088 .into_bytes();
2089 result.append(&mut type_bytes);
2090
2091 let mut bytes = f.upcast::<Blob>().get_bytes().unwrap_or(vec![]);
2092
2093 result.append(&mut bytes);
2094 result.extend(b"\r\n");
2095 },
2096 }
2097 }
2098
2099 let mut boundary_bytes = format!("--{boundary}--\r\n").into_bytes();
2100 result.append(&mut boundary_bytes);
2101
2102 result
2103}
2104
2105pub(crate) fn generate_boundary() -> String {
2107 let i1 = random::<u32>();
2108 let i2 = random::<u32>();
2109
2110 format!("---------------------------{0}{1}", i1, i2)
2111}