-
-
Notifications
You must be signed in to change notification settings - Fork 3.6k
Implement HTMLFormElement.requestSubmit() #27100
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -16,6 +16,7 @@ use crate::dom::bindings::codegen::Bindings::HTMLInputElementBinding::HTMLInputE | |
| use crate::dom::bindings::codegen::Bindings::HTMLTextAreaElementBinding::HTMLTextAreaElementMethods; | ||
| use crate::dom::bindings::codegen::Bindings::NodeListBinding::NodeListMethods; | ||
| use crate::dom::bindings::codegen::Bindings::WindowBinding::WindowBinding::WindowMethods; | ||
| use crate::dom::bindings::error::{Error, Fallible}; | ||
| use crate::dom::bindings::inheritance::{Castable, ElementTypeId, HTMLElementTypeId, NodeTypeId}; | ||
| use crate::dom::bindings::refcounted::Trusted; | ||
| use crate::dom::bindings::reflector::DomObject; | ||
|
|
@@ -88,6 +89,7 @@ pub struct HTMLFormElement { | |
| generation_id: Cell<GenerationId>, | ||
| controls: DomRefCell<Vec<Dom<Element>>>, | ||
| past_names_map: DomRefCell<HashMap<Atom, (Dom<Element>, Tm)>>, | ||
| firing_submission_events: Cell<bool>, | ||
| } | ||
|
|
||
| impl HTMLFormElement { | ||
|
|
@@ -104,6 +106,7 @@ impl HTMLFormElement { | |
| generation_id: Cell::new(GenerationId(0)), | ||
| controls: DomRefCell::new(Vec::new()), | ||
| past_names_map: DomRefCell::new(HashMap::new()), | ||
| firing_submission_events: Cell::new(false), | ||
| } | ||
| } | ||
|
|
||
|
|
@@ -243,6 +246,67 @@ impl HTMLFormElementMethods for HTMLFormElement { | |
| self.submit(SubmittedFrom::FromForm, FormSubmitter::FormElement(self)); | ||
| } | ||
|
|
||
| // https://html.spec.whatwg.org/multipage/#dom-form-requestsubmit | ||
| fn RequestSubmit(&self, submitter: Option<&HTMLElement>) -> Fallible<()> { | ||
| let submitter: FormSubmitter = match submitter { | ||
| Some(submitter_element) => { | ||
| // Step 1.1 | ||
| let error_not_a_submit_button = | ||
| Err(Error::Type("submitter must be a submit button".to_string())); | ||
|
|
||
| let element = match submitter_element.upcast::<Node>().type_id() { | ||
| NodeTypeId::Element(ElementTypeId::HTMLElement(element)) => element, | ||
| _ => { | ||
| return error_not_a_submit_button; | ||
| }, | ||
| }; | ||
|
|
||
| let submit_button = match element { | ||
| HTMLElementTypeId::HTMLInputElement => FormSubmitter::InputElement( | ||
| &submitter_element | ||
| .downcast::<HTMLInputElement>() | ||
| .expect("Failed to downcast submitter elem to HTMLInputElement."), | ||
| ), | ||
| HTMLElementTypeId::HTMLButtonElement => FormSubmitter::ButtonElement( | ||
| &submitter_element | ||
| .downcast::<HTMLButtonElement>() | ||
| .expect("Failed to downcast submitter elem to HTMLButtonElement."), | ||
| ), | ||
| _ => { | ||
| return error_not_a_submit_button; | ||
| }, | ||
| }; | ||
|
|
||
| if !submit_button.is_submit_button() { | ||
| return error_not_a_submit_button; | ||
| } | ||
|
|
||
| let submitters_owner = submit_button.form_owner(); | ||
|
|
||
| // Step 1.2 | ||
| let owner = match submitters_owner { | ||
| Some(owner) => owner, | ||
| None => { | ||
| return Err(Error::NotFound); | ||
| }, | ||
| }; | ||
|
|
||
| if *owner != *self { | ||
| return Err(Error::NotFound); | ||
| } | ||
|
|
||
| submit_button | ||
| }, | ||
| None => { | ||
| // Step 2 | ||
| FormSubmitter::FormElement(&self) | ||
| }, | ||
| }; | ||
| // Step 3 | ||
| self.submit(SubmittedFrom::NotFromForm, submitter); | ||
| Ok(()) | ||
| } | ||
|
|
||
| // https://html.spec.whatwg.org/multipage/#dom-form-reset | ||
| fn Reset(&self) { | ||
| self.reset(ResetFrom::FromForm); | ||
|
|
@@ -599,28 +663,38 @@ impl HTMLFormElement { | |
| let base = doc.base_url(); | ||
| // TODO: Handle browsing contexts (Step 4, 5) | ||
| // Step 6 | ||
| if submit_method_flag == SubmittedFrom::NotFromForm && !submitter.no_validate(self) { | ||
| if self.interactive_validation().is_err() { | ||
| // TODO: Implement event handlers on all form control elements | ||
| self.upcast::<EventTarget>().fire_event(atom!("invalid")); | ||
| if submit_method_flag == SubmittedFrom::NotFromForm { | ||
| // Step 6.1 | ||
| if self.firing_submission_events.get() { | ||
| return; | ||
| } | ||
| } | ||
| // Step 7 | ||
| // spec calls this "submitterButton" but it doesn't have to be a button, | ||
| // just not be the form itself | ||
| let submitter_button = match submitter { | ||
| FormSubmitter::FormElement(f) => { | ||
| if f == self { | ||
| None | ||
| } else { | ||
| Some(f.upcast::<HTMLElement>()) | ||
| // Step 6.2 | ||
| self.firing_submission_events.set(true); | ||
| // Step 6.3 | ||
| if !submitter.no_validate(self) { | ||
| if self.interactive_validation().is_err() { | ||
| // TODO: Implement event handlers on all form control elements | ||
| self.upcast::<EventTarget>().fire_event(atom!("invalid")); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This looks un-necessary, as it is already done at step 6.1 of static validation(which is called into at step 1 of interactive validation).
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I didn't change this part, I merely added indentation. Looking at the code you mentioned, I got an impression that static validation handles only elements inside the form, whereas this line fires an event on the form itself. I might be wrong about this, would you mind double-checking?
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ok. You're right about the different event targets, and I actually don't see where in the spec the event is fired on the element as a whole, however if this isn't an actual change, let's leave it for now...
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Actually I think I've found where the event should be fired on the element as a whole, and this line appears incorrect, so I've filed a follow-up at #27133 |
||
| self.firing_submission_events.set(false); | ||
| return; | ||
| } | ||
| }, | ||
| FormSubmitter::InputElement(i) => Some(i.upcast::<HTMLElement>()), | ||
| FormSubmitter::ButtonElement(b) => Some(b.upcast::<HTMLElement>()), | ||
| }; | ||
| if submit_method_flag == SubmittedFrom::NotFromForm { | ||
| } | ||
| // Step 6.4 | ||
| // spec calls this "submitterButton" but it doesn't have to be a button, | ||
| // just not be the form itself | ||
| let submitter_button = match submitter { | ||
| FormSubmitter::FormElement(f) => { | ||
| if f == self { | ||
| None | ||
| } else { | ||
| Some(f.upcast::<HTMLElement>()) | ||
| } | ||
| }, | ||
| FormSubmitter::InputElement(i) => Some(i.upcast::<HTMLElement>()), | ||
| FormSubmitter::ButtonElement(b) => Some(b.upcast::<HTMLElement>()), | ||
| }; | ||
|
|
||
| // Step 6.5 | ||
| let event = SubmitEvent::new( | ||
| &self.global(), | ||
| atom!("submit"), | ||
|
|
@@ -630,48 +704,51 @@ impl HTMLFormElement { | |
| ); | ||
| let event = event.upcast::<Event>(); | ||
| event.fire(self.upcast::<EventTarget>()); | ||
|
|
||
| // Step 6.6 | ||
| self.firing_submission_events.set(false); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please number as Step 6.6
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done |
||
| // Step 6.7 | ||
| if event.DefaultPrevented() { | ||
| return; | ||
| } | ||
|
|
||
| // Step 7-3 | ||
| // Step 6.8 | ||
| if self.upcast::<Element>().cannot_navigate() { | ||
| return; | ||
| } | ||
| } | ||
|
|
||
| // Step 8 | ||
| // Step 7 | ||
| let encoding = self.pick_encoding(); | ||
|
|
||
| // Step 9 | ||
| // Step 8 | ||
| let mut form_data = match self.get_form_dataset(Some(submitter), Some(encoding)) { | ||
| Some(form_data) => form_data, | ||
| None => return, | ||
| }; | ||
|
|
||
| // Step 10 | ||
| // Step 9 | ||
| if self.upcast::<Element>().cannot_navigate() { | ||
| return; | ||
| } | ||
|
|
||
| // Step 11 | ||
| // Step 10 | ||
| let mut action = submitter.action(); | ||
|
|
||
| // Step 12 | ||
| // Step 11 | ||
| if action.is_empty() { | ||
| action = DOMString::from(base.as_str()); | ||
| } | ||
| // Step 13-14 | ||
| // Step 12-13 | ||
| let action_components = match base.join(&action) { | ||
| Ok(url) => url, | ||
| Err(_) => return, | ||
| }; | ||
| // Step 15-17 | ||
| // Step 14-16 | ||
| let scheme = action_components.scheme().to_owned(); | ||
| let enctype = submitter.enctype(); | ||
| let method = submitter.method(); | ||
|
|
||
| // Step 18-21 | ||
| // Step 17-21 | ||
| let target_attribute_value = submitter.target(); | ||
| let source = doc.browsing_context().unwrap(); | ||
| let (maybe_chosen, _new) = source.choose_browsing_context(target_attribute_value, false); | ||
|
|
@@ -1232,11 +1309,14 @@ pub enum FormMethod { | |
| FormDialog, | ||
| } | ||
|
|
||
| /// <https://html.spec.whatwg.org/multipage/#form-associated-element> | ||
| #[derive(Clone, Copy, MallocSizeOf)] | ||
| pub enum FormSubmitter<'a> { | ||
| FormElement(&'a HTMLFormElement), | ||
| InputElement(&'a HTMLInputElement), | ||
| ButtonElement(&'a HTMLButtonElement), // TODO: image submit, etc etc | ||
| ButtonElement(&'a HTMLButtonElement), | ||
| // TODO: implement other types of form associated elements | ||
| // (including custom elements) that can be passed as submitter. | ||
| } | ||
|
|
||
| impl<'a> FormSubmitter<'a> { | ||
|
|
@@ -1332,6 +1412,27 @@ impl<'a> FormSubmitter<'a> { | |
| ), | ||
| } | ||
| } | ||
|
|
||
| // https://html.spec.whatwg.org/multipage/#concept-submit-button | ||
| fn is_submit_button(&self) -> bool { | ||
| match *self { | ||
| // https://html.spec.whatwg.org/multipage/#image-button-state-(type=image) | ||
| // https://html.spec.whatwg.org/multipage/#submit-button-state-(type=submit) | ||
| FormSubmitter::InputElement(input_element) => input_element.is_submit_button(), | ||
| // https://html.spec.whatwg.org/multipage/#attr-button-type-submit-state | ||
| FormSubmitter::ButtonElement(button_element) => button_element.is_submit_button(), | ||
| _ => false, | ||
| } | ||
| } | ||
|
|
||
| // https://html.spec.whatwg.org/multipage/#form-owner | ||
| fn form_owner(&self) -> Option<DomRoot<HTMLFormElement>> { | ||
| match *self { | ||
| FormSubmitter::ButtonElement(button_el) => button_el.form_owner(), | ||
| FormSubmitter::InputElement(input_el) => input_el.form_owner(), | ||
| _ => None, | ||
| } | ||
| } | ||
| } | ||
|
|
||
| pub trait FormControl: DomObject { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,19 +1,3 @@ | ||
| [form-submission-algorithm.html] | ||
| [If form's firing submission events is true, then return; 'invalid' event] | ||
| expected: FAIL | ||
|
|
||
| [firing an event named submit; form.requestSubmit(submitter)] | ||
| expected: FAIL | ||
|
|
||
| [firing an event named submit; form.requestSubmit(null)] | ||
| expected: FAIL | ||
|
|
||
| [firing an event named submit; form.requestSubmit()] | ||
| expected: FAIL | ||
|
|
||
| [Submission URL should always have a non-null query part] | ||
| expected: FAIL | ||
|
|
||
| [If firing submission events flag of form is true, then return] | ||
| expected: FAIL | ||
|
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,22 +1,6 @@ | ||
| [form-requestsubmit.html] | ||
| [requestSubmit() doesn't run interactive validation reentrantly] | ||
| expected: FAIL | ||
|
|
||
| [The value of the submitter should be appended, and form* attributes of the submitter should be handled.] | ||
| expected: FAIL | ||
|
|
||
| [Passing a submit button not owned by the context object should throw] | ||
| expected: FAIL | ||
|
|
||
| [requestSubmit() for a disconnected form should not submit the form] | ||
| expected: FAIL | ||
|
|
||
| [requestSubmit() should trigger interactive form validation] | ||
| expected: FAIL | ||
|
|
||
| [requestSubmit() doesn't run form submission reentrantly] | ||
| expected: FAIL | ||
|
|
||
| [requestSubmit() should accept button[type=submit\], input[type=submit\], and input[type=image\]] | ||
| expected: FAIL | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please number the various Step 6 substeps, like you've already done with 6.4 below
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done, for the whole function (numbers were off, I suppose the spec changed at some point)