Implement basic framework for static and interactive validation on forms

This commit is contained in:
Keith Yeung 2015-11-29 17:32:09 -08:00
parent 85b43ea317
commit 3395e54585
7 changed files with 227 additions and 249 deletions

View file

@ -115,6 +115,12 @@ impl HTMLButtonElementMethods for HTMLButtonElement {
// https://html.spec.whatwg.org/multipage/#dom-fs-formtarget
make_setter!(SetFormTarget, "formtarget");
// https://html.spec.whatwg.org/multipage/#attr-fs-formnovalidate
make_bool_getter!(FormNoValidate, "formnovalidate");
// https://html.spec.whatwg.org/multipage/#attr-fs-formnovalidate
make_bool_setter!(SetFormNoValidate, "formnovalidate");
// https://html.spec.whatwg.org/multipage/#dom-fe-name
make_getter!(Name, "name");

View file

@ -9,6 +9,7 @@ use dom::bindings::codegen::Bindings::HTMLButtonElementBinding::HTMLButtonElemen
use dom::bindings::codegen::Bindings::HTMLFormElementBinding;
use dom::bindings::codegen::Bindings::HTMLFormElementBinding::HTMLFormElementMethods;
use dom::bindings::codegen::Bindings::HTMLInputElementBinding::HTMLInputElementMethods;
use dom::bindings::codegen::Bindings::HTMLTextAreaElementBinding::HTMLTextAreaElementMethods;
use dom::bindings::conversions::DerivedFrom;
use dom::bindings::global::GlobalRef;
use dom::bindings::inheritance::{Castable, ElementTypeId, HTMLElementTypeId, NodeTypeId};
@ -17,10 +18,13 @@ use dom::bindings::reflector::Reflectable;
use dom::document::Document;
use dom::element::Element;
use dom::event::{Event, EventBubbles, EventCancelable};
use dom::htmlbuttonelement::{HTMLButtonElement};
use dom::eventtarget::EventTarget;
use dom::htmlbuttonelement::HTMLButtonElement;
use dom::htmldatalistelement::HTMLDataListElement;
use dom::htmlelement::HTMLElement;
use dom::htmlinputelement::HTMLInputElement;
use dom::htmlobjectelement::HTMLObjectElement;
use dom::htmlselectelement::HTMLSelectElement;
use dom::htmltextareaelement::HTMLTextAreaElement;
use dom::node::{Node, document_from_node, window_from_node};
use dom::virtualmethods::VirtualMethods;
@ -140,7 +144,7 @@ impl HTMLFormElementMethods for HTMLFormElement {
}
}
#[derive(Copy, Clone, HeapSizeOf)]
#[derive(Copy, Clone, HeapSizeOf, PartialEq)]
pub enum SubmittedFrom {
FromFormSubmitMethod,
NotFromFormSubmitMethod
@ -154,13 +158,31 @@ pub enum ResetFrom {
impl HTMLFormElement {
pub fn submit(&self, _submit_method_flag: SubmittedFrom, submitter: FormSubmitter) {
/// [Form submission](https://html.spec.whatwg.org/multipage/#concept-form-submit)
pub fn submit(&self, submit_method_flag: SubmittedFrom, submitter: FormSubmitter) {
// Step 1
let doc = document_from_node(self);
let win = window_from_node(self);
let base = doc.url();
// TODO: Handle browsing contexts
// TODO: Handle validation
// Step 4
if submit_method_flag == SubmittedFrom::NotFromFormSubmitMethod
&& !submitter.no_validate(self)
{
if self.interactive_validation().is_err() {
// TODO: Implement event handlers on all form control elements
// XXXKiChjang: We're also calling the following two statements quite often,
// we should refactor it into a function
let event = Event::new(GlobalRef::Window(win.r()),
atom!("invalid"),
EventBubbles::DoesNotBubble,
EventCancelable::NotCancelable);
event.fire(self.upcast());
return;
}
}
// Step 5
if submit_method_flag == SubmittedFrom::NotFromFormSubmitMethod {
let event = Event::new(GlobalRef::Window(win.r()),
atom!("submit"),
EventBubbles::Bubbles,
@ -169,17 +191,21 @@ impl HTMLFormElement {
if event.DefaultPrevented() {
return;
}
}
// Step 6
let form_data = self.get_form_dataset(Some(submitter));
// Step 7-8
// Step 7
let mut action = submitter.action();
// Step 8
if action.is_empty() {
action = DOMString::from(base.serialize());
}
// TODO: Resolve the url relative to the submitter element
// Step 10-15
let action_components =
UrlParser::new().base_url(base).parse(&action).unwrap_or((*base).clone());
// Step 9-11
let action_components = match UrlParser::new().base_url(base).parse(&action) {
Ok(url) => url,
Err(_) => return
};
// Step 12-15
let _action = action_components.serialize();
let scheme = action_components.scheme.clone();
let enctype = submitter.enctype();
@ -219,13 +245,123 @@ impl HTMLFormElement {
win.pipeline(), load_data)).unwrap();
}
/// Interactively validate the constraints of form elements
/// https://html.spec.whatwg.org/multipage/#interactively-validate-the-constraints
fn interactive_validation(&self) -> Result<(), ()> {
// Step 1-3
let unhandled_invalid_controls = match self.static_validation() {
Ok(()) => return Ok(()),
Err(err) => err
};
// TODO: Report the problems with the constraints of at least one of
// the elements given in unhandled invalid controls to the user
// Step 4
Err(())
}
/// Statitically validate the constraints of form elements
/// https://html.spec.whatwg.org/multipage/#statically-validate-the-constraints
fn static_validation(&self) -> Result<(), Vec<FormSubmittableElement>> {
let node = self.upcast::<Node>();
// FIXME(#3553): This is an incorrect way of getting controls owned by the
// form, refactor this when html5ever's form owner PR lands
// Step 1-3
let invalid_controls = node.traverse_preorder().filter_map(|field| {
if let Some(el) = field.downcast::<Element>() {
None // Remove this line if you decide to refactor
// XXXKiChjang: Refactor the following commented up code so that form control elements
// each have a candidate_for_validation and satisfies_constraints methods
// XXXKiChjang: This should go into candidate_for_validation
// if field.ancestors()
// .any(|a| Root::downcast::<HTMLDataListElement>(a).is_some())
// // XXXKiChjang this may be wrong, this is not checking the ancestor
// // elements to find whether an HTMLFieldSetElement exists and is disabled
// || el.get_disabled_state() {
// return None;
// }
// XXXKiChjang: This should go into satisfies_constraints for each individual form element
// match field.type_id() {
// NodeTypeId::Element(ElementTypeId::HTMLElement(element)) => {
// match element {
// HTMLElementTypeId::HTMLButtonElement => {
// let button = field.downcast::<HTMLButtonElement>().unwrap();
// // Substep 1
// // https://html.spec.whatwg.org/multipage/#the-button-element:barred-from-constraint-validation
// if button.Type() != "submit" { return None; }
// // Substep 2
// // TODO: Check constraints on HTMLButtonElement
// // Substep 3
// Some(FormSubmittableElement::ButtonElement(Root::from_ref(&*button)))
// }
// HTMLElementTypeId::HTMLInputElement => {
// let input = field.downcast::<HTMLInputElement>().unwrap();
// // Substep 1
// // https://html.spec.whatwg.org/multipage/#candidate-for-constraint-validation
// if input.type_() == atom!("hidden")
// || input.type_() == atom!("reset")
// || input.type_() == atom!("button")
// || input.ReadOnly() { return None; }
// // Substep 2
// // TODO: Check constraints on HTMLInputElement
// // Substep 3
// Some(FormSubmittableElement::InputElement(Root::from_ref(&*input)))
// }
// HTMLElementTypeId::HTMLSelectElement => {
// let select = field.downcast::<HTMLSelectElement>().unwrap();
// // Substep 1 not necessary, HTMLSelectElements are not barred from constraint validation
// // Substep 2
// // TODO: Check constraints on HTMLSelectElement
// // Substep 3
// Some(FormSubmittableElement::SelectElement(Root::from_ref(&*select)))
// }
// HTMLElementTypeId::HTMLTextAreaElement => {
// let textarea = field.downcast::<HTMLTextAreaElement>().unwrap();
// // Substep 1
// // https://html.spec.whatwg.org/multipage/#the-textarea-element:barred-from-constraint-validation
// if textarea.ReadOnly() { return None; }
// // Substep 2
// // TODO: Check constraints on HTMLTextAreaElement
// // Substep 3
// Some(FormSubmittableElement::TextAreaElement(Root::from_ref(&*textarea)))
// }
// _ => None
// }
// }
// _ => None
// }
} else {
None
}
}).collect::<Vec<FormSubmittableElement>>();
// Step 4
if invalid_controls.is_empty() { return Ok(()); }
// Step 5-6
let win = window_from_node(self);
let unhandled_invalid_controls = invalid_controls.into_iter().filter_map(|field| {
let event = Event::new(GlobalRef::Window(win.r()),
atom!("invalid"),
EventBubbles::DoesNotBubble,
EventCancelable::Cancelable);
event.fire(field.as_event_target());
if !event.DefaultPrevented() { return Some(field); }
None
}).collect::<Vec<FormSubmittableElement>>();
// Step 7
Err(unhandled_invalid_controls)
}
/// https://html.spec.whatwg.org/multipage/#constructing-the-form-data-set
/// Steps range from 1 to 3
fn get_unclean_dataset(&self, submitter: Option<FormSubmitter>) -> Vec<FormDatum> {
let node = self.upcast::<Node>();
// TODO: This is an incorrect way of getting controls owned
// FIXME(#3553): This is an incorrect way of getting controls owned
// by the form, but good enough until html5ever lands
// Step 1-2
node.traverse_preorder().filter_map(|child| {
// Step 3.1: The field element is disabled.
match child.downcast::<Element>() {
@ -382,6 +518,29 @@ pub enum FormMethod {
FormDialog
}
#[derive(HeapSizeOf)]
pub enum FormSubmittableElement {
ButtonElement(Root<HTMLButtonElement>),
InputElement(Root<HTMLInputElement>),
// TODO: HTMLKeygenElement unimplemented
// KeygenElement(&'a HTMLKeygenElement),
ObjectElement(Root<HTMLObjectElement>),
SelectElement(Root<HTMLSelectElement>),
TextAreaElement(Root<HTMLTextAreaElement>)
}
impl FormSubmittableElement {
fn as_event_target(&self) -> &EventTarget {
match *self {
FormSubmittableElement::ButtonElement(ref button) => button.r().upcast(),
FormSubmittableElement::InputElement(ref input) => input.r().upcast(),
FormSubmittableElement::ObjectElement(ref object) => object.r().upcast(),
FormSubmittableElement::SelectElement(ref select) => select.r().upcast(),
FormSubmittableElement::TextAreaElement(ref textarea) => textarea.r().upcast()
}
}
}
#[derive(Copy, Clone, HeapSizeOf)]
pub enum FormSubmitter<'a> {
FormElement(&'a HTMLFormElement),
@ -466,6 +625,22 @@ impl<'a> FormSubmitter<'a> {
}
}
}
fn no_validate(&self, form_owner: &HTMLFormElement) -> bool {
match *self {
FormSubmitter::FormElement(form) => form.NoValidate(),
FormSubmitter::InputElement(input_element) => {
input_element.get_form_boolean_attribute(&atom!("formnovalidate"),
|i| i.FormNoValidate(),
|f| f.NoValidate())
}
FormSubmitter::ButtonElement(button_element) => {
button_element.get_form_boolean_attribute(&atom!("formnovalidate"),
|i| i.FormNoValidate(),
|f| f.NoValidate())
}
}
}
}
pub trait FormControl: DerivedFrom<Element> + Reflectable {
@ -506,9 +681,28 @@ pub trait FormControl: DerivedFrom<Element> + Reflectable {
}
}
fn get_form_boolean_attribute<InputFn, OwnerFn>(&self,
attr: &Atom,
input: InputFn,
owner: OwnerFn)
-> bool
where InputFn: Fn(&Self) -> bool,
OwnerFn: Fn(&HTMLFormElement) -> bool
{
if self.to_element().has_attribute(attr) {
input(self)
} else {
self.form_owner().map_or(false, |t| owner(t.r()))
}
}
fn to_element(&self) -> &Element {
self.upcast()
}
// XXXKiChjang: Implement these on inheritors
// fn candidate_for_validation(&self) -> bool;
// fn satisfies_constraints(&self) -> bool;
}
impl VirtualMethods for HTMLFormElement {

View file

@ -340,6 +340,12 @@ impl HTMLInputElementMethods for HTMLInputElement {
// https://html.spec.whatwg.org/multipage/#dom-input-formtarget
make_setter!(SetFormTarget, "formtarget");
// https://html.spec.whatwg.org/multipage/#attr-fs-formnovalidate
make_bool_getter!(FormNoValidate, "formnovalidate");
// https://html.spec.whatwg.org/multipage/#attr-fs-formnovalidate
make_bool_setter!(SetFormNoValidate, "formnovalidate");
// https://html.spec.whatwg.org/multipage/#dom-input-maxlength
make_int_getter!(MaxLength, "maxlength", DEFAULT_MAX_LENGTH);

View file

@ -11,7 +11,7 @@ interface HTMLButtonElement : HTMLElement {
attribute DOMString formAction;
attribute DOMString formEnctype;
attribute DOMString formMethod;
// attribute boolean formNoValidate;
attribute boolean formNoValidate;
attribute DOMString formTarget;
attribute DOMString name;
attribute DOMString type;

View file

@ -18,7 +18,7 @@ interface HTMLInputElement : HTMLElement {
attribute DOMString formAction;
attribute DOMString formEnctype;
attribute DOMString formMethod;
// attribute boolean formNoValidate;
attribute boolean formNoValidate;
attribute DOMString formTarget;
// attribute unsigned long height;
attribute boolean indeterminate;

View file

@ -4845,9 +4845,6 @@
[HTMLInputElement interface: attribute files]
expected: FAIL
[HTMLInputElement interface: attribute formNoValidate]
expected: FAIL
[HTMLInputElement interface: attribute height]
expected: FAIL
@ -4965,9 +4962,6 @@
[HTMLInputElement interface: document.createElement("input") must inherit property "files" with the proper type (9)]
expected: FAIL
[HTMLInputElement interface: document.createElement("input") must inherit property "formNoValidate" with the proper type (13)]
expected: FAIL
[HTMLInputElement interface: document.createElement("input") must inherit property "height" with the proper type (15)]
expected: FAIL
@ -5091,9 +5085,6 @@
[HTMLButtonElement interface: attribute autofocus]
expected: FAIL
[HTMLButtonElement interface: attribute formNoValidate]
expected: FAIL
[HTMLButtonElement interface: attribute menu]
expected: FAIL
@ -5115,9 +5106,6 @@
[HTMLButtonElement interface: document.createElement("button") must inherit property "autofocus" with the proper type (0)]
expected: FAIL
[HTMLButtonElement interface: document.createElement("button") must inherit property "formNoValidate" with the proper type (6)]
expected: FAIL
[HTMLButtonElement interface: document.createElement("button") must inherit property "menu" with the proper type (11)]
expected: FAIL

View file

@ -4146,114 +4146,6 @@
[input.formMethod: IDL get with DOM attribute unset]
expected: FAIL
[input.formNoValidate: typeof IDL attribute]
expected: FAIL
[input.formNoValidate: IDL get with DOM attribute unset]
expected: FAIL
[input.formNoValidate: setAttribute() to "" followed by IDL get]
expected: FAIL
[input.formNoValidate: setAttribute() to " foo " followed by IDL get]
expected: FAIL
[input.formNoValidate: setAttribute() to undefined followed by IDL get]
expected: FAIL
[input.formNoValidate: setAttribute() to null followed by IDL get]
expected: FAIL
[input.formNoValidate: setAttribute() to 7 followed by IDL get]
expected: FAIL
[input.formNoValidate: setAttribute() to 1.5 followed by IDL get]
expected: FAIL
[input.formNoValidate: setAttribute() to true followed by IDL get]
expected: FAIL
[input.formNoValidate: setAttribute() to false followed by IDL get]
expected: FAIL
[input.formNoValidate: setAttribute() to object "[object Object\]" followed by IDL get]
expected: FAIL
[input.formNoValidate: setAttribute() to NaN followed by IDL get]
expected: FAIL
[input.formNoValidate: setAttribute() to Infinity followed by IDL get]
expected: FAIL
[input.formNoValidate: setAttribute() to -Infinity followed by IDL get]
expected: FAIL
[input.formNoValidate: setAttribute() to "\\0" followed by IDL get]
expected: FAIL
[input.formNoValidate: setAttribute() to object "test-toString" followed by IDL get]
expected: FAIL
[input.formNoValidate: setAttribute() to object "test-valueOf" followed by IDL get]
expected: FAIL
[input.formNoValidate: setAttribute() to "formNoValidate" followed by IDL get]
expected: FAIL
[input.formNoValidate: IDL set to "" followed by hasAttribute()]
expected: FAIL
[input.formNoValidate: IDL set to "" followed by IDL get]
expected: FAIL
[input.formNoValidate: IDL set to " foo " followed by IDL get]
expected: FAIL
[input.formNoValidate: IDL set to undefined followed by hasAttribute()]
expected: FAIL
[input.formNoValidate: IDL set to undefined followed by IDL get]
expected: FAIL
[input.formNoValidate: IDL set to null followed by hasAttribute()]
expected: FAIL
[input.formNoValidate: IDL set to null followed by IDL get]
expected: FAIL
[input.formNoValidate: IDL set to 7 followed by IDL get]
expected: FAIL
[input.formNoValidate: IDL set to 1.5 followed by IDL get]
expected: FAIL
[input.formNoValidate: IDL set to false followed by hasAttribute()]
expected: FAIL
[input.formNoValidate: IDL set to object "[object Object\]" followed by IDL get]
expected: FAIL
[input.formNoValidate: IDL set to NaN followed by hasAttribute()]
expected: FAIL
[input.formNoValidate: IDL set to NaN followed by IDL get]
expected: FAIL
[input.formNoValidate: IDL set to Infinity followed by IDL get]
expected: FAIL
[input.formNoValidate: IDL set to -Infinity followed by IDL get]
expected: FAIL
[input.formNoValidate: IDL set to "\\0" followed by IDL get]
expected: FAIL
[input.formNoValidate: IDL set to object "test-toString" followed by IDL get]
expected: FAIL
[input.formNoValidate: IDL set to object "test-valueOf" followed by IDL get]
expected: FAIL
[input.inputMode: typeof IDL attribute]
expected: FAIL
@ -6849,114 +6741,6 @@
[button.formMethod: IDL get with DOM attribute unset]
expected: FAIL
[button.formNoValidate: typeof IDL attribute]
expected: FAIL
[button.formNoValidate: IDL get with DOM attribute unset]
expected: FAIL
[button.formNoValidate: setAttribute() to "" followed by IDL get]
expected: FAIL
[button.formNoValidate: setAttribute() to " foo " followed by IDL get]
expected: FAIL
[button.formNoValidate: setAttribute() to undefined followed by IDL get]
expected: FAIL
[button.formNoValidate: setAttribute() to null followed by IDL get]
expected: FAIL
[button.formNoValidate: setAttribute() to 7 followed by IDL get]
expected: FAIL
[button.formNoValidate: setAttribute() to 1.5 followed by IDL get]
expected: FAIL
[button.formNoValidate: setAttribute() to true followed by IDL get]
expected: FAIL
[button.formNoValidate: setAttribute() to false followed by IDL get]
expected: FAIL
[button.formNoValidate: setAttribute() to object "[object Object\]" followed by IDL get]
expected: FAIL
[button.formNoValidate: setAttribute() to NaN followed by IDL get]
expected: FAIL
[button.formNoValidate: setAttribute() to Infinity followed by IDL get]
expected: FAIL
[button.formNoValidate: setAttribute() to -Infinity followed by IDL get]
expected: FAIL
[button.formNoValidate: setAttribute() to "\\0" followed by IDL get]
expected: FAIL
[button.formNoValidate: setAttribute() to object "test-toString" followed by IDL get]
expected: FAIL
[button.formNoValidate: setAttribute() to object "test-valueOf" followed by IDL get]
expected: FAIL
[button.formNoValidate: setAttribute() to "formNoValidate" followed by IDL get]
expected: FAIL
[button.formNoValidate: IDL set to "" followed by hasAttribute()]
expected: FAIL
[button.formNoValidate: IDL set to "" followed by IDL get]
expected: FAIL
[button.formNoValidate: IDL set to " foo " followed by IDL get]
expected: FAIL
[button.formNoValidate: IDL set to undefined followed by hasAttribute()]
expected: FAIL
[button.formNoValidate: IDL set to undefined followed by IDL get]
expected: FAIL
[button.formNoValidate: IDL set to null followed by hasAttribute()]
expected: FAIL
[button.formNoValidate: IDL set to null followed by IDL get]
expected: FAIL
[button.formNoValidate: IDL set to 7 followed by IDL get]
expected: FAIL
[button.formNoValidate: IDL set to 1.5 followed by IDL get]
expected: FAIL
[button.formNoValidate: IDL set to false followed by hasAttribute()]
expected: FAIL
[button.formNoValidate: IDL set to object "[object Object\]" followed by IDL get]
expected: FAIL
[button.formNoValidate: IDL set to NaN followed by hasAttribute()]
expected: FAIL
[button.formNoValidate: IDL set to NaN followed by IDL get]
expected: FAIL
[button.formNoValidate: IDL set to Infinity followed by IDL get]
expected: FAIL
[button.formNoValidate: IDL set to -Infinity followed by IDL get]
expected: FAIL
[button.formNoValidate: IDL set to "\\0" followed by IDL get]
expected: FAIL
[button.formNoValidate: IDL set to object "test-toString" followed by IDL get]
expected: FAIL
[button.formNoValidate: IDL set to object "test-valueOf" followed by IDL get]
expected: FAIL
[button.itemScope: typeof IDL attribute]
expected: FAIL