diff options
author | Maxim Tsoy <maks.tsoy@gmail.com> | 2020-06-27 02:01:01 +0200 |
---|---|---|
committer | Maxim Tsoy <maks.tsoy@gmail.com> | 2020-07-02 12:33:21 +0200 |
commit | 8194da2752feda8ae58de455ebe0ca6f5904feb8 (patch) | |
tree | 1668ad0396fd6e1a5867392c1d99c389a17bb43c | |
parent | 19b36bd7952630d2e4a749565c570d3cca217658 (diff) | |
download | servo-8194da2752feda8ae58de455ebe0ca6f5904feb8.tar.gz servo-8194da2752feda8ae58de455ebe0ca6f5904feb8.zip |
Implement HTMLFormElement.requestSubmit()Also includes a fix for reentrant form submission behavior
8 files changed, 143 insertions, 80 deletions
diff --git a/components/script/dom/htmlbuttonelement.rs b/components/script/dom/htmlbuttonelement.rs index 100d9fb92b7..9e9898f328c 100755 --- a/components/script/dom/htmlbuttonelement.rs +++ b/components/script/dom/htmlbuttonelement.rs @@ -77,6 +77,11 @@ impl HTMLButtonElement { document, ) } + + #[inline] + pub fn is_submit_button(&self) -> bool { + self.button_type.get() == ButtonType::Submit + } } impl HTMLButtonElementMethods for HTMLButtonElement { diff --git a/components/script/dom/htmlformelement.rs b/components/script/dom/htmlformelement.rs index 76ae4a7ab27..4e2439b13dd 100644 --- a/components/script/dom/htmlformelement.rs +++ b/components/script/dom/htmlformelement.rs @@ -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")); + 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); + // 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 { diff --git a/components/script/dom/htmlinputelement.rs b/components/script/dom/htmlinputelement.rs index 1078f71d59e..a8ca388c172 100755 --- a/components/script/dom/htmlinputelement.rs +++ b/components/script/dom/htmlinputelement.rs @@ -405,6 +405,12 @@ impl HTMLInputElement { self.input_type.get() } + #[inline] + pub fn is_submit_button(&self) -> bool { + let input_type = self.input_type.get(); + input_type == InputType::Submit || input_type == InputType::Image + } + pub fn disable_sanitization(&self) { self.sanitization_flag.set(false); } diff --git a/components/script/dom/webidls/HTMLFormElement.webidl b/components/script/dom/webidls/HTMLFormElement.webidl index c7ea91a472c..78d7e96ec39 100644 --- a/components/script/dom/webidls/HTMLFormElement.webidl +++ b/components/script/dom/webidls/HTMLFormElement.webidl @@ -32,6 +32,7 @@ interface HTMLFormElement : HTMLElement { getter (RadioNodeList or Element) (DOMString name); void submit(); + [Throws] void requestSubmit(optional HTMLElement? submitter = null); [CEReactions] void reset(); boolean checkValidity(); diff --git a/tests/wpt/metadata-layout-2020/html/dom/idlharness.https.html.ini b/tests/wpt/metadata-layout-2020/html/dom/idlharness.https.html.ini index 3dabbc6d6a3..a3cf6d4bf2d 100644 --- a/tests/wpt/metadata-layout-2020/html/dom/idlharness.https.html.ini +++ b/tests/wpt/metadata-layout-2020/html/dom/idlharness.https.html.ini @@ -1650,9 +1650,6 @@ [idlharness.https.html?include=HTML.*] - [HTMLFormElement interface: calling requestSubmit(optional HTMLElement?) on document.createElement("form") with too few arguments must throw TypeError] - expected: FAIL - [HTMLTableSectionElement interface: document.createElement("tfoot") must inherit property "align" with the proper type] expected: FAIL @@ -2307,9 +2304,6 @@ [HTMLObjectElement interface: document.createElement("object") must inherit property "archive" with the proper type] expected: FAIL - [HTMLFormElement interface: operation requestSubmit(optional HTMLElement?)] - expected: FAIL - [HTMLTableColElement interface: document.createElement("col") must inherit property "chOff" with the proper type] expected: FAIL diff --git a/tests/wpt/metadata/html/dom/idlharness.https.html.ini b/tests/wpt/metadata/html/dom/idlharness.https.html.ini index bbc23fe7631..6f4b20ee287 100644 --- a/tests/wpt/metadata/html/dom/idlharness.https.html.ini +++ b/tests/wpt/metadata/html/dom/idlharness.https.html.ini @@ -3621,9 +3621,6 @@ [HTMLAreaElement interface: document.createElement("area") must inherit property "download" with the proper type] expected: FAIL - [HTMLFormElement interface: calling requestSubmit(HTMLElement) on document.createElement("form") with too few arguments must throw TypeError] - expected: FAIL - [HTMLProgressElement interface: attribute value] expected: FAIL @@ -3711,9 +3708,6 @@ [HTMLImageElement interface: document.createElement("img") must inherit property "loading" with the proper type] expected: FAIL - [HTMLFormElement interface: calling requestSubmit(optional HTMLElement?) on document.createElement("form") with too few arguments must throw TypeError] - expected: FAIL - [HTMLSlotElement interface: calling assignedNodes(optional AssignedNodesOptions) on document.createElement("slot") with too few arguments must throw TypeError] expected: FAIL @@ -3732,18 +3726,12 @@ [HTMLAllCollection interface: document.all must inherit property "item(optional DOMString)" with the proper type] expected: FAIL - [HTMLFormElement interface: operation requestSubmit(optional HTMLElement?)] - expected: FAIL - [HTMLAllCollection interface: calling item(optional DOMString) on document.all with too few arguments must throw TypeError] expected: FAIL [HTMLAllCollection interface: operation item(optional DOMString)] expected: FAIL - [HTMLFormElement interface: document.createElement("form") must inherit property "requestSubmit(optional HTMLElement?)" with the proper type] - expected: FAIL - [HTMLCanvasElement interface: document.createElement("canvas") must inherit property "toBlob(BlobCallback, optional DOMString, optional any)" with the proper type] expected: FAIL diff --git a/tests/wpt/metadata/html/semantics/forms/form-submission-0/form-submission-algorithm.html.ini b/tests/wpt/metadata/html/semantics/forms/form-submission-0/form-submission-algorithm.html.ini index 193868a895b..3d6a35c28e5 100644 --- a/tests/wpt/metadata/html/semantics/forms/form-submission-0/form-submission-algorithm.html.ini +++ b/tests/wpt/metadata/html/semantics/forms/form-submission-0/form-submission-algorithm.html.ini @@ -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 - diff --git a/tests/wpt/metadata/html/semantics/forms/the-form-element/form-requestsubmit.html.ini b/tests/wpt/metadata/html/semantics/forms/the-form-element/form-requestsubmit.html.ini index a58b5dc12ca..4f05392b2e0 100644 --- a/tests/wpt/metadata/html/semantics/forms/the-form-element/form-requestsubmit.html.ini +++ b/tests/wpt/metadata/html/semantics/forms/the-form-element/form-requestsubmit.html.ini @@ -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 - |