mirror of
https://github.com/servo/servo.git
synced 2025-06-06 16:45:39 +00:00
Implement HTMLFormElement.requestSubmit()Also includes a fix for reentrant form submission behavior
This commit is contained in:
parent
19b36bd795
commit
8194da2752
8 changed files with 143 additions and 80 deletions
|
@ -77,6 +77,11 @@ impl HTMLButtonElement {
|
||||||
document,
|
document,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn is_submit_button(&self) -> bool {
|
||||||
|
self.button_type.get() == ButtonType::Submit
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl HTMLButtonElementMethods for HTMLButtonElement {
|
impl HTMLButtonElementMethods for HTMLButtonElement {
|
||||||
|
|
|
@ -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::HTMLTextAreaElementBinding::HTMLTextAreaElementMethods;
|
||||||
use crate::dom::bindings::codegen::Bindings::NodeListBinding::NodeListMethods;
|
use crate::dom::bindings::codegen::Bindings::NodeListBinding::NodeListMethods;
|
||||||
use crate::dom::bindings::codegen::Bindings::WindowBinding::WindowBinding::WindowMethods;
|
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::inheritance::{Castable, ElementTypeId, HTMLElementTypeId, NodeTypeId};
|
||||||
use crate::dom::bindings::refcounted::Trusted;
|
use crate::dom::bindings::refcounted::Trusted;
|
||||||
use crate::dom::bindings::reflector::DomObject;
|
use crate::dom::bindings::reflector::DomObject;
|
||||||
|
@ -88,6 +89,7 @@ pub struct HTMLFormElement {
|
||||||
generation_id: Cell<GenerationId>,
|
generation_id: Cell<GenerationId>,
|
||||||
controls: DomRefCell<Vec<Dom<Element>>>,
|
controls: DomRefCell<Vec<Dom<Element>>>,
|
||||||
past_names_map: DomRefCell<HashMap<Atom, (Dom<Element>, Tm)>>,
|
past_names_map: DomRefCell<HashMap<Atom, (Dom<Element>, Tm)>>,
|
||||||
|
firing_submission_events: Cell<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl HTMLFormElement {
|
impl HTMLFormElement {
|
||||||
|
@ -104,6 +106,7 @@ impl HTMLFormElement {
|
||||||
generation_id: Cell::new(GenerationId(0)),
|
generation_id: Cell::new(GenerationId(0)),
|
||||||
controls: DomRefCell::new(Vec::new()),
|
controls: DomRefCell::new(Vec::new()),
|
||||||
past_names_map: DomRefCell::new(HashMap::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));
|
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
|
// https://html.spec.whatwg.org/multipage/#dom-form-reset
|
||||||
fn Reset(&self) {
|
fn Reset(&self) {
|
||||||
self.reset(ResetFrom::FromForm);
|
self.reset(ResetFrom::FromForm);
|
||||||
|
@ -599,28 +663,38 @@ impl HTMLFormElement {
|
||||||
let base = doc.base_url();
|
let base = doc.base_url();
|
||||||
// TODO: Handle browsing contexts (Step 4, 5)
|
// TODO: Handle browsing contexts (Step 4, 5)
|
||||||
// Step 6
|
// Step 6
|
||||||
if submit_method_flag == SubmittedFrom::NotFromForm && !submitter.no_validate(self) {
|
if submit_method_flag == SubmittedFrom::NotFromForm {
|
||||||
if self.interactive_validation().is_err() {
|
// Step 6.1
|
||||||
// TODO: Implement event handlers on all form control elements
|
if self.firing_submission_events.get() {
|
||||||
self.upcast::<EventTarget>().fire_event(atom!("invalid"));
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
// Step 6.2
|
||||||
// Step 7
|
self.firing_submission_events.set(true);
|
||||||
// spec calls this "submitterButton" but it doesn't have to be a button,
|
// Step 6.3
|
||||||
// just not be the form itself
|
if !submitter.no_validate(self) {
|
||||||
let submitter_button = match submitter {
|
if self.interactive_validation().is_err() {
|
||||||
FormSubmitter::FormElement(f) => {
|
// TODO: Implement event handlers on all form control elements
|
||||||
if f == self {
|
self.upcast::<EventTarget>().fire_event(atom!("invalid"));
|
||||||
None
|
self.firing_submission_events.set(false);
|
||||||
} else {
|
return;
|
||||||
Some(f.upcast::<HTMLElement>())
|
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
FormSubmitter::InputElement(i) => Some(i.upcast::<HTMLElement>()),
|
// Step 6.4
|
||||||
FormSubmitter::ButtonElement(b) => Some(b.upcast::<HTMLElement>()),
|
// spec calls this "submitterButton" but it doesn't have to be a button,
|
||||||
};
|
// just not be the form itself
|
||||||
if submit_method_flag == SubmittedFrom::NotFromForm {
|
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(
|
let event = SubmitEvent::new(
|
||||||
&self.global(),
|
&self.global(),
|
||||||
atom!("submit"),
|
atom!("submit"),
|
||||||
|
@ -630,48 +704,51 @@ impl HTMLFormElement {
|
||||||
);
|
);
|
||||||
let event = event.upcast::<Event>();
|
let event = event.upcast::<Event>();
|
||||||
event.fire(self.upcast::<EventTarget>());
|
event.fire(self.upcast::<EventTarget>());
|
||||||
|
|
||||||
|
// Step 6.6
|
||||||
|
self.firing_submission_events.set(false);
|
||||||
|
// Step 6.7
|
||||||
if event.DefaultPrevented() {
|
if event.DefaultPrevented() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
// Step 6.8
|
||||||
// Step 7-3
|
|
||||||
if self.upcast::<Element>().cannot_navigate() {
|
if self.upcast::<Element>().cannot_navigate() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Step 8
|
// Step 7
|
||||||
let encoding = self.pick_encoding();
|
let encoding = self.pick_encoding();
|
||||||
|
|
||||||
// Step 9
|
// Step 8
|
||||||
let mut form_data = match self.get_form_dataset(Some(submitter), Some(encoding)) {
|
let mut form_data = match self.get_form_dataset(Some(submitter), Some(encoding)) {
|
||||||
Some(form_data) => form_data,
|
Some(form_data) => form_data,
|
||||||
None => return,
|
None => return,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Step 10
|
// Step 9
|
||||||
if self.upcast::<Element>().cannot_navigate() {
|
if self.upcast::<Element>().cannot_navigate() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Step 11
|
// Step 10
|
||||||
let mut action = submitter.action();
|
let mut action = submitter.action();
|
||||||
|
|
||||||
// Step 12
|
// Step 11
|
||||||
if action.is_empty() {
|
if action.is_empty() {
|
||||||
action = DOMString::from(base.as_str());
|
action = DOMString::from(base.as_str());
|
||||||
}
|
}
|
||||||
// Step 13-14
|
// Step 12-13
|
||||||
let action_components = match base.join(&action) {
|
let action_components = match base.join(&action) {
|
||||||
Ok(url) => url,
|
Ok(url) => url,
|
||||||
Err(_) => return,
|
Err(_) => return,
|
||||||
};
|
};
|
||||||
// Step 15-17
|
// Step 14-16
|
||||||
let scheme = action_components.scheme().to_owned();
|
let scheme = action_components.scheme().to_owned();
|
||||||
let enctype = submitter.enctype();
|
let enctype = submitter.enctype();
|
||||||
let method = submitter.method();
|
let method = submitter.method();
|
||||||
|
|
||||||
// Step 18-21
|
// Step 17-21
|
||||||
let target_attribute_value = submitter.target();
|
let target_attribute_value = submitter.target();
|
||||||
let source = doc.browsing_context().unwrap();
|
let source = doc.browsing_context().unwrap();
|
||||||
let (maybe_chosen, _new) = source.choose_browsing_context(target_attribute_value, false);
|
let (maybe_chosen, _new) = source.choose_browsing_context(target_attribute_value, false);
|
||||||
|
@ -1232,11 +1309,14 @@ pub enum FormMethod {
|
||||||
FormDialog,
|
FormDialog,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <https://html.spec.whatwg.org/multipage/#form-associated-element>
|
||||||
#[derive(Clone, Copy, MallocSizeOf)]
|
#[derive(Clone, Copy, MallocSizeOf)]
|
||||||
pub enum FormSubmitter<'a> {
|
pub enum FormSubmitter<'a> {
|
||||||
FormElement(&'a HTMLFormElement),
|
FormElement(&'a HTMLFormElement),
|
||||||
InputElement(&'a HTMLInputElement),
|
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> {
|
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 {
|
pub trait FormControl: DomObject {
|
||||||
|
|
|
@ -405,6 +405,12 @@ impl HTMLInputElement {
|
||||||
self.input_type.get()
|
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) {
|
pub fn disable_sanitization(&self) {
|
||||||
self.sanitization_flag.set(false);
|
self.sanitization_flag.set(false);
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,6 +32,7 @@ interface HTMLFormElement : HTMLElement {
|
||||||
getter (RadioNodeList or Element) (DOMString name);
|
getter (RadioNodeList or Element) (DOMString name);
|
||||||
|
|
||||||
void submit();
|
void submit();
|
||||||
|
[Throws] void requestSubmit(optional HTMLElement? submitter = null);
|
||||||
[CEReactions]
|
[CEReactions]
|
||||||
void reset();
|
void reset();
|
||||||
boolean checkValidity();
|
boolean checkValidity();
|
||||||
|
|
|
@ -1650,9 +1650,6 @@
|
||||||
|
|
||||||
|
|
||||||
[idlharness.https.html?include=HTML.*]
|
[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]
|
[HTMLTableSectionElement interface: document.createElement("tfoot") must inherit property "align" with the proper type]
|
||||||
expected: FAIL
|
expected: FAIL
|
||||||
|
|
||||||
|
@ -2307,9 +2304,6 @@
|
||||||
[HTMLObjectElement interface: document.createElement("object") must inherit property "archive" with the proper type]
|
[HTMLObjectElement interface: document.createElement("object") must inherit property "archive" with the proper type]
|
||||||
expected: FAIL
|
expected: FAIL
|
||||||
|
|
||||||
[HTMLFormElement interface: operation requestSubmit(optional HTMLElement?)]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[HTMLTableColElement interface: document.createElement("col") must inherit property "chOff" with the proper type]
|
[HTMLTableColElement interface: document.createElement("col") must inherit property "chOff" with the proper type]
|
||||||
expected: FAIL
|
expected: FAIL
|
||||||
|
|
||||||
|
|
|
@ -3621,9 +3621,6 @@
|
||||||
[HTMLAreaElement interface: document.createElement("area") must inherit property "download" with the proper type]
|
[HTMLAreaElement interface: document.createElement("area") must inherit property "download" with the proper type]
|
||||||
expected: FAIL
|
expected: FAIL
|
||||||
|
|
||||||
[HTMLFormElement interface: calling requestSubmit(HTMLElement) on document.createElement("form") with too few arguments must throw TypeError]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[HTMLProgressElement interface: attribute value]
|
[HTMLProgressElement interface: attribute value]
|
||||||
expected: FAIL
|
expected: FAIL
|
||||||
|
|
||||||
|
@ -3711,9 +3708,6 @@
|
||||||
[HTMLImageElement interface: document.createElement("img") must inherit property "loading" with the proper type]
|
[HTMLImageElement interface: document.createElement("img") must inherit property "loading" with the proper type]
|
||||||
expected: FAIL
|
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]
|
[HTMLSlotElement interface: calling assignedNodes(optional AssignedNodesOptions) on document.createElement("slot") with too few arguments must throw TypeError]
|
||||||
expected: FAIL
|
expected: FAIL
|
||||||
|
|
||||||
|
@ -3732,18 +3726,12 @@
|
||||||
[HTMLAllCollection interface: document.all must inherit property "item(optional DOMString)" with the proper type]
|
[HTMLAllCollection interface: document.all must inherit property "item(optional DOMString)" with the proper type]
|
||||||
expected: FAIL
|
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]
|
[HTMLAllCollection interface: calling item(optional DOMString) on document.all with too few arguments must throw TypeError]
|
||||||
expected: FAIL
|
expected: FAIL
|
||||||
|
|
||||||
[HTMLAllCollection interface: operation item(optional DOMString)]
|
[HTMLAllCollection interface: operation item(optional DOMString)]
|
||||||
expected: FAIL
|
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]
|
[HTMLCanvasElement interface: document.createElement("canvas") must inherit property "toBlob(BlobCallback, optional DOMString, optional any)" with the proper type]
|
||||||
expected: FAIL
|
expected: FAIL
|
||||||
|
|
||||||
|
|
|
@ -1,19 +1,3 @@
|
||||||
[form-submission-algorithm.html]
|
[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]
|
[Submission URL should always have a non-null query part]
|
||||||
expected: FAIL
|
expected: FAIL
|
||||||
|
|
||||||
[If firing submission events flag of form is true, then return]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
|
|
|
@ -1,22 +1,6 @@
|
||||||
[form-requestsubmit.html]
|
[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.]
|
[The value of the submitter should be appended, and form* attributes of the submitter should be handled.]
|
||||||
expected: FAIL
|
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]
|
[requestSubmit() should trigger interactive form validation]
|
||||||
expected: FAIL
|
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
|
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue