Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 {
Copy link
Copy Markdown
Member

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

Copy link
Copy Markdown
Contributor Author

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)

// 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"));
Copy link
Copy Markdown
Member

@gterzian gterzian Jun 29, 2020

Choose a reason for hiding this comment

The 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).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The 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?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The 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...

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The 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"),
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);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please number as Step 6.6

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The 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);
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
Expand Up @@ -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

Expand Down Expand Up @@ -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

Expand Down
12 changes: 0 additions & 12 deletions tests/wpt/metadata/html/dom/idlharness.https.html.ini
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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

Expand All @@ -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

Expand Down
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