Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement basic framework for static and interactive validation on forms #8747

Merged
merged 2 commits into from Dec 17, 2015
Merged
Changes from 1 commit
Commits
File filter...
Filter file types
Jump to…
Jump to file
Failed to load files.

Always

Just for now

Next

Implement basic framework for static and interactive validation on forms

  • Loading branch information
KiChjang committed Dec 16, 2015
commit 3395e5458586173fbd07cc65131ad8fd990fcccb
@@ -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");

@@ -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,32 +158,54 @@ 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
let event = Event::new(GlobalRef::Window(win.r()),
atom!("submit"),
EventBubbles::Bubbles,
EventCancelable::Cancelable);
event.fire(self.upcast());
if event.DefaultPrevented() {
return;
// 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,
EventCancelable::Cancelable);
event.fire(self.upcast());
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
// by the form, but good enough until html5ever lands
// Step 1-2
// FIXME(#3553): This is an incorrect way of getting controls owned
// by the form, but good enough until html5ever lands
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 {
@@ -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);

@@ -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;
@@ -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;
@@ -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

ProTip! Use n and p to navigate between commits in a pull request.
You can’t perform that action at this time.