Skip to content

Commit

Permalink
Implement HTMLFormElement.requestSubmit()
Browse files Browse the repository at this point in the history
Also includes a fix for reentrant form submission behavior
  • Loading branch information
muodov committed Jul 1, 2020
1 parent 19b36bd commit 332ad1a
Show file tree
Hide file tree
Showing 5 changed files with 143 additions and 46 deletions.
5 changes: 5 additions & 0 deletions components/script/dom/htmlbuttonelement.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
161 changes: 131 additions & 30 deletions components/script/dom/htmlformelement.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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 {
Expand All @@ -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),
}
}

Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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"),
Expand All @@ -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);
Expand Down Expand Up @@ -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> {
Expand Down Expand Up @@ -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 {
Expand Down
6 changes: 6 additions & 0 deletions components/script/dom/htmlinputelement.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down
1 change: 1 addition & 0 deletions components/script/dom/webidls/HTMLFormElement.webidl
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
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

0 comments on commit 332ad1a

Please sign in to comment.