1use std::borrow::{Cow, ToOwned};
6use std::cell::Cell;
7
8use content_security_policy::sandboxing_directive::SandboxingFlagSet;
9use dom_struct::dom_struct;
10use encoding_rs::{Encoding, UTF_8};
11use headers::{ContentType, HeaderMapExt};
12use html5ever::{LocalName, Prefix, local_name};
13use http::Method;
14use js::context::{JSContext, NoGC};
15use js::rust::HandleObject;
16use mime::{self, Mime};
17use net_traits::request::Referrer;
18use rand::random;
19use rustc_hash::FxBuildHasher;
20use script_bindings::cell::DomRefCell;
21use script_bindings::codegen::GenericBindings::DocumentFragmentBinding::DocumentFragmentMethods;
22use script_bindings::match_domstring_ascii;
23use script_bindings::reflector::DomObject;
24use servo_constellation_traits::{LoadData, LoadOrigin, NavigationHistoryBehavior};
25use style::attr::AttrValue;
26use style::str::split_html_space_chars;
27use stylo_atoms::Atom;
28use stylo_dom::ElementState;
29
30use crate::body::Extractable;
31use crate::dom::bindings::codegen::Bindings::BlobBinding::BlobMethods;
32use crate::dom::bindings::codegen::Bindings::DocumentBinding::DocumentMethods;
33use crate::dom::bindings::codegen::Bindings::EventBinding::EventMethods;
34use crate::dom::bindings::codegen::Bindings::HTMLButtonElementBinding::HTMLButtonElementMethods;
35use crate::dom::bindings::codegen::Bindings::HTMLElementBinding::HTMLElementMethods;
36use crate::dom::bindings::codegen::Bindings::HTMLFormControlsCollectionBinding::HTMLFormControlsCollectionMethods;
37use crate::dom::bindings::codegen::Bindings::HTMLFormElementBinding::HTMLFormElementMethods;
38use crate::dom::bindings::codegen::Bindings::HTMLInputElementBinding::HTMLInputElementMethods;
39use crate::dom::bindings::codegen::Bindings::HTMLOrSVGElementBinding::FocusOptions;
40use crate::dom::bindings::codegen::Bindings::HTMLTextAreaElementBinding::HTMLTextAreaElementMethods;
41use crate::dom::bindings::codegen::Bindings::NodeBinding::{NodeConstants, NodeMethods};
42use crate::dom::bindings::codegen::Bindings::NodeListBinding::NodeListMethods;
43use crate::dom::bindings::codegen::Bindings::RadioNodeListBinding::RadioNodeListMethods;
44use crate::dom::bindings::codegen::Bindings::WindowBinding::Window_Binding::WindowMethods;
45use crate::dom::bindings::codegen::UnionTypes::RadioNodeListOrElement;
46use crate::dom::bindings::error::{Error, Fallible};
47use crate::dom::bindings::inheritance::{Castable, ElementTypeId, HTMLElementTypeId, NodeTypeId};
48use crate::dom::bindings::refcounted::Trusted;
49use crate::dom::bindings::reflector::DomGlobal;
50use crate::dom::bindings::root::{Dom, DomOnceCell, DomRoot, MutNullableDom};
51use crate::dom::bindings::str::DOMString;
52use crate::dom::bindings::trace::{HashMapTracedValues, NoTrace};
53use crate::dom::blob::Blob;
54use crate::dom::customelementregistry::CallbackReaction;
55use crate::dom::document::Document;
56use crate::dom::domtokenlist::DOMTokenList;
57use crate::dom::element::attributes::storage::AttrRef;
58use crate::dom::element::{AttributeMutation, AttributeMutationReason, Element};
59use crate::dom::event::{Event, EventBubbles, EventCancelable};
60use crate::dom::eventtarget::EventTarget;
61use crate::dom::file::File;
62use crate::dom::formdata::FormData;
63use crate::dom::formdataevent::FormDataEvent;
64use crate::dom::html::htmlbuttonelement::HTMLButtonElement;
65use crate::dom::html::htmlcollection::CollectionFilter;
66use crate::dom::html::htmldatalistelement::HTMLDataListElement;
67use crate::dom::html::htmlelement::HTMLElement;
68use crate::dom::html::htmlfieldsetelement::HTMLFieldSetElement;
69use crate::dom::html::htmlformcontrolscollection::HTMLFormControlsCollection;
70use crate::dom::html::htmlimageelement::HTMLImageElement;
71use crate::dom::html::htmllegendelement::HTMLLegendElement;
72use crate::dom::html::htmlobjectelement::HTMLObjectElement;
73use crate::dom::html::htmloutputelement::HTMLOutputElement;
74use crate::dom::html::htmlselectelement::HTMLSelectElement;
75use crate::dom::html::htmltextareaelement::HTMLTextAreaElement;
76use crate::dom::html::input_element::HTMLInputElement;
77use crate::dom::input_element::input_type::InputType;
78use crate::dom::node::virtualmethods::VirtualMethods;
79use crate::dom::node::{Node, NodeFlags, NodeTraits, UnbindContext, VecPreOrderInsertionHelper};
80use crate::dom::nodelist::{NodeList, RadioListMode};
81use crate::dom::radionodelist::RadioNodeList;
82use crate::dom::submitevent::SubmitEvent;
83use crate::dom::types::{DocumentFragment, HTMLIFrameElement};
84use crate::dom::window::Window;
85use crate::links::{LinkRelations, get_element_target, valid_navigable_target_name_or_keyword};
86use crate::navigation::navigate;
87use crate::script_runtime::CanGc;
88use crate::script_thread::ScriptThread;
89
90#[dom_struct]
92pub(crate) struct HTMLFormElement {
93 htmlelement: HTMLElement,
94 marked_for_reset: Cell<bool>,
95 constructing_entry_list: Cell<bool>,
97 elements: DomOnceCell<HTMLFormControlsCollection>,
98 controls: DomRefCell<Vec<Dom<Element>>>,
99
100 #[expect(clippy::type_complexity)]
102 past_names_map:
103 DomRefCell<HashMapTracedValues<Atom, (Dom<Element>, NoTrace<usize>), FxBuildHasher>>,
104
105 current_name_generation: Cell<usize>,
107
108 firing_submission_events: Cell<bool>,
109 rel_list: MutNullableDom<DOMTokenList>,
110
111 planned_navigation: Cell<usize>,
113
114 #[no_trace]
116 relations: Cell<LinkRelations>,
117}
118
119impl HTMLFormElement {
120 fn new_inherited(
121 local_name: LocalName,
122 prefix: Option<Prefix>,
123 document: &Document,
124 ) -> HTMLFormElement {
125 HTMLFormElement {
126 htmlelement: HTMLElement::new_inherited_with_state(
127 ElementState::VALID,
128 local_name,
129 prefix,
130 document,
131 ),
132 marked_for_reset: Cell::new(false),
133 constructing_entry_list: Cell::new(false),
134 elements: Default::default(),
135 controls: DomRefCell::new(Vec::new()),
136 past_names_map: DomRefCell::new(HashMapTracedValues::new_fx()),
137 current_name_generation: Cell::new(0),
138 firing_submission_events: Cell::new(false),
139 rel_list: Default::default(),
140 planned_navigation: Default::default(),
141 relations: Cell::new(LinkRelations::empty()),
142 }
143 }
144
145 pub(crate) fn new(
146 cx: &mut JSContext,
147 local_name: LocalName,
148 prefix: Option<Prefix>,
149 document: &Document,
150 proto: Option<HandleObject>,
151 ) -> DomRoot<HTMLFormElement> {
152 Node::reflect_node_with_proto(
153 cx,
154 Box::new(HTMLFormElement::new_inherited(local_name, prefix, document)),
155 document,
156 proto,
157 )
158 }
159
160 fn filter_for_radio_list(mode: RadioListMode, child: &Element, name: &Atom) -> bool {
161 if let Some(child) = child.downcast::<Element>() {
162 match mode {
163 RadioListMode::ControlsExceptImageInputs => {
164 if child
165 .downcast::<HTMLElement>()
166 .is_some_and(|c| c.is_listed_element()) &&
167 (child.get_id().is_some_and(|i| i == *name) ||
168 child.get_name().is_some_and(|n| n == *name))
169 {
170 if let Some(inp) = child.downcast::<HTMLInputElement>() {
171 return !matches!(*inp.input_type(), InputType::Image(_));
173 } else {
174 return true;
176 }
177 }
178 return false;
179 },
180 RadioListMode::Images => {
181 return child.is::<HTMLImageElement>() &&
182 (child.get_id().is_some_and(|i| i == *name) ||
183 child.get_name().is_some_and(|n| n == *name));
184 },
185 }
186 }
187 false
188 }
189
190 pub(crate) fn nth_for_radio_list(
191 &self,
192 index: u32,
193 mode: RadioListMode,
194 name: &Atom,
195 ) -> Option<DomRoot<Node>> {
196 self.controls
197 .borrow()
198 .iter()
199 .filter(|n| HTMLFormElement::filter_for_radio_list(mode, n, name))
200 .nth(index as usize)
201 .map(|n| DomRoot::from_ref(n.upcast::<Node>()))
202 }
203
204 pub(crate) fn count_for_radio_list(&self, mode: RadioListMode, name: &Atom) -> u32 {
205 self.controls
206 .borrow()
207 .iter()
208 .filter(|n| HTMLFormElement::filter_for_radio_list(mode, n, name))
209 .count() as u32
210 }
211}
212
213impl HTMLFormElementMethods<crate::DomTypeHolder> for HTMLFormElement {
214 make_getter!(AcceptCharset, "accept-charset");
216
217 make_setter!(SetAcceptCharset, "accept-charset");
219
220 make_form_action_getter!(Action, "action");
222
223 make_setter!(SetAction, "action");
225
226 make_enumerated_getter!(
228 Autocomplete,
229 "autocomplete",
230 "on" | "off",
231 missing => "on",
232 invalid => "on"
233 );
234
235 make_setter!(SetAutocomplete, "autocomplete");
237
238 make_enumerated_getter!(
240 Enctype,
241 "enctype",
242 "application/x-www-form-urlencoded" | "text/plain" | "multipart/form-data",
243 missing => "application/x-www-form-urlencoded",
244 invalid => "application/x-www-form-urlencoded"
245 );
246
247 make_setter!(SetEnctype, "enctype");
249
250 fn Encoding(&self) -> DOMString {
252 self.Enctype()
253 }
254
255 fn SetEncoding(&self, cx: &mut JSContext, value: DOMString) {
257 self.SetEnctype(cx, value)
258 }
259
260 make_enumerated_getter!(
262 Method,
263 "method",
264 "get" | "post" | "dialog",
265 missing => "get",
266 invalid => "get"
267 );
268
269 make_setter!(SetMethod, "method");
271
272 make_getter!(Name, "name");
274
275 make_atomic_setter!(SetName, "name");
277
278 make_bool_getter!(NoValidate, "novalidate");
280
281 make_bool_setter!(SetNoValidate, "novalidate");
283
284 make_getter!(Target, "target");
286
287 make_setter!(SetTarget, "target");
289
290 make_getter!(Rel, "rel");
292
293 fn Submit(&self, cx: &mut JSContext) {
295 self.submit(
296 cx,
297 SubmittedFrom::FromForm,
298 FormSubmitterElement::Form(self),
299 );
300 }
301
302 fn RequestSubmit(&self, cx: &mut JSContext, submitter: Option<&HTMLElement>) -> Fallible<()> {
304 let submitter: FormSubmitterElement = match submitter {
305 Some(submitter_element) => {
306 let error_not_a_submit_button =
308 Err(Error::Type(c"submitter must be a submit button".to_owned()));
309
310 let element = match submitter_element.upcast::<Node>().type_id() {
311 NodeTypeId::Element(ElementTypeId::HTMLElement(element)) => element,
312 _ => {
313 return error_not_a_submit_button;
314 },
315 };
316
317 let submit_button = match element {
318 HTMLElementTypeId::HTMLInputElement => FormSubmitterElement::Input(
319 submitter_element
320 .downcast::<HTMLInputElement>()
321 .expect("Failed to downcast submitter elem to HTMLInputElement."),
322 ),
323 HTMLElementTypeId::HTMLButtonElement => FormSubmitterElement::Button(
324 submitter_element
325 .downcast::<HTMLButtonElement>()
326 .expect("Failed to downcast submitter elem to HTMLButtonElement."),
327 ),
328 _ => {
329 return error_not_a_submit_button;
330 },
331 };
332
333 if !submit_button.is_submit_button() {
334 return error_not_a_submit_button;
335 }
336
337 let submitters_owner = submit_button.form_owner();
338
339 let owner = match submitters_owner {
341 Some(owner) => owner,
342 None => {
343 return Err(Error::NotFound(None));
344 },
345 };
346
347 if *owner != *self {
348 return Err(Error::NotFound(None));
349 }
350
351 submit_button
352 },
353 None => {
354 FormSubmitterElement::Form(self)
356 },
357 };
358 self.submit(cx, SubmittedFrom::NotFromForm, submitter);
360 Ok(())
361 }
362
363 fn Reset(&self, cx: &mut JSContext) {
365 self.reset(cx, ResetFrom::FromForm);
366 }
367
368 fn Elements(&self, cx: &mut JSContext) -> DomRoot<HTMLFormControlsCollection> {
370 #[derive(JSTraceable, MallocSizeOf)]
371 struct ElementsFilter {
372 form: DomRoot<HTMLFormElement>,
373 }
374 impl CollectionFilter for ElementsFilter {
375 fn filter<'a>(&self, elem: &'a Element, _root: &'a Node) -> bool {
376 let form_owner = match elem.upcast::<Node>().type_id() {
377 NodeTypeId::Element(ElementTypeId::HTMLElement(t)) => match t {
378 HTMLElementTypeId::HTMLButtonElement => {
379 elem.downcast::<HTMLButtonElement>().unwrap().form_owner()
380 },
381 HTMLElementTypeId::HTMLFieldSetElement => {
382 elem.downcast::<HTMLFieldSetElement>().unwrap().form_owner()
383 },
384 HTMLElementTypeId::HTMLInputElement => {
385 let input_elem = elem.downcast::<HTMLInputElement>().unwrap();
386 if matches!(*input_elem.input_type(), InputType::Image(_)) {
387 return false;
388 }
389 input_elem.form_owner()
390 },
391 HTMLElementTypeId::HTMLObjectElement => {
392 elem.downcast::<HTMLObjectElement>().unwrap().form_owner()
393 },
394 HTMLElementTypeId::HTMLOutputElement => {
395 elem.downcast::<HTMLOutputElement>().unwrap().form_owner()
396 },
397 HTMLElementTypeId::HTMLSelectElement => {
398 elem.downcast::<HTMLSelectElement>().unwrap().form_owner()
399 },
400 HTMLElementTypeId::HTMLTextAreaElement => {
401 elem.downcast::<HTMLTextAreaElement>().unwrap().form_owner()
402 },
403 HTMLElementTypeId::HTMLElement => {
404 let html_element = elem.downcast::<HTMLElement>().unwrap();
405 if html_element.is_form_associated_custom_element() {
406 html_element.form_owner()
407 } else {
408 return false;
409 }
410 },
411 _ => {
412 debug_assert!(
413 !elem.downcast::<HTMLElement>().unwrap().is_listed_element()
414 );
415 return false;
416 },
417 },
418 _ => return false,
419 };
420
421 match form_owner {
422 Some(form_owner) => form_owner == self.form,
423 None => false,
424 }
425 }
426 }
427 DomRoot::from_ref(self.elements.init_once(|| {
428 let filter = Box::new(ElementsFilter {
429 form: DomRoot::from_ref(self),
430 });
431 let window = self.owner_window();
432 HTMLFormControlsCollection::new(cx, &window, self, filter)
433 }))
434 }
435
436 fn Length(&self, cx: &mut JSContext) -> u32 {
438 self.Elements(cx).Length(cx)
439 }
440
441 fn IndexedGetter(&self, cx: &mut JSContext, index: u32) -> Option<DomRoot<Element>> {
443 let elements = self.Elements(cx);
444 elements.IndexedGetter(cx, index)
445 }
446
447 fn NamedGetter(&self, cx: &mut JSContext, name: DOMString) -> Option<RadioNodeListOrElement> {
449 let window = self.owner_window();
450
451 let name = Atom::from(name);
452
453 let mut candidates =
455 RadioNodeList::new_controls_except_image_inputs(cx, &window, self, &name);
456 let mut candidates_length = candidates.Length(cx);
457
458 if candidates_length == 0 {
460 candidates = RadioNodeList::new_images(cx, &window, self, &name);
461 candidates_length = candidates.Length(cx);
462 }
463
464 let mut past_names_map = self.past_names_map.borrow_mut();
465
466 if candidates_length == 0 {
468 if past_names_map.contains_key(&name) {
469 return Some(RadioNodeListOrElement::Element(DomRoot::from_ref(
470 &*past_names_map.get(&name).unwrap().0,
471 )));
472 }
473 return None;
474 }
475
476 if candidates_length > 1 {
478 return Some(RadioNodeListOrElement::RadioNodeList(candidates));
479 }
480
481 let element_node = candidates.upcast::<NodeList>().Item(0).unwrap();
484 past_names_map.insert(
485 name,
486 (
487 Dom::from_ref(element_node.downcast::<Element>().unwrap()),
488 NoTrace(self.current_name_generation.get() + 1),
489 ),
490 );
491 self.current_name_generation
492 .set(self.current_name_generation.get() + 1);
493
494 Some(RadioNodeListOrElement::Element(DomRoot::from_ref(
496 element_node.downcast::<Element>().unwrap(),
497 )))
498 }
499
500 fn SetRel(&self, cx: &mut JSContext, rel: DOMString) {
502 self.upcast::<Element>()
503 .set_tokenlist_attribute(cx, &local_name!("rel"), rel);
504 }
505
506 fn RelList(&self, cx: &mut JSContext) -> DomRoot<DOMTokenList> {
508 self.rel_list.or_init(|| {
509 DOMTokenList::new(
510 cx,
511 self.upcast(),
512 &local_name!("rel"),
513 Some(vec![
514 Atom::from("noopener"),
515 Atom::from("noreferrer"),
516 Atom::from("opener"),
517 ]),
518 )
519 })
520 }
521
522 fn SupportedPropertyNames(&self, _: &NoGC) -> Vec<DOMString> {
524 #[derive(Debug, Eq, Ord, PartialEq, PartialOrd)]
526 enum SourcedNameSource {
527 Id,
528 Name,
529 Past(usize),
530 }
531
532 impl SourcedNameSource {
533 fn is_past(&self) -> bool {
534 matches!(self, SourcedNameSource::Past(..))
535 }
536 }
537
538 struct SourcedName {
539 name: Atom,
540 element: DomRoot<Element>,
541 source: SourcedNameSource,
542 }
543
544 let mut sourced_names_vec: Vec<SourcedName> = Vec::new();
545
546 for child in self.controls.borrow().iter() {
548 if child
549 .downcast::<HTMLElement>()
550 .is_some_and(|c| c.is_listed_element())
551 {
552 if let Some(id_atom) = child.get_id() {
553 let entry = SourcedName {
554 name: id_atom,
555 element: DomRoot::from_ref(child),
556 source: SourcedNameSource::Id,
557 };
558 sourced_names_vec.push(entry);
559 }
560 if let Some(name_atom) = child.get_name() {
561 let entry = SourcedName {
562 name: name_atom,
563 element: DomRoot::from_ref(child),
564 source: SourcedNameSource::Name,
565 };
566 sourced_names_vec.push(entry);
567 }
568 }
569 }
570
571 for child in self.controls.borrow().iter() {
573 if child.is::<HTMLImageElement>() {
574 if let Some(id_atom) = child.get_id() {
575 let entry = SourcedName {
576 name: id_atom,
577 element: DomRoot::from_ref(child),
578 source: SourcedNameSource::Id,
579 };
580 sourced_names_vec.push(entry);
581 }
582 if let Some(name_atom) = child.get_name() {
583 let entry = SourcedName {
584 name: name_atom,
585 element: DomRoot::from_ref(child),
586 source: SourcedNameSource::Name,
587 };
588 sourced_names_vec.push(entry);
589 }
590 }
591 }
592
593 let past_names_map = self.past_names_map.borrow();
595 for (key, val) in past_names_map.iter() {
596 let entry = SourcedName {
597 name: key.clone(),
598 element: DomRoot::from_ref(&*val.0),
599 source: SourcedNameSource::Past(self.current_name_generation.get() - val.1.0),
600 };
601 sourced_names_vec.push(entry);
602 }
603
604 sourced_names_vec.sort_by(|a, b| {
616 if a.element
617 .upcast::<Node>()
618 .CompareDocumentPosition(b.element.upcast::<Node>()) ==
619 0
620 {
621 if a.source.is_past() && b.source.is_past() {
622 b.source.cmp(&a.source)
623 } else {
624 a.source.cmp(&b.source)
625 }
626 } else if a
627 .element
628 .upcast::<Node>()
629 .CompareDocumentPosition(b.element.upcast::<Node>()) &
630 NodeConstants::DOCUMENT_POSITION_FOLLOWING ==
631 NodeConstants::DOCUMENT_POSITION_FOLLOWING
632 {
633 std::cmp::Ordering::Less
634 } else {
635 std::cmp::Ordering::Greater
636 }
637 });
638
639 sourced_names_vec.retain(|sn| !sn.name.to_string().is_empty());
641
642 let mut names_vec: Vec<DOMString> = Vec::new();
644 for elem in sourced_names_vec.iter() {
645 if !names_vec.iter().any(|name| *name == *elem.name) {
646 names_vec.push(DOMString::from(&*elem.name));
647 }
648 }
649
650 names_vec
651 }
652
653 fn CheckValidity(&self, cx: &mut JSContext) -> bool {
655 self.static_validation(cx).is_ok()
656 }
657
658 fn ReportValidity(&self, cx: &mut JSContext) -> bool {
660 self.interactive_validation(cx).is_ok()
661 }
662}
663
664#[derive(Clone, Copy, MallocSizeOf, PartialEq)]
665pub(crate) enum SubmittedFrom {
666 FromForm,
667 NotFromForm,
668}
669
670#[derive(Clone, Copy, MallocSizeOf)]
671pub(crate) enum ResetFrom {
672 FromForm,
673 NotFromForm,
674}
675
676impl HTMLFormElement {
677 fn pick_encoding(&self) -> &'static Encoding {
679 if self
681 .upcast::<Element>()
682 .has_attribute(&local_name!("accept-charset"))
683 {
684 let input = self
686 .upcast::<Element>()
687 .get_string_attribute(&local_name!("accept-charset"));
688
689 let input = input.str();
691 let mut candidate_encodings =
692 split_html_space_chars(&input).filter_map(|c| Encoding::for_label(c.as_bytes()));
693
694 return candidate_encodings.next().unwrap_or(UTF_8);
696 }
697
698 self.owner_document().encoding()
700 }
701
702 pub(crate) fn update_validity(&self, cx: &mut JSContext) {
703 let is_any_invalid = self
704 .controls
705 .borrow()
706 .iter()
707 .any(|control| control.is_invalid(cx, false));
708
709 self.upcast::<Element>()
710 .set_state(ElementState::VALID, !is_any_invalid);
711 self.upcast::<Element>()
712 .set_state(ElementState::INVALID, is_any_invalid);
713 }
714
715 pub(crate) fn submit(
717 &self,
718 cx: &mut JSContext,
719 submit_method_flag: SubmittedFrom,
720 submitter: FormSubmitterElement,
721 ) {
722 if self.upcast::<Element>().cannot_navigate() {
724 return;
725 }
726
727 if self.constructing_entry_list.get() {
729 return;
730 }
731 let doc = self.owner_document();
733
734 if doc.has_active_sandboxing_flag(SandboxingFlagSet::SANDBOXED_FORMS_BROWSING_CONTEXT_FLAG)
737 {
738 return;
739 }
740
741 let base = doc.base_url();
742 if submit_method_flag == SubmittedFrom::NotFromForm {
745 if self.firing_submission_events.get() {
747 return;
748 }
749 self.firing_submission_events.set(true);
751 if !submitter.no_validate(self) && self.interactive_validation(cx).is_err() {
753 self.firing_submission_events.set(false);
754 return;
755 }
756 let submitter_button = match submitter {
760 FormSubmitterElement::Form(f) => {
761 if f == self {
762 None
763 } else {
764 Some(f.upcast::<HTMLElement>())
765 }
766 },
767 FormSubmitterElement::Input(i) => Some(i.upcast::<HTMLElement>()),
768 FormSubmitterElement::Button(b) => Some(b.upcast::<HTMLElement>()),
769 };
770
771 let event = SubmitEvent::new(
773 cx,
774 self.global().as_window(),
775 atom!("submit"),
776 true,
777 true,
778 submitter_button.map(DomRoot::from_ref),
779 );
780 let event = event.upcast::<Event>();
781 event.fire(cx, self.upcast::<EventTarget>());
782
783 self.firing_submission_events.set(false);
785 if event.DefaultPrevented() {
787 return;
788 }
789 if self.upcast::<Element>().cannot_navigate() {
791 return;
792 }
793 }
794
795 let encoding = self.pick_encoding();
797
798 let mut form_data = match self.get_form_dataset(cx, Some(submitter), Some(encoding)) {
800 Some(form_data) => form_data,
801 None => return,
802 };
803
804 if self.upcast::<Element>().cannot_navigate() {
806 return;
807 }
808
809 let method = submitter.method();
811 let mut action = submitter.action();
816
817 if action.is_empty() {
819 action = DOMString::from(base.as_str());
820 }
821 let action_components = match doc.encoding_parse_a_url(&action.str()) {
823 Ok(url) => url,
824 Err(_) => return,
826 };
827 let scheme = action_components.scheme().to_owned();
829 let enctype = submitter.enctype();
831
832 let form_target_attribute = submitter.target();
835 let form_target = if submitter.is_submit_button() &&
836 valid_navigable_target_name_or_keyword(&form_target_attribute)
837 {
838 Some(form_target_attribute)
839 } else {
840 None
842 };
843 let form_owner = submitter.form_owner();
845 let form = form_owner.as_deref().unwrap_or(self);
846 let target = get_element_target(form.upcast::<Element>(), form_target);
847
848 let noopener = self.relations.get().get_element_noopener(target.as_ref());
850
851 let source = doc.browsing_context().unwrap();
854 let (maybe_chosen, _new) =
855 source.choose_browsing_context(cx, target.unwrap_or_default(), noopener);
856
857 let Some(chosen) = maybe_chosen else {
858 return;
860 };
861 let target_document = match chosen.document() {
862 Some(doc) => doc,
863 None => return,
864 };
865
866 let history_handling = if doc == target_document && !doc.completely_loaded() {
870 NavigationHistoryBehavior::Replace
871 } else {
872 NavigationHistoryBehavior::Auto
873 };
874
875 let target_window = target_document.window();
876 let mut load_data = LoadData::new(
877 LoadOrigin::Script(doc.origin().snapshot()),
878 action_components,
879 target_document.about_base_url(),
880 None,
881 target_window.as_global_scope().get_referrer(),
882 target_document.get_referrer_policy(),
883 Some(target_window.as_global_scope().is_secure_context()),
884 Some(target_document.insecure_requests_policy()),
885 target_document.has_trustworthy_ancestor_origin(),
886 target_document.creation_sandboxing_flag_set_considering_parent_iframe(),
887 );
888
889 match (&*scheme, method) {
893 (_, FormMethod::Dialog) => {
894 },
897 ("http", FormMethod::Get) | ("https", FormMethod::Get) | ("data", FormMethod::Get) => {
899 load_data
900 .headers
901 .typed_insert(ContentType::from(mime::APPLICATION_WWW_FORM_URLENCODED));
902 self.mutate_action_url(&mut form_data, load_data, target_window, history_handling);
903 },
904 ("http", FormMethod::Post) | ("https", FormMethod::Post) => {
906 load_data.method = Method::POST;
907 self.submit_entity_body(
908 cx,
909 &mut form_data,
910 load_data,
911 enctype,
912 encoding,
913 target_window,
914 history_handling,
915 );
916 },
917 ("file", _) |
919 ("about", _) |
920 ("data", FormMethod::Post) |
921 ("ftp", _) |
922 ("javascript", _) => {
923 self.plan_to_navigate(load_data, target_window, history_handling);
924 },
925 ("mailto", FormMethod::Post) => {
926 },
929 ("mailto", FormMethod::Get) => {
930 },
933 _ => (),
934 }
935 }
936
937 fn mutate_action_url(
939 &self,
940 form_data: &mut [FormDatum],
941 mut load_data: LoadData,
942 target: &Window,
943 history_handling: NavigationHistoryBehavior,
944 ) {
945 self.set_url_query_pairs(
946 &mut load_data.url,
947 form_data.iter().map(|field| {
948 (
949 field.name.normalize_crlf(),
950 field.replace_value().normalize_crlf(),
951 )
952 }),
953 );
954
955 self.plan_to_navigate(load_data, target, history_handling);
956 }
957
958 #[allow(clippy::too_many_arguments)]
960 fn submit_entity_body(
961 &self,
962 cx: &mut JSContext,
963 form_data: &mut [FormDatum],
964 mut load_data: LoadData,
965 enctype: FormEncType,
966 encoding: &'static Encoding,
967 target: &Window,
968 history_handling: NavigationHistoryBehavior,
969 ) {
970 let boundary = generate_boundary();
971 let bytes = match enctype {
972 FormEncType::UrlEncoded => {
973 load_data
974 .headers
975 .typed_insert(ContentType::from(mime::APPLICATION_WWW_FORM_URLENCODED));
976
977 let mut url = load_data.url.clone();
978 self.set_url_query_pairs(
979 &mut url,
980 form_data.iter().map(|field| {
984 (
985 field.name.normalize_crlf(),
986 field.replace_value().normalize_crlf(),
987 )
988 }),
989 );
990
991 url.query().unwrap_or("").to_string().into_bytes()
992 },
993 FormEncType::MultipartFormData => {
994 let mime: Mime = format!("multipart/form-data; boundary={}", boundary)
995 .parse()
996 .unwrap();
997 load_data.headers.typed_insert(ContentType::from(mime));
998 encode_multipart_form_data(form_data, boundary, encoding)
999 },
1000 FormEncType::TextPlain => {
1001 load_data
1002 .headers
1003 .typed_insert(ContentType::from(mime::TEXT_PLAIN));
1004 let pairs = form_data.iter().map(|field| {
1008 (
1009 field.name.normalize_crlf(),
1010 field.replace_value().normalize_crlf(),
1011 )
1012 });
1013 let body = encode_plaintext(pairs);
1016 encode_with_html_fallback(&body, encoding).into_owned()
1018 },
1019 };
1020
1021 let global = self.global();
1022
1023 let request_body = bytes
1024 .extract(cx, &global, false)
1025 .expect("Couldn't extract body.")
1026 .into_net_request_body(cx)
1027 .0;
1028 load_data.data = Some(request_body);
1029
1030 self.plan_to_navigate(load_data, target, history_handling);
1031 }
1032
1033 fn set_url_query_pairs<T>(
1034 &self,
1035 url: &mut servo_url::ServoUrl,
1036 pairs: impl Iterator<Item = (T, String)>,
1037 ) where
1038 T: AsRef<str>,
1039 {
1040 let encoding = self.pick_encoding();
1041 url.as_mut_url()
1042 .query_pairs_mut()
1043 .encoding_override(Some(&|s| encode_with_html_fallback(s, encoding)))
1044 .clear()
1045 .extend_pairs(pairs);
1046 }
1047
1048 fn plan_to_navigate(
1050 &self,
1051 mut load_data: LoadData,
1052 target: &Window,
1053 history_handling: NavigationHistoryBehavior,
1054 ) {
1055 let elem = self.upcast::<Element>();
1060 let referrer = match elem.get_attribute_string_value(&local_name!("rel")) {
1061 Some(link_types) if link_types.contains("noreferrer") => Referrer::NoReferrer,
1062 _ => target.as_global_scope().get_referrer(),
1063 };
1064
1065 self.planned_navigation
1068 .set(self.planned_navigation.get().wrapping_add(1));
1069 let planned_navigation = self.planned_navigation.get();
1070
1071 let ongoing_navigation = target.set_ongoing_navigation();
1087
1088 let referrer_policy = target.Document().get_referrer_policy();
1089 load_data.creator_pipeline_id = Some(target.pipeline_id());
1090 load_data.referrer = referrer;
1091 load_data.referrer_policy = referrer_policy;
1092
1093 if let Some(window_proxy) = target.undiscarded_window_proxy() &&
1096 let Some(frame) = window_proxy
1097 .frame_element()
1098 .and_then(|e| e.downcast::<HTMLIFrameElement>())
1099 {
1100 frame.note_pending_navigation()
1101 }
1102
1103 let form = Trusted::new(self);
1106 let window = Trusted::new(target);
1107 let task = task!(navigate_to_form_planned_navigation: move |cx| {
1108 if planned_navigation != form.root().planned_navigation.get() {
1112 return;
1113 }
1114
1115 if ongoing_navigation != window.root().ongoing_navigation() {
1118 return;
1119 }
1120
1121 navigate(
1123 cx,
1124 &window.root(),
1125 history_handling,
1126 false,
1127 load_data,
1128 )
1129 });
1130
1131 target
1136 .global()
1137 .task_manager()
1138 .dom_manipulation_task_source()
1139 .queue(task)
1140 }
1141
1142 fn interactive_validation(&self, cx: &mut JSContext) -> Result<(), ()> {
1145 let unhandled_invalid_controls = match self.static_validation(cx) {
1150 Ok(()) => return Ok(()),
1151 Err(err) => err,
1152 };
1153
1154 let mut first = true;
1157
1158 for elem in unhandled_invalid_controls {
1159 if let Some(validatable) = elem.as_maybe_validatable() {
1160 error!("Validation error: {}", validatable.validation_message(cx));
1161 }
1162 if first && let Some(html_elem) = elem.downcast::<HTMLElement>() {
1163 html_elem.Focus(cx, &FocusOptions::default());
1170 first = false;
1171 }
1172 }
1173
1174 Err(())
1178 }
1179
1180 fn static_validation(&self, cx: &mut JSContext) -> Result<(), Vec<DomRoot<Element>>> {
1183 let invalid_controls = self
1185 .controls
1186 .borrow()
1187 .iter()
1188 .filter_map(|field| {
1189 if let Some(element) = field.downcast::<Element>() {
1190 if element.is_invalid(cx, true) {
1191 Some(DomRoot::from_ref(element))
1192 } else {
1193 None
1194 }
1195 } else {
1196 None
1197 }
1198 })
1199 .collect::<Vec<DomRoot<Element>>>();
1200 if invalid_controls.is_empty() {
1202 return Ok(());
1203 }
1204 let unhandled_invalid_controls = invalid_controls
1206 .into_iter()
1207 .filter_map(|field| {
1208 let not_canceled = field
1211 .upcast::<EventTarget>()
1212 .fire_cancelable_event(cx, atom!("invalid"));
1213 if not_canceled {
1215 return Some(field);
1216 }
1217 None
1218 })
1219 .collect::<Vec<DomRoot<Element>>>();
1220 Err(unhandled_invalid_controls)
1222 }
1223
1224 fn get_unclean_dataset(
1229 &self,
1230 cx: &mut JSContext,
1231 submitter: Option<FormSubmitterElement>,
1232 encoding: Option<&'static Encoding>,
1233 ) -> Vec<FormDatum> {
1234 let mut data_set = Vec::new();
1235 for child in self.controls.borrow().iter() {
1236 if child.disabled_state() {
1238 continue;
1239 }
1240 let child = child.upcast::<Node>();
1241
1242 if child.ancestors().any(|a| a.is::<HTMLDataListElement>()) {
1244 continue;
1245 }
1246 if let NodeTypeId::Element(ElementTypeId::HTMLElement(element)) = child.type_id() {
1247 match element {
1248 HTMLElementTypeId::HTMLInputElement => {
1249 let input = child.downcast::<HTMLInputElement>().unwrap();
1250 let (ref mut form_datums, should_continue) =
1251 input.form_datums(submitter, encoding);
1252 data_set.append(form_datums);
1253 if should_continue {
1254 continue;
1255 }
1256 },
1257 HTMLElementTypeId::HTMLButtonElement => {
1258 let button = child.downcast::<HTMLButtonElement>().unwrap();
1259 if let Some(datum) = button.form_datum(submitter) {
1260 data_set.push(datum);
1261 }
1262 },
1263 HTMLElementTypeId::HTMLObjectElement => {
1264 },
1266 HTMLElementTypeId::HTMLSelectElement => {
1267 let select = child.downcast::<HTMLSelectElement>().unwrap();
1268 select.push_form_data(cx.no_gc(), &mut data_set);
1269 },
1270 HTMLElementTypeId::HTMLTextAreaElement => {
1271 let textarea = child.downcast::<HTMLTextAreaElement>().unwrap();
1272 let name = textarea.Name();
1273 if !name.is_empty() {
1274 data_set.push(FormDatum {
1275 ty: textarea.Type(),
1276 name,
1277 value: FormDatumValue::String(textarea.Value()),
1278 });
1279 }
1280 },
1281 HTMLElementTypeId::HTMLElement => {
1282 let custom = child.downcast::<HTMLElement>().unwrap();
1283 if custom.is_form_associated_custom_element() {
1284 let internals = custom
1286 .upcast::<Element>()
1287 .ensure_element_internals(CanGc::from_cx(cx));
1288 internals.perform_entry_construction(&mut data_set);
1289 }
1291 },
1292 _ => (),
1293 }
1294 }
1295
1296 let child_element = child.downcast::<Element>().unwrap();
1298 let dirname = child_element.get_string_attribute(&local_name!("dirname"));
1299
1300 let is_input_auto_directionality_form_associated_element = child_element
1306 .downcast::<HTMLInputElement>()
1307 .is_some_and(|input| input.is_auto_directionality_form_associated_element());
1308 let is_textarea_element = child_element.is::<HTMLTextAreaElement>();
1309 if !dirname.is_empty() &&
1310 (is_input_auto_directionality_form_associated_element || is_textarea_element)
1311 {
1312 let dir = DOMString::from(child_element.directionality());
1315
1316 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 cx: &mut JSContext,
1331 submitter: Option<FormSubmitterElement>,
1332 encoding: Option<&'static Encoding>,
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(cx, submitter, encoding);
1344
1345 let window = self.owner_window();
1346
1347 let form_data = FormData::new(Some(ret), &window.global(), CanGc::from_cx(cx));
1349
1350 let event = FormDataEvent::new(
1352 cx,
1353 &window,
1354 atom!("formdata"),
1355 EventBubbles::Bubbles,
1356 EventCancelable::NotCancelable,
1357 &form_data,
1358 );
1359
1360 event
1361 .upcast::<Event>()
1362 .fire(cx, self.upcast::<EventTarget>());
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, cx: &mut JSContext, control: &T) {
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(cx);
1422 }
1423
1424 fn remove_control<T: ?Sized + FormControl>(&self, cx: &mut JSContext, control: &T) {
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(cx);
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(cx);
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 cx,
1479 html_element.upcast::<Element>(),
1480 CallbackReaction::FormReset,
1481 None,
1482 )
1483 }
1484 }
1485}
1486
1487#[derive(Clone, JSTraceable, MallocSizeOf)]
1488pub(crate) enum FormDatumValue {
1489 File(DomRoot<File>),
1490 String(DOMString),
1491}
1492
1493#[derive(Clone, JSTraceable, MallocSizeOf)]
1494pub(crate) struct FormDatum {
1495 pub(crate) ty: DOMString,
1496 pub(crate) name: DOMString,
1497 pub(crate) value: FormDatumValue,
1498}
1499
1500impl FormDatum {
1501 pub(crate) fn replace_value(&self) -> &DOMString {
1502 match self.value {
1503 FormDatumValue::File(ref f) => f.name(),
1504 FormDatumValue::String(ref s) => s,
1505 }
1506 }
1507}
1508
1509#[derive(Clone, Copy, MallocSizeOf)]
1510pub(crate) enum FormEncType {
1511 TextPlain,
1512 UrlEncoded,
1513 MultipartFormData,
1514}
1515
1516#[derive(Clone, Copy, MallocSizeOf)]
1517pub(crate) enum FormMethod {
1518 Get,
1519 Post,
1520 Dialog,
1521}
1522
1523#[derive(Clone, Copy, MallocSizeOf)]
1525pub(crate) enum FormSubmitterElement<'a> {
1526 Form(&'a HTMLFormElement),
1527 Input(&'a HTMLInputElement),
1528 Button(&'a HTMLButtonElement),
1529 }
1532
1533impl FormSubmitterElement<'_> {
1534 fn action(&self) -> DOMString {
1535 match *self {
1536 FormSubmitterElement::Form(form) => form.Action(),
1537 FormSubmitterElement::Input(input_element) => input_element.get_form_attribute(
1538 &local_name!("formaction"),
1539 |i| i.FormAction(),
1540 |f| f.Action(),
1541 ),
1542 FormSubmitterElement::Button(button_element) => button_element.get_form_attribute(
1543 &local_name!("formaction"),
1544 |i| i.FormAction(),
1545 |f| f.Action(),
1546 ),
1547 }
1548 }
1549
1550 fn enctype(&self) -> FormEncType {
1551 let attr = match *self {
1552 FormSubmitterElement::Form(form) => form.Enctype(),
1553 FormSubmitterElement::Input(input_element) => input_element.get_form_attribute(
1554 &local_name!("formenctype"),
1555 |i| i.FormEnctype(),
1556 |f| f.Enctype(),
1557 ),
1558 FormSubmitterElement::Button(button_element) => button_element.get_form_attribute(
1559 &local_name!("formenctype"),
1560 |i| i.FormEnctype(),
1561 |f| f.Enctype(),
1562 ),
1563 };
1564 match_domstring_ascii!(attr,
1567 "multipart/form-data" => FormEncType::MultipartFormData,
1568 "text/plain" => FormEncType::TextPlain,
1569 _ => FormEncType::UrlEncoded,
1570 )
1571 }
1572
1573 fn method(&self) -> FormMethod {
1574 let attr = match *self {
1575 FormSubmitterElement::Form(form) => form.Method(),
1576 FormSubmitterElement::Input(input_element) => input_element.get_form_attribute(
1577 &local_name!("formmethod"),
1578 |i| i.FormMethod(),
1579 |f| f.Method(),
1580 ),
1581 FormSubmitterElement::Button(button_element) => button_element.get_form_attribute(
1582 &local_name!("formmethod"),
1583 |i| i.FormMethod(),
1584 |f| f.Method(),
1585 ),
1586 };
1587 match_domstring_ascii!(attr,
1588 "dialog" => FormMethod::Dialog,
1589 "post" => FormMethod::Post,
1590 _ => FormMethod::Get,
1591 )
1592 }
1593
1594 fn target(&self) -> DOMString {
1595 match *self {
1596 FormSubmitterElement::Form(form) => form.Target(),
1597 FormSubmitterElement::Input(input_element) => input_element.get_form_attribute(
1598 &local_name!("formtarget"),
1599 |i| i.FormTarget(),
1600 |f| f.Target(),
1601 ),
1602 FormSubmitterElement::Button(button_element) => button_element.get_form_attribute(
1603 &local_name!("formtarget"),
1604 |i| i.FormTarget(),
1605 |f| f.Target(),
1606 ),
1607 }
1608 }
1609
1610 fn no_validate(&self, _form_owner: &HTMLFormElement) -> bool {
1611 match *self {
1612 FormSubmitterElement::Form(form) => form.NoValidate(),
1613 FormSubmitterElement::Input(input_element) => input_element.get_form_boolean_attribute(
1614 &local_name!("formnovalidate"),
1615 |i| i.FormNoValidate(),
1616 |f| f.NoValidate(),
1617 ),
1618 FormSubmitterElement::Button(button_element) => button_element
1619 .get_form_boolean_attribute(
1620 &local_name!("formnovalidate"),
1621 |i| i.FormNoValidate(),
1622 |f| f.NoValidate(),
1623 ),
1624 }
1625 }
1626
1627 pub(crate) fn is_submit_button(&self) -> bool {
1629 match *self {
1630 FormSubmitterElement::Input(input_element) => input_element.is_submit_button(),
1633 FormSubmitterElement::Button(button_element) => button_element.is_submit_button(),
1635 _ => false,
1636 }
1637 }
1638
1639 pub(crate) fn form_owner(&self) -> Option<DomRoot<HTMLFormElement>> {
1641 match *self {
1642 FormSubmitterElement::Button(button_el) => button_el.form_owner(),
1643 FormSubmitterElement::Input(input_el) => input_el.form_owner(),
1644 _ => None,
1645 }
1646 }
1647}
1648
1649pub(crate) trait FormControl: DomObject<ReflectorType = ()> + NodeTraits {
1650 fn form_owner(&self) -> Option<DomRoot<HTMLFormElement>>;
1651 fn set_form_owner(&self, form: Option<&HTMLFormElement>);
1652 fn to_html_element(&self) -> &HTMLElement;
1653
1654 fn to_element(&self) -> &Element {
1655 self.to_html_element().upcast::<Element>()
1656 }
1657
1658 fn is_listed(&self) -> bool {
1659 self.to_html_element().is_listed_element()
1660 }
1661
1662 fn set_form_owner_from_parser(&self, cx: &mut JSContext, form: &HTMLFormElement) {
1667 let elem = self.to_element();
1668 let node = elem.upcast::<Node>();
1669 node.set_flag(NodeFlags::PARSER_ASSOCIATED_FORM_OWNER, true);
1670 form.add_control(cx, self);
1671 self.set_form_owner(Some(form));
1672 }
1673
1674 fn reset_form_owner(&self, cx: &mut JSContext) {
1676 let elem = self.to_element();
1677 let node = elem.upcast::<Node>();
1678 let old_owner = self.form_owner();
1679 let has_form_id = elem.has_attribute(&local_name!("form"));
1680 let nearest_form_ancestor = node
1681 .ancestors()
1682 .find_map(DomRoot::downcast::<HTMLFormElement>);
1683
1684 if old_owner.is_some() &&
1686 !(self.is_listed() && has_form_id) &&
1687 nearest_form_ancestor == old_owner
1688 {
1689 return;
1690 }
1691
1692 let new_owner = if self.is_listed() && has_form_id && elem.is_connected() {
1694 let form_id = elem.get_string_attribute(&local_name!("form"));
1698 let first_relevant_element = if let Some(shadow_root) = self.containing_shadow_root() {
1699 shadow_root
1700 .upcast::<DocumentFragment>()
1701 .GetElementById(cx, form_id)
1702 } else {
1703 node.owner_document().GetElementById(cx, form_id)
1704 };
1705
1706 first_relevant_element.and_then(DomRoot::downcast::<HTMLFormElement>)
1707 } else {
1708 nearest_form_ancestor
1710 };
1711
1712 if old_owner != new_owner {
1713 if let Some(o) = old_owner {
1714 o.remove_control(cx, self);
1715 }
1716 if let Some(ref new_owner) = new_owner {
1717 new_owner.add_control(cx, self);
1718 }
1719 if let Some(html_elem) = elem.downcast::<HTMLElement>() &&
1721 html_elem.is_form_associated_custom_element()
1722 {
1723 ScriptThread::enqueue_callback_reaction(
1724 cx,
1725 elem,
1726 CallbackReaction::FormAssociated(
1727 new_owner.as_ref().map(|form| DomRoot::from_ref(&**form)),
1728 ),
1729 None,
1730 )
1731 }
1732 self.set_form_owner(new_owner.as_deref());
1733 }
1734 }
1735
1736 fn form_attribute_mutated(&self, cx: &mut JSContext, mutation: AttributeMutation) {
1738 match mutation {
1739 AttributeMutation::Set(..) => {
1740 self.register_if_necessary();
1741 },
1742 AttributeMutation::Removed => {
1743 self.unregister_if_necessary();
1744 },
1745 }
1746
1747 self.reset_form_owner(cx);
1748 }
1749
1750 fn register_if_necessary(&self) {
1752 let elem = self.to_element();
1753 let form_id = elem.get_string_attribute(&local_name!("form"));
1754 let node = elem.upcast::<Node>();
1755
1756 if self.is_listed() && !form_id.is_empty() && node.is_connected() {
1757 node.owner_document()
1758 .register_form_id_listener(form_id, self);
1759 }
1760 }
1761
1762 fn unregister_if_necessary(&self) {
1763 let elem = self.to_element();
1764 let form_id = elem.get_string_attribute(&local_name!("form"));
1765
1766 if self.is_listed() && !form_id.is_empty() {
1767 elem.owner_document()
1768 .unregister_form_id_listener(form_id, self);
1769 }
1770 }
1771
1772 fn bind_form_control_to_tree(&self, cx: &mut JSContext) {
1774 let elem = self.to_element();
1775 let node = elem.upcast::<Node>();
1776
1777 let must_skip_reset = node.get_flag(NodeFlags::PARSER_ASSOCIATED_FORM_OWNER);
1782 node.set_flag(NodeFlags::PARSER_ASSOCIATED_FORM_OWNER, false);
1783
1784 if !must_skip_reset {
1785 self.form_attribute_mutated(
1786 cx,
1787 AttributeMutation::Set(None, AttributeMutationReason::Directly),
1788 );
1789 }
1790 }
1791
1792 fn unbind_form_control_from_tree(&self, cx: &mut JSContext) {
1794 let elem = self.to_element();
1795 let has_form_attr = elem.has_attribute(&local_name!("form"));
1796 let same_subtree = self
1797 .form_owner()
1798 .is_none_or(|form| elem.is_in_same_home_subtree(&*form));
1799
1800 self.unregister_if_necessary();
1801
1802 if !same_subtree || (self.is_listed() && has_form_attr) {
1808 self.reset_form_owner(cx);
1809 }
1810 }
1811
1812 fn get_form_attribute<InputFn, OwnerFn>(
1813 &self,
1814 attr: &LocalName,
1815 input: InputFn,
1816 owner: OwnerFn,
1817 ) -> DOMString
1818 where
1819 InputFn: Fn(&Self) -> DOMString,
1820 OwnerFn: Fn(&HTMLFormElement) -> DOMString,
1821 Self: Sized,
1822 {
1823 if self.to_element().has_attribute(attr) {
1824 input(self)
1825 } else {
1826 self.form_owner().map_or(DOMString::new(), |t| owner(&t))
1827 }
1828 }
1829
1830 fn get_form_boolean_attribute<InputFn, OwnerFn>(
1831 &self,
1832 attr: &LocalName,
1833 input: InputFn,
1834 owner: OwnerFn,
1835 ) -> bool
1836 where
1837 InputFn: Fn(&Self) -> bool,
1838 OwnerFn: Fn(&HTMLFormElement) -> bool,
1839 Self: Sized,
1840 {
1841 if self.to_element().has_attribute(attr) {
1842 input(self)
1843 } else {
1844 self.form_owner().is_some_and(|t| owner(&t))
1845 }
1846 }
1847
1848 fn is_candidate_for_constraint_validation(&self) -> bool {
1850 let element = self.to_element();
1851 let html_element = element.downcast::<HTMLElement>();
1852 if let Some(html_element) = html_element {
1853 html_element.is_submittable_element() || element.is_instance_validatable()
1854 } else {
1855 false
1856 }
1857 }
1858
1859 fn moving_steps(&self, cx: &mut JSContext) {
1860 let same_subtree = self
1863 .form_owner()
1864 .is_none_or(|form| self.to_element().is_in_same_home_subtree(&*form));
1865 if !same_subtree {
1866 self.reset_form_owner(cx)
1867 }
1868 }
1869
1870 }
1873
1874impl VirtualMethods for HTMLFormElement {
1875 fn super_type(&self) -> Option<&dyn VirtualMethods> {
1876 Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods)
1877 }
1878
1879 fn unbind_from_tree(&self, cx: &mut JSContext, context: &UnbindContext) {
1880 self.super_type().unwrap().unbind_from_tree(cx, context);
1881
1882 rooted_vec!(let mut to_reset);
1885 to_reset.extend(
1886 self.controls
1887 .borrow()
1888 .iter()
1889 .filter(|c| !c.is_in_same_home_subtree(self))
1890 .cloned(),
1891 );
1892
1893 for control in to_reset.iter() {
1894 control
1895 .as_maybe_form_control()
1896 .expect("Element must be a form control")
1897 .reset_form_owner(cx);
1898 }
1899 }
1900
1901 fn parse_plain_attribute(&self, name: &LocalName, value: DOMString) -> AttrValue {
1902 match name {
1903 &local_name!("rel") => AttrValue::from_serialized_tokenlist(value.into()),
1904 _ => self
1905 .super_type()
1906 .unwrap()
1907 .parse_plain_attribute(name, value),
1908 }
1909 }
1910
1911 fn attribute_mutated(
1912 &self,
1913 cx: &mut JSContext,
1914 attr: AttrRef<'_>,
1915 mutation: AttributeMutation,
1916 ) {
1917 self.super_type()
1918 .unwrap()
1919 .attribute_mutated(cx, attr, mutation);
1920
1921 match *attr.local_name() {
1922 local_name!("rel") | local_name!("rev") => {
1923 self.relations
1924 .set(LinkRelations::for_element(self.upcast()));
1925 },
1926 _ => {},
1927 }
1928 }
1929}
1930
1931pub(crate) trait FormControlElementHelpers {
1932 fn as_maybe_form_control(&self) -> Option<&dyn FormControl>;
1933}
1934
1935impl FormControlElementHelpers for Element {
1936 fn as_maybe_form_control(&self) -> Option<&dyn FormControl> {
1937 let node = self.upcast::<Node>();
1938
1939 match node.type_id() {
1940 NodeTypeId::Element(ElementTypeId::HTMLElement(
1941 HTMLElementTypeId::HTMLButtonElement,
1942 )) => Some(self.downcast::<HTMLButtonElement>().unwrap() as &dyn FormControl),
1943 NodeTypeId::Element(ElementTypeId::HTMLElement(
1944 HTMLElementTypeId::HTMLFieldSetElement,
1945 )) => Some(self.downcast::<HTMLFieldSetElement>().unwrap() as &dyn FormControl),
1946 NodeTypeId::Element(ElementTypeId::HTMLElement(
1947 HTMLElementTypeId::HTMLImageElement,
1948 )) => Some(self.downcast::<HTMLImageElement>().unwrap() as &dyn FormControl),
1949 NodeTypeId::Element(ElementTypeId::HTMLElement(
1950 HTMLElementTypeId::HTMLInputElement,
1951 )) => Some(self.downcast::<HTMLInputElement>().unwrap() as &dyn FormControl),
1952 NodeTypeId::Element(ElementTypeId::HTMLElement(
1953 HTMLElementTypeId::HTMLLegendElement,
1954 )) => Some(self.downcast::<HTMLLegendElement>().unwrap() as &dyn FormControl),
1955 NodeTypeId::Element(ElementTypeId::HTMLElement(
1956 HTMLElementTypeId::HTMLObjectElement,
1957 )) => Some(self.downcast::<HTMLObjectElement>().unwrap() as &dyn FormControl),
1958 NodeTypeId::Element(ElementTypeId::HTMLElement(
1959 HTMLElementTypeId::HTMLOutputElement,
1960 )) => Some(self.downcast::<HTMLOutputElement>().unwrap() as &dyn FormControl),
1961 NodeTypeId::Element(ElementTypeId::HTMLElement(
1962 HTMLElementTypeId::HTMLSelectElement,
1963 )) => Some(self.downcast::<HTMLSelectElement>().unwrap() as &dyn FormControl),
1964 NodeTypeId::Element(ElementTypeId::HTMLElement(
1965 HTMLElementTypeId::HTMLTextAreaElement,
1966 )) => Some(self.downcast::<HTMLTextAreaElement>().unwrap() as &dyn FormControl),
1967 _ => self.downcast::<HTMLElement>().and_then(|elem| {
1968 if elem.is_form_associated_custom_element() {
1969 Some(elem as &dyn FormControl)
1970 } else {
1971 None
1972 }
1973 }),
1974 }
1975 }
1976}
1977
1978fn encode_plaintext(pairs: impl Iterator<Item = (String, String)>) -> String {
1980 let mut result = String::new();
1982 for (name, value) in pairs {
1984 result.push_str(&name);
1986 result.push('=');
1988 result.push_str(&value);
1990 result.push_str("\r\n");
1993 }
1994 result
1996}
1997
1998fn encode_with_html_fallback<'a>(input: &'a str, encoding: &'static Encoding) -> Cow<'a, [u8]> {
2004 encoding.encode(input).0
2005}
2006
2007pub(crate) fn encode_multipart_form_data(
2009 form_data: &mut [FormDatum],
2010 boundary: String,
2011 encoding: &'static Encoding,
2012) -> Vec<u8> {
2013 let mut result = vec![];
2014
2015 fn escape_header_bytes(input: &[u8]) -> Vec<u8> {
2022 let mut output = Vec::with_capacity(input.len());
2023 for &b in input {
2024 match b {
2025 b'\n' => output.extend(b"%0A"),
2026 b'\r' => output.extend(b"%0D"),
2027 b'"' => output.extend(b"%22"),
2028 _ => output.push(b),
2029 }
2030 }
2031 output
2032 }
2033
2034 for entry in form_data.iter_mut() {
2035 entry.name = entry.name.normalize_crlf().into();
2039
2040 if let FormDatumValue::String(ref s) = entry.value {
2045 entry.value = FormDatumValue::String(s.normalize_crlf().into());
2046 }
2047
2048 let mut boundary_bytes = format!("--{}\r\n", boundary).into_bytes();
2050 result.append(&mut boundary_bytes);
2051
2052 let name_str = &*entry.name.str();
2054 let encoded_name = encode_with_html_fallback(name_str, encoding);
2055 let escaped_name = escape_header_bytes(&encoded_name);
2057
2058 match entry.value {
2059 FormDatumValue::String(ref s) => {
2060 let value_str = &*s.str();
2062 let encoded_value = encode_with_html_fallback(value_str, encoding);
2063
2064 result.extend(b"Content-Disposition: form-data; name=\"");
2066 result.extend(&escaped_name);
2067 result.extend(b"\"\r\n\r\n");
2068 result.extend_from_slice(&encoded_value);
2069 result.extend(b"\r\n");
2070 },
2071 FormDatumValue::File(ref f) => {
2072 let filename_str = &*f.name().str();
2074 let encoded_filename = encode_with_html_fallback(filename_str, encoding);
2075 let escaped_filename = escape_header_bytes(&encoded_filename);
2077
2078 result.extend(b"Content-Disposition: form-data; name=\"");
2079 result.extend(&escaped_name);
2080 result.extend(b"\"; filename=\"");
2081 result.extend(&escaped_filename);
2082
2083 result.extend(b"\"\r\nContent-Type: ");
2085
2086 let content_type: Mime = f
2087 .upcast::<Blob>()
2088 .Type()
2089 .parse()
2090 .unwrap_or(mime::TEXT_PLAIN);
2091 result.extend(content_type.as_ref().as_bytes());
2092 result.extend(b"\r\n\r\n");
2093
2094 let mut bytes = f.upcast::<Blob>().get_bytes().unwrap_or_default();
2095
2096 result.append(&mut bytes);
2097 result.extend(b"\r\n");
2098 },
2099 }
2100 }
2101
2102 let mut boundary_bytes = format!("--{boundary}--\r\n").into_bytes();
2104 result.append(&mut boundary_bytes);
2105
2106 result
2107}
2108
2109pub(crate) fn generate_boundary() -> String {
2111 let i1 = random::<u32>();
2112 let i2 = random::<u32>();
2113
2114 format!("---------------------------{0}{1}", i1, i2)
2115}