mirror of
https://github.com/servo/servo.git
synced 2025-08-03 04:30:10 +01:00
Form constraints validation
This commit is contained in:
parent
e47e884cc7
commit
779552ee7d
72 changed files with 1224 additions and 4336 deletions
|
@ -519,6 +519,28 @@ impl DOMString {
|
|||
// Step 7, 8, 9
|
||||
Ok((date_tuple, time_tuple))
|
||||
}
|
||||
|
||||
/// https://html.spec.whatwg.org/multipage/#valid-e-mail-address
|
||||
pub fn is_valid_email_address_string(&self) -> bool {
|
||||
lazy_static! {
|
||||
static ref RE: Regex = Regex::new(concat!(
|
||||
r"^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?",
|
||||
r"(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$"
|
||||
))
|
||||
.unwrap();
|
||||
}
|
||||
RE.is_match(&self.0)
|
||||
}
|
||||
|
||||
/// https://html.spec.whatwg.org/multipage/#valid-simple-colour
|
||||
pub fn is_valid_simple_color_string(&self) -> bool {
|
||||
let mut chars = self.0.chars();
|
||||
if self.0.len() == 7 && chars.next() == Some('#') {
|
||||
chars.all(|c| c.is_digit(16))
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Borrow<str> for DOMString {
|
||||
|
|
|
@ -56,6 +56,7 @@ use crate::dom::htmllegendelement::HTMLLegendElement;
|
|||
use crate::dom::htmllinkelement::HTMLLinkElement;
|
||||
use crate::dom::htmlobjectelement::HTMLObjectElement;
|
||||
use crate::dom::htmloptgroupelement::HTMLOptGroupElement;
|
||||
use crate::dom::htmloutputelement::HTMLOutputElement;
|
||||
use crate::dom::htmlselectelement::HTMLSelectElement;
|
||||
use crate::dom::htmlstyleelement::HTMLStyleElement;
|
||||
use crate::dom::htmltablecellelement::{HTMLTableCellElement, HTMLTableCellElementLayoutHelpers};
|
||||
|
@ -3281,6 +3282,18 @@ impl Element {
|
|||
let element = self.downcast::<HTMLTextAreaElement>().unwrap();
|
||||
Some(element as &dyn Validatable)
|
||||
},
|
||||
NodeTypeId::Element(ElementTypeId::HTMLElement(
|
||||
HTMLElementTypeId::HTMLFieldSetElement,
|
||||
)) => {
|
||||
let element = self.downcast::<HTMLFieldSetElement>().unwrap();
|
||||
Some(element as &dyn Validatable)
|
||||
},
|
||||
NodeTypeId::Element(ElementTypeId::HTMLElement(
|
||||
HTMLElementTypeId::HTMLOutputElement,
|
||||
)) => {
|
||||
let element = self.downcast::<HTMLOutputElement>().unwrap();
|
||||
Some(element as &dyn Validatable)
|
||||
},
|
||||
_ => None,
|
||||
};
|
||||
element
|
||||
|
|
|
@ -19,8 +19,8 @@ use crate::dom::htmlformelement::{FormControl, FormDatum, FormDatumValue};
|
|||
use crate::dom::htmlformelement::{FormSubmitter, ResetFrom, SubmittedFrom};
|
||||
use crate::dom::node::{window_from_node, BindContext, Node, UnbindContext};
|
||||
use crate::dom::nodelist::NodeList;
|
||||
use crate::dom::validation::Validatable;
|
||||
use crate::dom::validitystate::{ValidationFlags, ValidityState};
|
||||
use crate::dom::validation::{is_barred_by_datalist_ancestor, Validatable};
|
||||
use crate::dom::validitystate::ValidityState;
|
||||
use crate::dom::virtualmethods::VirtualMethods;
|
||||
use dom_struct::dom_struct;
|
||||
use html5ever::{LocalName, Prefix};
|
||||
|
@ -41,6 +41,7 @@ pub struct HTMLButtonElement {
|
|||
button_type: Cell<ButtonType>,
|
||||
form_owner: MutNullableDom<HTMLFormElement>,
|
||||
labels_node_list: MutNullableDom<NodeList>,
|
||||
validity_state: MutNullableDom<ValidityState>,
|
||||
}
|
||||
|
||||
impl HTMLButtonElement {
|
||||
|
@ -59,6 +60,7 @@ impl HTMLButtonElement {
|
|||
button_type: Cell::new(ButtonType::Submit),
|
||||
form_owner: Default::default(),
|
||||
labels_node_list: Default::default(),
|
||||
validity_state: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -78,12 +80,6 @@ impl HTMLButtonElement {
|
|||
}
|
||||
|
||||
impl HTMLButtonElementMethods for HTMLButtonElement {
|
||||
// https://html.spec.whatwg.org/multipage/#dom-cva-validity
|
||||
fn Validity(&self) -> DomRoot<ValidityState> {
|
||||
let window = window_from_node(self);
|
||||
ValidityState::new(&window, self.upcast())
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/#dom-fe-disabled
|
||||
make_bool_getter!(Disabled, "disabled");
|
||||
|
||||
|
@ -150,6 +146,36 @@ impl HTMLButtonElementMethods for HTMLButtonElement {
|
|||
|
||||
// https://html.spec.whatwg.org/multipage/#dom-lfe-labels
|
||||
make_labels_getter!(Labels, labels_node_list);
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/#dom-cva-willvalidate
|
||||
fn WillValidate(&self) -> bool {
|
||||
self.is_instance_validatable()
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/#dom-cva-validity
|
||||
fn Validity(&self) -> DomRoot<ValidityState> {
|
||||
self.validity_state()
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/#dom-cva-checkvalidity
|
||||
fn CheckValidity(&self) -> bool {
|
||||
self.check_validity()
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/#dom-cva-reportvalidity
|
||||
fn ReportValidity(&self) -> bool {
|
||||
self.report_validity()
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/#dom-cva-validationmessage
|
||||
fn ValidationMessage(&self) -> DOMString {
|
||||
self.validation_message()
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/#dom-cva-setcustomvalidity
|
||||
fn SetCustomValidity(&self, error: DOMString) {
|
||||
self.validity_state().set_custom_error_message(error);
|
||||
}
|
||||
}
|
||||
|
||||
impl HTMLButtonElement {
|
||||
|
@ -268,13 +294,22 @@ impl FormControl for HTMLButtonElement {
|
|||
}
|
||||
|
||||
impl Validatable for HTMLButtonElement {
|
||||
fn is_instance_validatable(&self) -> bool {
|
||||
true
|
||||
fn as_element(&self) -> &Element {
|
||||
self.upcast()
|
||||
}
|
||||
fn validate(&self, validate_flags: ValidationFlags) -> bool {
|
||||
if validate_flags.is_empty() {}
|
||||
// Need more flag check for different validation types later
|
||||
true
|
||||
|
||||
fn validity_state(&self) -> DomRoot<ValidityState> {
|
||||
self.validity_state
|
||||
.or_init(|| ValidityState::new(&window_from_node(self), self.upcast()))
|
||||
}
|
||||
|
||||
fn is_instance_validatable(&self) -> bool {
|
||||
// https://html.spec.whatwg.org/multipage/#the-button-element%3Abarred-from-constraint-validation
|
||||
// https://html.spec.whatwg.org/multipage/#enabling-and-disabling-form-controls%3A-the-disabled-attribute%3Abarred-from-constraint-validation
|
||||
// https://html.spec.whatwg.org/multipage/#the-datalist-element%3Abarred-from-constraint-validation
|
||||
self.button_type.get() == ButtonType::Submit &&
|
||||
!self.upcast::<Element>().disabled_state() &&
|
||||
!is_barred_by_datalist_ancestor(self.upcast())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -14,6 +14,7 @@ use crate::dom::htmlelement::HTMLElement;
|
|||
use crate::dom::htmlformelement::{FormControl, HTMLFormElement};
|
||||
use crate::dom::htmllegendelement::HTMLLegendElement;
|
||||
use crate::dom::node::{window_from_node, Node, ShadowIncluding};
|
||||
use crate::dom::validation::Validatable;
|
||||
use crate::dom::validitystate::ValidityState;
|
||||
use crate::dom::virtualmethods::VirtualMethods;
|
||||
use dom_struct::dom_struct;
|
||||
|
@ -25,6 +26,7 @@ use style::element_state::ElementState;
|
|||
pub struct HTMLFieldSetElement {
|
||||
htmlelement: HTMLElement,
|
||||
form_owner: MutNullableDom<HTMLFormElement>,
|
||||
validity_state: MutNullableDom<ValidityState>,
|
||||
}
|
||||
|
||||
impl HTMLFieldSetElement {
|
||||
|
@ -41,6 +43,7 @@ impl HTMLFieldSetElement {
|
|||
document,
|
||||
),
|
||||
form_owner: Default::default(),
|
||||
validity_state: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -75,12 +78,6 @@ impl HTMLFieldSetElementMethods for HTMLFieldSetElement {
|
|||
HTMLCollection::create(&window, self.upcast(), filter)
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/#dom-cva-validity
|
||||
fn Validity(&self) -> DomRoot<ValidityState> {
|
||||
let window = window_from_node(self);
|
||||
ValidityState::new(&window, self.upcast())
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/#dom-fieldset-disabled
|
||||
make_bool_getter!(Disabled, "disabled");
|
||||
|
||||
|
@ -97,6 +94,36 @@ impl HTMLFieldSetElementMethods for HTMLFieldSetElement {
|
|||
fn GetForm(&self) -> Option<DomRoot<HTMLFormElement>> {
|
||||
self.form_owner()
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/#dom-cva-willvalidate
|
||||
fn WillValidate(&self) -> bool {
|
||||
self.is_instance_validatable()
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/#dom-cva-validity
|
||||
fn Validity(&self) -> DomRoot<ValidityState> {
|
||||
self.validity_state()
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/#dom-cva-checkvalidity
|
||||
fn CheckValidity(&self) -> bool {
|
||||
self.check_validity()
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/#dom-cva-reportvalidity
|
||||
fn ReportValidity(&self) -> bool {
|
||||
self.report_validity()
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/#dom-cva-validationmessage
|
||||
fn ValidationMessage(&self) -> DOMString {
|
||||
self.validation_message()
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/#dom-cva-setcustomvalidity
|
||||
fn SetCustomValidity(&self, error: DOMString) {
|
||||
self.validity_state().set_custom_error_message(error);
|
||||
}
|
||||
}
|
||||
|
||||
impl VirtualMethods for HTMLFieldSetElement {
|
||||
|
@ -185,3 +212,19 @@ impl FormControl for HTMLFieldSetElement {
|
|||
self.upcast::<Element>()
|
||||
}
|
||||
}
|
||||
|
||||
impl Validatable for HTMLFieldSetElement {
|
||||
fn as_element(&self) -> &Element {
|
||||
self.upcast()
|
||||
}
|
||||
|
||||
fn validity_state(&self) -> DomRoot<ValidityState> {
|
||||
self.validity_state
|
||||
.or_init(|| ValidityState::new(&window_from_node(self), self.upcast()))
|
||||
}
|
||||
|
||||
fn is_instance_validatable(&self) -> bool {
|
||||
// fieldset is not a submittable element (https://html.spec.whatwg.org/multipage/#category-submit)
|
||||
false
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ use crate::dom::bindings::codegen::Bindings::BlobBinding::BlobMethods;
|
|||
use crate::dom::bindings::codegen::Bindings::DocumentBinding::DocumentMethods;
|
||||
use crate::dom::bindings::codegen::Bindings::EventBinding::EventMethods;
|
||||
use crate::dom::bindings::codegen::Bindings::HTMLButtonElementBinding::HTMLButtonElementMethods;
|
||||
use crate::dom::bindings::codegen::Bindings::HTMLElementBinding::HTMLElementMethods;
|
||||
use crate::dom::bindings::codegen::Bindings::HTMLFormControlsCollectionBinding::HTMLFormControlsCollectionMethods;
|
||||
use crate::dom::bindings::codegen::Bindings::HTMLFormElementBinding::HTMLFormElementMethods;
|
||||
use crate::dom::bindings::codegen::Bindings::HTMLInputElementBinding::HTMLInputElementMethods;
|
||||
|
@ -43,7 +44,7 @@ use crate::dom::htmloutputelement::HTMLOutputElement;
|
|||
use crate::dom::htmlselectelement::HTMLSelectElement;
|
||||
use crate::dom::htmltextareaelement::HTMLTextAreaElement;
|
||||
use crate::dom::node::{document_from_node, window_from_node};
|
||||
use crate::dom::node::{Node, NodeFlags, ShadowIncluding};
|
||||
use crate::dom::node::{Node, NodeFlags};
|
||||
use crate::dom::node::{UnbindContext, VecPreOrderInsertionHelper};
|
||||
use crate::dom::nodelist::{NodeList, RadioListMode};
|
||||
use crate::dom::radionodelist::RadioNodeList;
|
||||
|
@ -509,6 +510,16 @@ impl HTMLFormElementMethods for HTMLFormElement {
|
|||
|
||||
return names_vec;
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/#dom-form-checkvalidity
|
||||
fn CheckValidity(&self) -> bool {
|
||||
self.static_validation().is_ok()
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/#dom-form-reportvalidity
|
||||
fn ReportValidity(&self) -> bool {
|
||||
self.interactive_validation().is_ok()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, MallocSizeOf, PartialEq)]
|
||||
|
@ -856,48 +867,56 @@ impl HTMLFormElement {
|
|||
/// 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() {
|
||||
// Step 1-2
|
||||
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 3
|
||||
let mut first = true;
|
||||
|
||||
for elem in unhandled_invalid_controls {
|
||||
if let Some(validatable) = elem.as_maybe_validatable() {
|
||||
println!("Validation error: {}", validatable.validation_message());
|
||||
}
|
||||
if first {
|
||||
if let Some(html_elem) = elem.downcast::<HTMLElement>() {
|
||||
html_elem.Focus();
|
||||
first = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
fn static_validation(&self) -> Result<(), Vec<DomRoot<Element>>> {
|
||||
let controls = self.controls.borrow();
|
||||
// Step 1-3
|
||||
let invalid_controls = node
|
||||
.traverse_preorder(ShadowIncluding::No)
|
||||
let invalid_controls = controls
|
||||
.iter()
|
||||
.filter_map(|field| {
|
||||
if let Some(el) = field.downcast::<Element>() {
|
||||
if el.disabled_state() {
|
||||
let validatable = match el.as_maybe_validatable() {
|
||||
Some(v) => v,
|
||||
None => return None,
|
||||
};
|
||||
if !validatable.is_instance_validatable() {
|
||||
None
|
||||
} else if validatable.validate(ValidationFlags::all()).is_empty() {
|
||||
None
|
||||
} else {
|
||||
let validatable = match el.as_maybe_validatable() {
|
||||
Some(v) => v,
|
||||
None => return None,
|
||||
};
|
||||
if !validatable.is_instance_validatable() {
|
||||
None
|
||||
} else if validatable.validate(ValidationFlags::empty()) {
|
||||
None
|
||||
} else {
|
||||
Some(FormSubmittableElement::from_element(&el))
|
||||
}
|
||||
Some(DomRoot::from_ref(el))
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect::<Vec<FormSubmittableElement>>();
|
||||
.collect::<Vec<DomRoot<Element>>>();
|
||||
// Step 4
|
||||
if invalid_controls.is_empty() {
|
||||
return Ok(());
|
||||
|
@ -907,14 +926,14 @@ impl HTMLFormElement {
|
|||
.into_iter()
|
||||
.filter_map(|field| {
|
||||
let event = field
|
||||
.as_event_target()
|
||||
.upcast::<EventTarget>()
|
||||
.fire_cancelable_event(atom!("invalid"));
|
||||
if !event.DefaultPrevented() {
|
||||
return Some(field);
|
||||
}
|
||||
None
|
||||
})
|
||||
.collect::<Vec<FormSubmittableElement>>();
|
||||
.collect::<Vec<DomRoot<Element>>>();
|
||||
// Step 7
|
||||
Err(unhandled_invalid_controls)
|
||||
}
|
||||
|
@ -1204,46 +1223,6 @@ pub enum FormMethod {
|
|||
FormDialog,
|
||||
}
|
||||
|
||||
#[derive(MallocSizeOf)]
|
||||
#[allow(dead_code)]
|
||||
pub enum FormSubmittableElement {
|
||||
ButtonElement(DomRoot<HTMLButtonElement>),
|
||||
InputElement(DomRoot<HTMLInputElement>),
|
||||
// TODO: HTMLKeygenElement unimplemented
|
||||
// KeygenElement(&'a HTMLKeygenElement),
|
||||
ObjectElement(DomRoot<HTMLObjectElement>),
|
||||
SelectElement(DomRoot<HTMLSelectElement>),
|
||||
TextAreaElement(DomRoot<HTMLTextAreaElement>),
|
||||
}
|
||||
|
||||
impl FormSubmittableElement {
|
||||
fn as_event_target(&self) -> &EventTarget {
|
||||
match *self {
|
||||
FormSubmittableElement::ButtonElement(ref button) => button.upcast(),
|
||||
FormSubmittableElement::InputElement(ref input) => input.upcast(),
|
||||
FormSubmittableElement::ObjectElement(ref object) => object.upcast(),
|
||||
FormSubmittableElement::SelectElement(ref select) => select.upcast(),
|
||||
FormSubmittableElement::TextAreaElement(ref textarea) => textarea.upcast(),
|
||||
}
|
||||
}
|
||||
|
||||
fn from_element(element: &Element) -> FormSubmittableElement {
|
||||
if let Some(input) = element.downcast::<HTMLInputElement>() {
|
||||
FormSubmittableElement::InputElement(DomRoot::from_ref(&input))
|
||||
} else if let Some(input) = element.downcast::<HTMLButtonElement>() {
|
||||
FormSubmittableElement::ButtonElement(DomRoot::from_ref(&input))
|
||||
} else if let Some(input) = element.downcast::<HTMLObjectElement>() {
|
||||
FormSubmittableElement::ObjectElement(DomRoot::from_ref(&input))
|
||||
} else if let Some(input) = element.downcast::<HTMLSelectElement>() {
|
||||
FormSubmittableElement::SelectElement(DomRoot::from_ref(&input))
|
||||
} else if let Some(input) = element.downcast::<HTMLTextAreaElement>() {
|
||||
FormSubmittableElement::TextAreaElement(DomRoot::from_ref(&input))
|
||||
} else {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, MallocSizeOf)]
|
||||
pub enum FormSubmitter<'a> {
|
||||
FormElement(&'a HTMLFormElement),
|
||||
|
|
|
@ -39,9 +39,10 @@ use crate::dom::node::{
|
|||
};
|
||||
use crate::dom::nodelist::NodeList;
|
||||
use crate::dom::textcontrol::{TextControlElement, TextControlSelection};
|
||||
use crate::dom::validation::Validatable;
|
||||
use crate::dom::validitystate::ValidationFlags;
|
||||
use crate::dom::validation::{is_barred_by_datalist_ancestor, Validatable};
|
||||
use crate::dom::validitystate::{ValidationFlags, ValidityState};
|
||||
use crate::dom::virtualmethods::VirtualMethods;
|
||||
use crate::realms::enter_realm;
|
||||
use crate::script_runtime::JSContext as SafeJSContext;
|
||||
use crate::textinput::KeyReaction::{
|
||||
DispatchInput, Nothing, RedrawSelection, TriggerDefaultAction,
|
||||
|
@ -55,8 +56,12 @@ use embedder_traits::FilterPattern;
|
|||
use encoding_rs::Encoding;
|
||||
use html5ever::{LocalName, Prefix};
|
||||
use js::jsapi::{
|
||||
ClippedTime, DateGetMsecSinceEpoch, Handle, JSObject, NewDateObject, ObjectIsDate,
|
||||
ClippedTime, DateGetMsecSinceEpoch, Handle, JSObject, JS_ClearPendingException, NewDateObject,
|
||||
NewUCRegExpObject, ObjectIsDate, RegExpFlag_Unicode, RegExpFlags,
|
||||
};
|
||||
use js::jsval::UndefinedValue;
|
||||
use js::rust::jsapi_wrapped::{ExecuteRegExpNoStatics, ObjectIsRegExp};
|
||||
use js::rust::{HandleObject, MutableHandleObject};
|
||||
use msg::constellation_msg::InputMethodType;
|
||||
use net_traits::blob_url_store::get_blob_origin;
|
||||
use net_traits::filemanager_thread::FileManagerThreadMsg;
|
||||
|
@ -67,12 +72,15 @@ use script_traits::ScriptToConstellationChan;
|
|||
use servo_atoms::Atom;
|
||||
use std::borrow::Cow;
|
||||
use std::cell::Cell;
|
||||
use std::f64;
|
||||
use std::ops::Range;
|
||||
use std::ptr;
|
||||
use std::ptr::NonNull;
|
||||
use style::attr::AttrValue;
|
||||
use style::element_state::ElementState;
|
||||
use style::str::{split_commas, str_join};
|
||||
use unicode_bidi::{bidi_class, BidiClass};
|
||||
use url::Url;
|
||||
|
||||
const DEFAULT_SUBMIT_VALUE: &'static str = "Submit";
|
||||
const DEFAULT_RESET_VALUE: &'static str = "Reset";
|
||||
|
@ -135,6 +143,11 @@ impl InputType {
|
|||
self.is_textual() || *self == InputType::Password
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/#has-a-periodic-domain
|
||||
fn has_periodic_domain(&self) -> bool {
|
||||
*self == InputType::Time
|
||||
}
|
||||
|
||||
fn to_str(&self) -> &str {
|
||||
match *self {
|
||||
InputType::Button => "button",
|
||||
|
@ -253,6 +266,7 @@ pub struct HTMLInputElement {
|
|||
filelist: MutNullableDom<FileList>,
|
||||
form_owner: MutNullableDom<HTMLFormElement>,
|
||||
labels_node_list: MutNullableDom<NodeList>,
|
||||
validity_state: MutNullableDom<ValidityState>,
|
||||
}
|
||||
|
||||
#[derive(JSTraceable)]
|
||||
|
@ -307,6 +321,7 @@ impl HTMLInputElement {
|
|||
filelist: MutNullableDom::new(None),
|
||||
form_owner: Default::default(),
|
||||
labels_node_list: MutNullableDom::new(None),
|
||||
validity_state: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -402,6 +417,52 @@ impl HTMLInputElement {
|
|||
textinput.set_content(value);
|
||||
}
|
||||
|
||||
fn does_readonly_apply(&self) -> bool {
|
||||
match self.input_type() {
|
||||
InputType::Text |
|
||||
InputType::Search |
|
||||
InputType::Url |
|
||||
InputType::Tel |
|
||||
InputType::Email |
|
||||
InputType::Password |
|
||||
InputType::Date |
|
||||
InputType::Month |
|
||||
InputType::Week |
|
||||
InputType::Time |
|
||||
InputType::DatetimeLocal |
|
||||
InputType::Number => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn does_minmaxlength_apply(&self) -> bool {
|
||||
match self.input_type() {
|
||||
InputType::Text |
|
||||
InputType::Search |
|
||||
InputType::Url |
|
||||
InputType::Tel |
|
||||
InputType::Email |
|
||||
InputType::Password => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn does_pattern_apply(&self) -> bool {
|
||||
match self.input_type() {
|
||||
InputType::Text |
|
||||
InputType::Search |
|
||||
InputType::Url |
|
||||
InputType::Tel |
|
||||
InputType::Email |
|
||||
InputType::Password => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn does_multiple_apply(&self) -> bool {
|
||||
self.input_type() == InputType::Email
|
||||
}
|
||||
|
||||
// valueAsNumber, step, min, and max all share the same set of
|
||||
// input types they apply to
|
||||
fn does_value_as_number_apply(&self) -> bool {
|
||||
|
@ -701,6 +762,212 @@ impl HTMLInputElement {
|
|||
})
|
||||
.map(|el| DomRoot::from_ref(&*el))
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/#suffering-from-being-missing
|
||||
fn suffers_from_being_missing(&self, value: &DOMString) -> bool {
|
||||
match self.input_type() {
|
||||
// https://html.spec.whatwg.org/multipage/#checkbox-state-(type%3Dcheckbox)%3Asuffering-from-being-missing
|
||||
InputType::Checkbox => self.Required() && !self.Checked(),
|
||||
// https://html.spec.whatwg.org/multipage/#radio-button-state-(type%3Dradio)%3Asuffering-from-being-missing
|
||||
InputType::Radio => {
|
||||
let mut is_required = self.Required();
|
||||
let mut is_checked = self.Checked();
|
||||
for other in radio_group_iter(self, self.radio_group_name().as_ref()) {
|
||||
is_required = is_required || other.Required();
|
||||
is_checked = is_checked || other.Checked();
|
||||
}
|
||||
is_required && !is_checked
|
||||
},
|
||||
// https://html.spec.whatwg.org/multipage/#file-upload-state-(type%3Dfile)%3Asuffering-from-being-missing
|
||||
InputType::File => {
|
||||
self.Required() &&
|
||||
self.filelist
|
||||
.get()
|
||||
.map_or(true, |files| files.Length() == 0)
|
||||
},
|
||||
// https://html.spec.whatwg.org/multipage/#the-required-attribute%3Asuffering-from-being-missing
|
||||
_ => {
|
||||
self.Required() &&
|
||||
self.value_mode() == ValueMode::Value &&
|
||||
self.is_mutable() &&
|
||||
value.is_empty()
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/#suffering-from-a-type-mismatch
|
||||
fn suffers_from_type_mismatch(&self, value: &DOMString) -> bool {
|
||||
if value.is_empty() {
|
||||
return false;
|
||||
}
|
||||
|
||||
match self.input_type() {
|
||||
// https://html.spec.whatwg.org/multipage/#url-state-(type%3Durl)%3Asuffering-from-a-type-mismatch
|
||||
InputType::Url => Url::parse(&value).is_err(),
|
||||
// https://html.spec.whatwg.org/multipage/#e-mail-state-(type%3Demail)%3Asuffering-from-a-type-mismatch
|
||||
// https://html.spec.whatwg.org/multipage/#e-mail-state-(type%3Demail)%3Asuffering-from-a-type-mismatch-2
|
||||
InputType::Email => {
|
||||
if self.Multiple() {
|
||||
!split_commas(&value).all(|s| {
|
||||
DOMString::from_string(s.to_string()).is_valid_email_address_string()
|
||||
})
|
||||
} else {
|
||||
!value.is_valid_email_address_string()
|
||||
}
|
||||
},
|
||||
// Other input types don't suffer from type mismatch
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/#suffering-from-a-pattern-mismatch
|
||||
fn suffers_from_pattern_mismatch(&self, value: &DOMString) -> bool {
|
||||
// https://html.spec.whatwg.org/multipage/#the-pattern-attribute%3Asuffering-from-a-pattern-mismatch
|
||||
// https://html.spec.whatwg.org/multipage/#the-pattern-attribute%3Asuffering-from-a-pattern-mismatch-2
|
||||
let pattern_str = self.Pattern();
|
||||
if value.is_empty() || pattern_str.is_empty() || !self.does_pattern_apply() {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Rust's regex is not compatible, we need to use mozjs RegExp.
|
||||
let cx = self.global().get_cx();
|
||||
let _ac = enter_realm(self);
|
||||
rooted!(in(*cx) let mut pattern = ptr::null_mut::<JSObject>());
|
||||
|
||||
if compile_pattern(cx, &pattern_str, pattern.handle_mut()) {
|
||||
if self.Multiple() && self.does_multiple_apply() {
|
||||
!split_commas(&value)
|
||||
.all(|s| matches_js_regex(cx, pattern.handle(), s).unwrap_or(true))
|
||||
} else {
|
||||
!matches_js_regex(cx, pattern.handle(), &value).unwrap_or(true)
|
||||
}
|
||||
} else {
|
||||
// Element doesn't suffer from pattern mismatch if pattern is invalid.
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/#suffering-from-bad-input
|
||||
fn suffers_from_bad_input(&self, value: &DOMString) -> bool {
|
||||
if value.is_empty() {
|
||||
return false;
|
||||
}
|
||||
|
||||
match self.input_type() {
|
||||
// https://html.spec.whatwg.org/multipage/#e-mail-state-(type%3Demail)%3Asuffering-from-bad-input
|
||||
// https://html.spec.whatwg.org/multipage/#e-mail-state-(type%3Demail)%3Asuffering-from-bad-input-2
|
||||
InputType::Email => {
|
||||
// TODO: Check for input that cannot be converted to punycode.
|
||||
// Currently we don't support conversion of email values to punycode
|
||||
// so always return false.
|
||||
false
|
||||
},
|
||||
// https://html.spec.whatwg.org/multipage/#date-state-(type%3Ddate)%3Asuffering-from-bad-input
|
||||
InputType::Date => !value.is_valid_date_string(),
|
||||
// https://html.spec.whatwg.org/multipage/#month-state-(type%3Dmonth)%3Asuffering-from-bad-input
|
||||
InputType::Month => !value.is_valid_month_string(),
|
||||
// https://html.spec.whatwg.org/multipage/#week-state-(type%3Dweek)%3Asuffering-from-bad-input
|
||||
InputType::Week => !value.is_valid_week_string(),
|
||||
// https://html.spec.whatwg.org/multipage/#time-state-(type%3Dtime)%3Asuffering-from-bad-input
|
||||
InputType::Time => !value.is_valid_time_string(),
|
||||
// https://html.spec.whatwg.org/multipage/#local-date-and-time-state-(type%3Ddatetime-local)%3Asuffering-from-bad-input
|
||||
InputType::DatetimeLocal => value.parse_local_date_and_time_string().is_err(),
|
||||
// https://html.spec.whatwg.org/multipage/#number-state-(type%3Dnumber)%3Asuffering-from-bad-input
|
||||
// https://html.spec.whatwg.org/multipage/#range-state-(type%3Drange)%3Asuffering-from-bad-input
|
||||
InputType::Number | InputType::Range => !value.is_valid_floating_point_number_string(),
|
||||
// https://html.spec.whatwg.org/multipage/#color-state-(type%3Dcolor)%3Asuffering-from-bad-input
|
||||
InputType::Color => !value.is_valid_simple_color_string(),
|
||||
// Other input types don't suffer from bad input
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/#suffering-from-being-too-long
|
||||
// https://html.spec.whatwg.org/multipage/#suffering-from-being-too-short
|
||||
fn suffers_from_length_issues(&self, value: &DOMString) -> ValidationFlags {
|
||||
// https://html.spec.whatwg.org/multipage/#limiting-user-input-length%3A-the-maxlength-attribute%3Asuffering-from-being-too-long
|
||||
// https://html.spec.whatwg.org/multipage/#setting-minimum-input-length-requirements%3A-the-minlength-attribute%3Asuffering-from-being-too-short
|
||||
let value_dirty = self.value_dirty.get();
|
||||
let textinput = self.textinput.borrow();
|
||||
let edit_by_user = !textinput.was_last_change_by_set_content();
|
||||
|
||||
if value.is_empty() || !value_dirty || !edit_by_user || !self.does_minmaxlength_apply() {
|
||||
return ValidationFlags::empty();
|
||||
}
|
||||
|
||||
let mut failed_flags = ValidationFlags::empty();
|
||||
let UTF16CodeUnits(value_len) = textinput.utf16_len();
|
||||
let min_length = self.MinLength();
|
||||
let max_length = self.MaxLength();
|
||||
|
||||
if min_length != DEFAULT_MIN_LENGTH && value_len < (min_length as usize) {
|
||||
failed_flags.insert(ValidationFlags::TOO_SHORT);
|
||||
}
|
||||
|
||||
if max_length != DEFAULT_MAX_LENGTH && value_len > (max_length as usize) {
|
||||
failed_flags.insert(ValidationFlags::TOO_LONG);
|
||||
}
|
||||
|
||||
failed_flags
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/#suffering-from-an-underflow
|
||||
// https://html.spec.whatwg.org/multipage/#suffering-from-an-overflow
|
||||
// https://html.spec.whatwg.org/multipage/#suffering-from-a-step-mismatch
|
||||
fn suffers_from_range_issues(&self, value: &DOMString) -> ValidationFlags {
|
||||
if value.is_empty() || !self.does_value_as_number_apply() {
|
||||
return ValidationFlags::empty();
|
||||
}
|
||||
|
||||
let value_as_number = match self.convert_string_to_number(&value) {
|
||||
Ok(num) => num,
|
||||
Err(()) => return ValidationFlags::empty(),
|
||||
};
|
||||
|
||||
let mut failed_flags = ValidationFlags::empty();
|
||||
let min_value = self.minimum();
|
||||
let max_value = self.maximum();
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/#has-a-reversed-range
|
||||
let has_reversed_range = match (min_value, max_value) {
|
||||
(Some(min), Some(max)) => self.input_type().has_periodic_domain() && min > max,
|
||||
_ => false,
|
||||
};
|
||||
|
||||
if has_reversed_range {
|
||||
// https://html.spec.whatwg.org/multipage/#the-min-and-max-attributes:has-a-reversed-range-3
|
||||
if value_as_number > max_value.unwrap() && value_as_number < min_value.unwrap() {
|
||||
failed_flags.insert(ValidationFlags::RANGE_UNDERFLOW);
|
||||
failed_flags.insert(ValidationFlags::RANGE_OVERFLOW);
|
||||
}
|
||||
} else {
|
||||
// https://html.spec.whatwg.org/multipage/#the-min-and-max-attributes%3Asuffering-from-an-underflow-2
|
||||
if let Some(min_value) = min_value {
|
||||
if value_as_number < min_value {
|
||||
failed_flags.insert(ValidationFlags::RANGE_UNDERFLOW);
|
||||
}
|
||||
}
|
||||
// https://html.spec.whatwg.org/multipage/#the-min-and-max-attributes%3Asuffering-from-an-overflow-2
|
||||
if let Some(max_value) = max_value {
|
||||
if value_as_number > max_value {
|
||||
failed_flags.insert(ValidationFlags::RANGE_OVERFLOW);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/#the-step-attribute%3Asuffering-from-a-step-mismatch
|
||||
if let Some(step) = self.allowed_value_step() {
|
||||
// TODO: Spec has some issues here, see https://github.com/whatwg/html/issues/5207.
|
||||
// Chrome and Firefox parse values as decimals to get exact results,
|
||||
// we probably should too.
|
||||
let diff = (self.step_base() - value_as_number) % step / value_as_number;
|
||||
if diff.abs() > 1e-12 {
|
||||
failed_flags.insert(ValidationFlags::STEP_MISMATCH);
|
||||
}
|
||||
}
|
||||
|
||||
failed_flags
|
||||
}
|
||||
}
|
||||
|
||||
pub trait LayoutHTMLInputElementHelpers<'dom> {
|
||||
|
@ -1325,6 +1592,36 @@ impl HTMLInputElementMethods for HTMLInputElement {
|
|||
fn StepDown(&self, n: i32) -> ErrorResult {
|
||||
self.step_up_or_down(n, StepDirection::Down)
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/#dom-cva-willvalidate
|
||||
fn WillValidate(&self) -> bool {
|
||||
self.is_instance_validatable()
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/#dom-cva-validity
|
||||
fn Validity(&self) -> DomRoot<ValidityState> {
|
||||
self.validity_state()
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/#dom-cva-checkvalidity
|
||||
fn CheckValidity(&self) -> bool {
|
||||
self.check_validity()
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/#dom-cva-reportvalidity
|
||||
fn ReportValidity(&self) -> bool {
|
||||
self.report_validity()
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/#dom-cva-validationmessage
|
||||
fn ValidationMessage(&self) -> DOMString {
|
||||
self.validation_message()
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/#dom-cva-setcustomvalidity
|
||||
fn SetCustomValidity(&self, error: DOMString) {
|
||||
self.validity_state().set_custom_error_message(error);
|
||||
}
|
||||
}
|
||||
|
||||
fn radio_group_iter<'a>(
|
||||
|
@ -1648,16 +1945,7 @@ impl HTMLInputElement {
|
|||
}
|
||||
},
|
||||
InputType::Color => {
|
||||
let is_valid = {
|
||||
let mut chars = value.chars();
|
||||
if value.len() == 7 && chars.next() == Some('#') {
|
||||
chars.all(|c| c.is_digit(16))
|
||||
} else {
|
||||
false
|
||||
}
|
||||
};
|
||||
|
||||
if is_valid {
|
||||
if value.is_valid_simple_color_string() {
|
||||
value.make_ascii_lowercase();
|
||||
} else {
|
||||
*value = "#000000".into();
|
||||
|
@ -2352,13 +2640,73 @@ impl FormControl for HTMLInputElement {
|
|||
}
|
||||
|
||||
impl Validatable for HTMLInputElement {
|
||||
fn is_instance_validatable(&self) -> bool {
|
||||
// https://html.spec.whatwg.org/multipage/#candidate-for-constraint-validation
|
||||
true
|
||||
fn as_element(&self) -> &Element {
|
||||
self.upcast()
|
||||
}
|
||||
fn validate(&self, _validate_flags: ValidationFlags) -> bool {
|
||||
// call stub methods defined in validityState.rs file here according to the flags set in validate_flags
|
||||
true
|
||||
|
||||
fn validity_state(&self) -> DomRoot<ValidityState> {
|
||||
self.validity_state
|
||||
.or_init(|| ValidityState::new(&window_from_node(self), self.upcast()))
|
||||
}
|
||||
|
||||
fn is_instance_validatable(&self) -> bool {
|
||||
// https://html.spec.whatwg.org/multipage/#hidden-state-(type%3Dhidden)%3Abarred-from-constraint-validation
|
||||
// https://html.spec.whatwg.org/multipage/#button-state-(type%3Dbutton)%3Abarred-from-constraint-validation
|
||||
// https://html.spec.whatwg.org/multipage/#reset-button-state-(type%3Dreset)%3Abarred-from-constraint-validation
|
||||
// https://html.spec.whatwg.org/multipage/#enabling-and-disabling-form-controls%3A-the-disabled-attribute%3Abarred-from-constraint-validation
|
||||
// https://html.spec.whatwg.org/multipage/#the-readonly-attribute%3Abarred-from-constraint-validation
|
||||
// https://html.spec.whatwg.org/multipage/#the-datalist-element%3Abarred-from-constraint-validation
|
||||
match self.input_type() {
|
||||
InputType::Hidden | InputType::Button | InputType::Reset => false,
|
||||
_ => {
|
||||
!(self.upcast::<Element>().disabled_state() ||
|
||||
(self.ReadOnly() && self.does_readonly_apply()) ||
|
||||
is_barred_by_datalist_ancestor(self.upcast()))
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn perform_validation(&self, validate_flags: ValidationFlags) -> ValidationFlags {
|
||||
let mut failed_flags = ValidationFlags::empty();
|
||||
let value = self.Value();
|
||||
|
||||
if validate_flags.contains(ValidationFlags::VALUE_MISSING) {
|
||||
if self.suffers_from_being_missing(&value) {
|
||||
failed_flags.insert(ValidationFlags::VALUE_MISSING);
|
||||
}
|
||||
}
|
||||
|
||||
if validate_flags.contains(ValidationFlags::TYPE_MISMATCH) {
|
||||
if self.suffers_from_type_mismatch(&value) {
|
||||
failed_flags.insert(ValidationFlags::TYPE_MISMATCH);
|
||||
}
|
||||
}
|
||||
|
||||
if validate_flags.contains(ValidationFlags::PATTERN_MISMATCH) {
|
||||
if self.suffers_from_pattern_mismatch(&value) {
|
||||
failed_flags.insert(ValidationFlags::PATTERN_MISMATCH);
|
||||
}
|
||||
}
|
||||
|
||||
if validate_flags.contains(ValidationFlags::BAD_INPUT) {
|
||||
if self.suffers_from_bad_input(&value) {
|
||||
failed_flags.insert(ValidationFlags::BAD_INPUT);
|
||||
}
|
||||
}
|
||||
|
||||
if validate_flags.intersects(ValidationFlags::TOO_LONG | ValidationFlags::TOO_SHORT) {
|
||||
failed_flags |= self.suffers_from_length_issues(&value);
|
||||
}
|
||||
|
||||
if validate_flags.intersects(
|
||||
ValidationFlags::RANGE_UNDERFLOW |
|
||||
ValidationFlags::RANGE_OVERFLOW |
|
||||
ValidationFlags::STEP_MISMATCH,
|
||||
) {
|
||||
failed_flags |= self.suffers_from_range_issues(&value);
|
||||
}
|
||||
|
||||
failed_flags & validate_flags
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2545,3 +2893,68 @@ fn milliseconds_to_datetime(value: f64) -> Result<NaiveDateTime, ()> {
|
|||
let nanoseconds = milliseconds * 1e6;
|
||||
NaiveDateTime::from_timestamp_opt(seconds as i64, nanoseconds as u32).ok_or(())
|
||||
}
|
||||
|
||||
// This is used to compile JS-compatible regex provided in pattern attribute
|
||||
// that matches only the entirety of string.
|
||||
// https://html.spec.whatwg.org/multipage/#compiled-pattern-regular-expression
|
||||
fn compile_pattern(cx: SafeJSContext, pattern_str: &str, out_regex: MutableHandleObject) -> bool {
|
||||
// First check if pattern compiles...
|
||||
if new_js_regex(cx, pattern_str, out_regex) {
|
||||
// ...and if it does make pattern that matches only the entirety of string
|
||||
let pattern_str = format!("^(?:{})$", pattern_str);
|
||||
new_js_regex(cx, &pattern_str, out_regex)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unsafe_code)]
|
||||
fn new_js_regex(cx: SafeJSContext, pattern: &str, mut out_regex: MutableHandleObject) -> bool {
|
||||
let pattern: Vec<u16> = pattern.encode_utf16().collect();
|
||||
unsafe {
|
||||
out_regex.set(NewUCRegExpObject(
|
||||
*cx,
|
||||
pattern.as_ptr(),
|
||||
pattern.len(),
|
||||
RegExpFlags {
|
||||
flags_: RegExpFlag_Unicode,
|
||||
},
|
||||
));
|
||||
if out_regex.is_null() {
|
||||
JS_ClearPendingException(*cx);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
#[allow(unsafe_code)]
|
||||
fn matches_js_regex(cx: SafeJSContext, regex_obj: HandleObject, value: &str) -> Result<bool, ()> {
|
||||
let mut value: Vec<u16> = value.encode_utf16().collect();
|
||||
|
||||
unsafe {
|
||||
let mut is_regex = false;
|
||||
assert!(ObjectIsRegExp(*cx, regex_obj, &mut is_regex));
|
||||
assert!(is_regex);
|
||||
|
||||
rooted!(in(*cx) let mut rval = UndefinedValue());
|
||||
let mut index = 0;
|
||||
|
||||
let ok = ExecuteRegExpNoStatics(
|
||||
*cx,
|
||||
regex_obj,
|
||||
value.as_mut_ptr(),
|
||||
value.len(),
|
||||
&mut index,
|
||||
true,
|
||||
&mut rval.handle_mut(),
|
||||
);
|
||||
|
||||
if ok {
|
||||
Ok(!rval.is_null())
|
||||
} else {
|
||||
JS_ClearPendingException(*cx);
|
||||
Err(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,7 +14,7 @@ use crate::dom::htmlelement::HTMLElement;
|
|||
use crate::dom::htmlformelement::{FormControl, HTMLFormElement};
|
||||
use crate::dom::node::{window_from_node, Node};
|
||||
use crate::dom::validation::Validatable;
|
||||
use crate::dom::validitystate::{ValidationFlags, ValidityState};
|
||||
use crate::dom::validitystate::ValidityState;
|
||||
use crate::dom::virtualmethods::VirtualMethods;
|
||||
use dom_struct::dom_struct;
|
||||
use html5ever::{LocalName, Prefix};
|
||||
|
@ -28,6 +28,7 @@ pub struct HTMLObjectElement {
|
|||
#[ignore_malloc_size_of = "Arc"]
|
||||
image: DomRefCell<Option<Arc<Image>>>,
|
||||
form_owner: MutNullableDom<HTMLFormElement>,
|
||||
validity_state: MutNullableDom<ValidityState>,
|
||||
}
|
||||
|
||||
impl HTMLObjectElement {
|
||||
|
@ -40,6 +41,7 @@ impl HTMLObjectElement {
|
|||
htmlelement: HTMLElement::new_inherited(local_name, prefix, document),
|
||||
image: DomRefCell::new(None),
|
||||
form_owner: Default::default(),
|
||||
validity_state: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -82,12 +84,6 @@ impl<'a> ProcessDataURL for &'a HTMLObjectElement {
|
|||
}
|
||||
|
||||
impl HTMLObjectElementMethods for HTMLObjectElement {
|
||||
// https://html.spec.whatwg.org/multipage/#dom-cva-validity
|
||||
fn Validity(&self) -> DomRoot<ValidityState> {
|
||||
let window = window_from_node(self);
|
||||
ValidityState::new(&window, self.upcast())
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/#dom-object-type
|
||||
make_getter!(Type, "type");
|
||||
|
||||
|
@ -98,16 +94,51 @@ impl HTMLObjectElementMethods for HTMLObjectElement {
|
|||
fn GetForm(&self) -> Option<DomRoot<HTMLFormElement>> {
|
||||
self.form_owner()
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/#dom-cva-willvalidate
|
||||
fn WillValidate(&self) -> bool {
|
||||
self.is_instance_validatable()
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/#dom-cva-validity
|
||||
fn Validity(&self) -> DomRoot<ValidityState> {
|
||||
self.validity_state()
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/#dom-cva-checkvalidity
|
||||
fn CheckValidity(&self) -> bool {
|
||||
self.check_validity()
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/#dom-cva-reportvalidity
|
||||
fn ReportValidity(&self) -> bool {
|
||||
self.report_validity()
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/#dom-cva-validationmessage
|
||||
fn ValidationMessage(&self) -> DOMString {
|
||||
self.validation_message()
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/#dom-cva-setcustomvalidity
|
||||
fn SetCustomValidity(&self, error: DOMString) {
|
||||
self.validity_state().set_custom_error_message(error);
|
||||
}
|
||||
}
|
||||
|
||||
impl Validatable for HTMLObjectElement {
|
||||
fn is_instance_validatable(&self) -> bool {
|
||||
true
|
||||
fn as_element(&self) -> &Element {
|
||||
self.upcast()
|
||||
}
|
||||
fn validate(&self, validate_flags: ValidationFlags) -> bool {
|
||||
if validate_flags.is_empty() {}
|
||||
// Need more flag check for different validation types later
|
||||
true
|
||||
|
||||
fn validity_state(&self) -> DomRoot<ValidityState> {
|
||||
self.validity_state
|
||||
.or_init(|| ValidityState::new(&window_from_node(self), self.upcast()))
|
||||
}
|
||||
|
||||
fn is_instance_validatable(&self) -> bool {
|
||||
// https://html.spec.whatwg.org/multipage/#the-object-element%3Abarred-from-constraint-validation
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -14,6 +14,7 @@ use crate::dom::htmlelement::HTMLElement;
|
|||
use crate::dom::htmlformelement::{FormControl, HTMLFormElement};
|
||||
use crate::dom::node::{window_from_node, Node};
|
||||
use crate::dom::nodelist::NodeList;
|
||||
use crate::dom::validation::Validatable;
|
||||
use crate::dom::validitystate::ValidityState;
|
||||
use crate::dom::virtualmethods::VirtualMethods;
|
||||
use dom_struct::dom_struct;
|
||||
|
@ -25,6 +26,7 @@ pub struct HTMLOutputElement {
|
|||
form_owner: MutNullableDom<HTMLFormElement>,
|
||||
labels_node_list: MutNullableDom<NodeList>,
|
||||
default_value_override: DomRefCell<Option<DOMString>>,
|
||||
validity_state: MutNullableDom<ValidityState>,
|
||||
}
|
||||
|
||||
impl HTMLOutputElement {
|
||||
|
@ -38,6 +40,7 @@ impl HTMLOutputElement {
|
|||
form_owner: Default::default(),
|
||||
labels_node_list: Default::default(),
|
||||
default_value_override: DomRefCell::new(None),
|
||||
validity_state: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -62,12 +65,6 @@ impl HTMLOutputElement {
|
|||
}
|
||||
|
||||
impl HTMLOutputElementMethods for HTMLOutputElement {
|
||||
// https://html.spec.whatwg.org/multipage/#dom-cva-validity
|
||||
fn Validity(&self) -> DomRoot<ValidityState> {
|
||||
let window = window_from_node(self);
|
||||
ValidityState::new(&window, self.upcast())
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/#dom-fae-form
|
||||
fn GetForm(&self) -> Option<DomRoot<HTMLFormElement>> {
|
||||
self.form_owner()
|
||||
|
@ -118,6 +115,36 @@ impl HTMLOutputElementMethods for HTMLOutputElement {
|
|||
|
||||
// https://html.spec.whatwg.org/multipage/#dom-fe-name
|
||||
make_getter!(Name, "name");
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/#dom-cva-willvalidate
|
||||
fn WillValidate(&self) -> bool {
|
||||
self.is_instance_validatable()
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/#dom-cva-validity
|
||||
fn Validity(&self) -> DomRoot<ValidityState> {
|
||||
self.validity_state()
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/#dom-cva-checkvalidity
|
||||
fn CheckValidity(&self) -> bool {
|
||||
self.check_validity()
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/#dom-cva-reportvalidity
|
||||
fn ReportValidity(&self) -> bool {
|
||||
self.report_validity()
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/#dom-cva-validationmessage
|
||||
fn ValidationMessage(&self) -> DOMString {
|
||||
self.validation_message()
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/#dom-cva-setcustomvalidity
|
||||
fn SetCustomValidity(&self, error: DOMString) {
|
||||
self.validity_state().set_custom_error_message(error);
|
||||
}
|
||||
}
|
||||
|
||||
impl VirtualMethods for HTMLOutputElement {
|
||||
|
@ -149,3 +176,19 @@ impl FormControl for HTMLOutputElement {
|
|||
self.upcast::<Element>()
|
||||
}
|
||||
}
|
||||
|
||||
impl Validatable for HTMLOutputElement {
|
||||
fn as_element(&self) -> &Element {
|
||||
self.upcast()
|
||||
}
|
||||
|
||||
fn validity_state(&self) -> DomRoot<ValidityState> {
|
||||
self.validity_state
|
||||
.or_init(|| ValidityState::new(&window_from_node(self), self.upcast()))
|
||||
}
|
||||
|
||||
fn is_instance_validatable(&self) -> bool {
|
||||
// output is not a submittable element (https://html.spec.whatwg.org/multipage/#category-submit)
|
||||
false
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,7 +26,7 @@ use crate::dom::htmloptionelement::HTMLOptionElement;
|
|||
use crate::dom::htmloptionscollection::HTMLOptionsCollection;
|
||||
use crate::dom::node::{window_from_node, BindContext, Node, UnbindContext};
|
||||
use crate::dom::nodelist::NodeList;
|
||||
use crate::dom::validation::Validatable;
|
||||
use crate::dom::validation::{is_barred_by_datalist_ancestor, Validatable};
|
||||
use crate::dom::validitystate::{ValidationFlags, ValidityState};
|
||||
use crate::dom::virtualmethods::VirtualMethods;
|
||||
use dom_struct::dom_struct;
|
||||
|
@ -62,6 +62,7 @@ pub struct HTMLSelectElement {
|
|||
options: MutNullableDom<HTMLOptionsCollection>,
|
||||
form_owner: MutNullableDom<HTMLFormElement>,
|
||||
labels_node_list: MutNullableDom<NodeList>,
|
||||
validity_state: MutNullableDom<ValidityState>,
|
||||
}
|
||||
|
||||
static DEFAULT_SELECT_SIZE: u32 = 0;
|
||||
|
@ -82,6 +83,7 @@ impl HTMLSelectElement {
|
|||
options: Default::default(),
|
||||
form_owner: Default::default(),
|
||||
labels_node_list: Default::default(),
|
||||
validity_state: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -113,6 +115,18 @@ impl HTMLSelectElement {
|
|||
})
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/#placeholder-label-option
|
||||
fn get_placeholder_label_option(&self) -> Option<DomRoot<HTMLOptionElement>> {
|
||||
if self.Required() && !self.Multiple() && self.display_size() == 1 {
|
||||
self.list_of_options().next().filter(|node| {
|
||||
let parent = node.upcast::<Node>().GetParentNode();
|
||||
node.Value().is_empty() && parent.as_deref() == Some(self.upcast())
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/#the-select-element:concept-form-reset-control
|
||||
pub fn reset(&self) {
|
||||
for opt in self.list_of_options() {
|
||||
|
@ -196,12 +210,6 @@ impl HTMLSelectElement {
|
|||
}
|
||||
|
||||
impl HTMLSelectElementMethods for HTMLSelectElement {
|
||||
// https://html.spec.whatwg.org/multipage/#dom-cva-validity
|
||||
fn Validity(&self) -> DomRoot<ValidityState> {
|
||||
let window = window_from_node(self);
|
||||
ValidityState::new(&window, self.upcast())
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/#dom-select-add
|
||||
fn Add(
|
||||
&self,
|
||||
|
@ -234,6 +242,12 @@ impl HTMLSelectElementMethods for HTMLSelectElement {
|
|||
// https://html.spec.whatwg.org/multipage/#dom-fe-name
|
||||
make_atomic_setter!(SetName, "name");
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/#dom-select-required
|
||||
make_bool_getter!(Required, "required");
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/#dom-select-required
|
||||
make_bool_setter!(SetRequired, "required");
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/#dom-select-size
|
||||
make_uint_getter!(Size, "size", DEFAULT_SELECT_SIZE);
|
||||
|
||||
|
@ -354,6 +368,36 @@ impl HTMLSelectElementMethods for HTMLSelectElement {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/#dom-cva-willvalidate
|
||||
fn WillValidate(&self) -> bool {
|
||||
self.is_instance_validatable()
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/#dom-cva-validity
|
||||
fn Validity(&self) -> DomRoot<ValidityState> {
|
||||
self.validity_state()
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/#dom-cva-checkvalidity
|
||||
fn CheckValidity(&self) -> bool {
|
||||
self.check_validity()
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/#dom-cva-reportvalidity
|
||||
fn ReportValidity(&self) -> bool {
|
||||
self.report_validity()
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/#dom-cva-validationmessage
|
||||
fn ValidationMessage(&self) -> DOMString {
|
||||
self.validation_message()
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/#dom-cva-setcustomvalidity
|
||||
fn SetCustomValidity(&self, error: DOMString) {
|
||||
self.validity_state().set_custom_error_message(error);
|
||||
}
|
||||
}
|
||||
|
||||
impl VirtualMethods for HTMLSelectElement {
|
||||
|
@ -435,13 +479,36 @@ impl FormControl for HTMLSelectElement {
|
|||
}
|
||||
|
||||
impl Validatable for HTMLSelectElement {
|
||||
fn is_instance_validatable(&self) -> bool {
|
||||
true
|
||||
fn as_element(&self) -> &Element {
|
||||
self.upcast()
|
||||
}
|
||||
fn validate(&self, validate_flags: ValidationFlags) -> bool {
|
||||
if validate_flags.is_empty() {}
|
||||
// Need more flag check for different validation types later
|
||||
true
|
||||
|
||||
fn validity_state(&self) -> DomRoot<ValidityState> {
|
||||
self.validity_state
|
||||
.or_init(|| ValidityState::new(&window_from_node(self), self.upcast()))
|
||||
}
|
||||
|
||||
fn is_instance_validatable(&self) -> bool {
|
||||
// https://html.spec.whatwg.org/multipage/#enabling-and-disabling-form-controls%3A-the-disabled-attribute%3Abarred-from-constraint-validation
|
||||
// https://html.spec.whatwg.org/multipage/#the-datalist-element%3Abarred-from-constraint-validation
|
||||
!self.upcast::<Element>().disabled_state() && !is_barred_by_datalist_ancestor(self.upcast())
|
||||
}
|
||||
|
||||
fn perform_validation(&self, validate_flags: ValidationFlags) -> ValidationFlags {
|
||||
let mut failed_flags = ValidationFlags::empty();
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/#suffering-from-being-missing
|
||||
// https://html.spec.whatwg.org/multipage/#the-select-element%3Asuffering-from-being-missing
|
||||
if validate_flags.contains(ValidationFlags::VALUE_MISSING) && self.Required() {
|
||||
let placeholder = self.get_placeholder_label_option();
|
||||
let selected_option = self
|
||||
.list_of_options()
|
||||
.filter(|e| e.Selected() && placeholder.as_ref() != Some(e))
|
||||
.next();
|
||||
failed_flags.set(ValidationFlags::VALUE_MISSING, selected_option.is_none());
|
||||
}
|
||||
|
||||
failed_flags
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -29,7 +29,8 @@ use crate::dom::node::{
|
|||
};
|
||||
use crate::dom::nodelist::NodeList;
|
||||
use crate::dom::textcontrol::{TextControlElement, TextControlSelection};
|
||||
use crate::dom::validation::Validatable;
|
||||
use crate::dom::validation::{is_barred_by_datalist_ancestor, Validatable};
|
||||
use crate::dom::validitystate::{ValidationFlags, ValidityState};
|
||||
use crate::dom::virtualmethods::VirtualMethods;
|
||||
use crate::textinput::{
|
||||
Direction, KeyReaction, Lines, SelectionDirection, TextInput, UTF16CodeUnits, UTF8Bytes,
|
||||
|
@ -53,6 +54,7 @@ pub struct HTMLTextAreaElement {
|
|||
value_dirty: Cell<bool>,
|
||||
form_owner: MutNullableDom<HTMLFormElement>,
|
||||
labels_node_list: MutNullableDom<NodeList>,
|
||||
validity_state: MutNullableDom<ValidityState>,
|
||||
}
|
||||
|
||||
pub trait LayoutHTMLTextAreaElementHelpers {
|
||||
|
@ -163,6 +165,7 @@ impl HTMLTextAreaElement {
|
|||
value_dirty: Cell::new(false),
|
||||
form_owner: Default::default(),
|
||||
labels_node_list: Default::default(),
|
||||
validity_state: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -191,6 +194,13 @@ impl HTMLTextAreaElement {
|
|||
let el = self.upcast::<Element>();
|
||||
el.set_placeholder_shown_state(has_placeholder && !has_value);
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/#concept-fe-mutable
|
||||
fn is_mutable(&self) -> bool {
|
||||
// https://html.spec.whatwg.org/multipage/#the-textarea-element%3Aconcept-fe-mutable
|
||||
// https://html.spec.whatwg.org/multipage/#the-readonly-attribute:concept-fe-mutable
|
||||
!(self.upcast::<Element>().disabled_state() || self.ReadOnly())
|
||||
}
|
||||
}
|
||||
|
||||
impl TextControlElement for HTMLTextAreaElement {
|
||||
|
@ -395,6 +405,36 @@ impl HTMLTextAreaElementMethods for HTMLTextAreaElement {
|
|||
self.selection()
|
||||
.set_dom_range_text(replacement, Some(start), Some(end), selection_mode)
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/#dom-cva-willvalidate
|
||||
fn WillValidate(&self) -> bool {
|
||||
self.is_instance_validatable()
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/#dom-cva-validity
|
||||
fn Validity(&self) -> DomRoot<ValidityState> {
|
||||
self.validity_state()
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/#dom-cva-checkvalidity
|
||||
fn CheckValidity(&self) -> bool {
|
||||
self.check_validity()
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/#dom-cva-reportvalidity
|
||||
fn ReportValidity(&self) -> bool {
|
||||
self.report_validity()
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/#dom-cva-validationmessage
|
||||
fn ValidationMessage(&self) -> DOMString {
|
||||
self.validation_message()
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/#dom-cva-setcustomvalidity
|
||||
fn SetCustomValidity(&self, error: DOMString) {
|
||||
self.validity_state().set_custom_error_message(error);
|
||||
}
|
||||
}
|
||||
|
||||
impl HTMLTextAreaElement {
|
||||
|
@ -643,4 +683,61 @@ impl FormControl for HTMLTextAreaElement {
|
|||
}
|
||||
}
|
||||
|
||||
impl Validatable for HTMLTextAreaElement {}
|
||||
impl Validatable for HTMLTextAreaElement {
|
||||
fn as_element(&self) -> &Element {
|
||||
self.upcast()
|
||||
}
|
||||
|
||||
fn validity_state(&self) -> DomRoot<ValidityState> {
|
||||
self.validity_state
|
||||
.or_init(|| ValidityState::new(&window_from_node(self), self.upcast()))
|
||||
}
|
||||
|
||||
fn is_instance_validatable(&self) -> bool {
|
||||
// https://html.spec.whatwg.org/multipage/#enabling-and-disabling-form-controls%3A-the-disabled-attribute%3Abarred-from-constraint-validation
|
||||
// https://html.spec.whatwg.org/multipage/#the-textarea-element%3Abarred-from-constraint-validation
|
||||
// https://html.spec.whatwg.org/multipage/#the-datalist-element%3Abarred-from-constraint-validation
|
||||
!self.upcast::<Element>().disabled_state() &&
|
||||
!self.ReadOnly() &&
|
||||
!is_barred_by_datalist_ancestor(self.upcast())
|
||||
}
|
||||
|
||||
fn perform_validation(&self, validate_flags: ValidationFlags) -> ValidationFlags {
|
||||
let mut failed_flags = ValidationFlags::empty();
|
||||
|
||||
let textinput = self.textinput.borrow();
|
||||
let UTF16CodeUnits(value_len) = textinput.utf16_len();
|
||||
let last_edit_by_user = !textinput.was_last_change_by_set_content();
|
||||
let value_dirty = self.value_dirty.get();
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/#suffering-from-being-missing
|
||||
// https://html.spec.whatwg.org/multipage/#the-textarea-element%3Asuffering-from-being-missing
|
||||
if validate_flags.contains(ValidationFlags::VALUE_MISSING) {
|
||||
if self.Required() && self.is_mutable() && value_len == 0 {
|
||||
failed_flags.insert(ValidationFlags::VALUE_MISSING);
|
||||
}
|
||||
}
|
||||
|
||||
if value_dirty && last_edit_by_user && value_len > 0 {
|
||||
// https://html.spec.whatwg.org/multipage/#suffering-from-being-too-long
|
||||
// https://html.spec.whatwg.org/multipage/#limiting-user-input-length%3A-the-maxlength-attribute%3Asuffering-from-being-too-long
|
||||
if validate_flags.contains(ValidationFlags::TOO_LONG) {
|
||||
let max_length = self.MaxLength();
|
||||
if max_length != DEFAULT_MAX_LENGTH && value_len > (max_length as usize) {
|
||||
failed_flags.insert(ValidationFlags::TOO_LONG);
|
||||
}
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/#suffering-from-being-too-short
|
||||
// https://html.spec.whatwg.org/multipage/#setting-minimum-input-length-requirements%3A-the-minlength-attribute%3Asuffering-from-being-too-short
|
||||
if validate_flags.contains(ValidationFlags::TOO_SHORT) {
|
||||
let min_length = self.MinLength();
|
||||
if min_length != DEFAULT_MIN_LENGTH && value_len < (min_length as usize) {
|
||||
failed_flags.insert(ValidationFlags::TOO_SHORT);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
failed_flags
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,13 +1,115 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
use crate::dom::validitystate::ValidationFlags;
|
||||
use crate::dom::bindings::codegen::Bindings::EventBinding::EventBinding::EventMethods;
|
||||
use crate::dom::bindings::codegen::Bindings::HTMLElementBinding::HTMLElementMethods;
|
||||
use crate::dom::bindings::inheritance::Castable;
|
||||
use crate::dom::bindings::root::DomRoot;
|
||||
use crate::dom::bindings::str::DOMString;
|
||||
use crate::dom::element::Element;
|
||||
use crate::dom::eventtarget::EventTarget;
|
||||
use crate::dom::htmldatalistelement::HTMLDataListElement;
|
||||
use crate::dom::htmlelement::HTMLElement;
|
||||
use crate::dom::node::Node;
|
||||
use crate::dom::validitystate::{ValidationFlags, ValidityState};
|
||||
|
||||
/// Trait for elements with constraint validation support
|
||||
pub trait Validatable {
|
||||
fn is_instance_validatable(&self) -> bool {
|
||||
true
|
||||
fn as_element(&self) -> ∈
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/#dom-cva-validity
|
||||
fn validity_state(&self) -> DomRoot<ValidityState>;
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/#candidate-for-constraint-validation
|
||||
fn is_instance_validatable(&self) -> bool;
|
||||
|
||||
// Check if element satisfies its constraints, excluding custom errors
|
||||
fn perform_validation(&self, _validate_flags: ValidationFlags) -> ValidationFlags {
|
||||
ValidationFlags::empty()
|
||||
}
|
||||
fn validate(&self, _validate_flags: ValidationFlags) -> bool {
|
||||
true
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/#concept-fv-valid
|
||||
fn validate(&self, validate_flags: ValidationFlags) -> ValidationFlags {
|
||||
let mut failed_flags = self.perform_validation(validate_flags);
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/#suffering-from-a-custom-error
|
||||
if validate_flags.contains(ValidationFlags::CUSTOM_ERROR) {
|
||||
if !self.validity_state().custom_error_message().is_empty() {
|
||||
failed_flags.insert(ValidationFlags::CUSTOM_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
failed_flags
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/#check-validity-steps
|
||||
fn check_validity(&self) -> bool {
|
||||
if self.is_instance_validatable() && !self.validate(ValidationFlags::all()).is_empty() {
|
||||
self.as_element()
|
||||
.upcast::<EventTarget>()
|
||||
.fire_cancelable_event(atom!("invalid"));
|
||||
false
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/#report-validity-steps
|
||||
fn report_validity(&self) -> bool {
|
||||
// Step 1.
|
||||
if !self.is_instance_validatable() {
|
||||
return true;
|
||||
}
|
||||
|
||||
let flags = self.validate(ValidationFlags::all());
|
||||
if flags.is_empty() {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Step 1.1.
|
||||
let event = self
|
||||
.as_element()
|
||||
.upcast::<EventTarget>()
|
||||
.fire_cancelable_event(atom!("invalid"));
|
||||
|
||||
// Step 1.2.
|
||||
if !event.DefaultPrevented() {
|
||||
println!(
|
||||
"Validation error: {}",
|
||||
validation_message_for_flags(&self.validity_state(), flags)
|
||||
);
|
||||
if let Some(html_elem) = self.as_element().downcast::<HTMLElement>() {
|
||||
html_elem.Focus();
|
||||
}
|
||||
}
|
||||
|
||||
// Step 1.3.
|
||||
false
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/#dom-cva-validationmessage
|
||||
fn validation_message(&self) -> DOMString {
|
||||
if self.is_instance_validatable() {
|
||||
let flags = self.validate(ValidationFlags::all());
|
||||
validation_message_for_flags(&self.validity_state(), flags)
|
||||
} else {
|
||||
DOMString::new()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/#the-datalist-element%3Abarred-from-constraint-validation
|
||||
pub fn is_barred_by_datalist_ancestor(elem: &Node) -> bool {
|
||||
elem.upcast::<Node>()
|
||||
.ancestors()
|
||||
.any(|node| node.is::<HTMLDataListElement>())
|
||||
}
|
||||
|
||||
// Get message for given validation flags or custom error message
|
||||
fn validation_message_for_flags(state: &ValidityState, failed_flags: ValidationFlags) -> DOMString {
|
||||
if failed_flags.contains(ValidationFlags::CUSTOM_ERROR) {
|
||||
state.custom_error_message().clone()
|
||||
} else {
|
||||
DOMString::from(failed_flags.to_string())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,30 +2,18 @@
|
|||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use crate::dom::bindings::cell::{DomRefCell, Ref};
|
||||
use crate::dom::bindings::codegen::Bindings::ValidityStateBinding::ValidityStateMethods;
|
||||
use crate::dom::bindings::reflector::{reflect_dom_object, Reflector};
|
||||
use crate::dom::bindings::root::{Dom, DomRoot};
|
||||
use crate::dom::bindings::str::DOMString;
|
||||
use crate::dom::element::Element;
|
||||
use crate::dom::window::Window;
|
||||
use dom_struct::dom_struct;
|
||||
use itertools::Itertools;
|
||||
use std::fmt;
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/#validity-states
|
||||
#[derive(JSTraceable, MallocSizeOf)]
|
||||
#[allow(dead_code)]
|
||||
pub enum ValidityStatus {
|
||||
ValueMissing,
|
||||
TypeMismatch,
|
||||
PatternMismatch,
|
||||
TooLong,
|
||||
TooShort,
|
||||
RangeUnderflow,
|
||||
RangeOverflow,
|
||||
StepMismatch,
|
||||
BadInput,
|
||||
CustomError,
|
||||
Valid,
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
pub struct ValidationFlags: u32 {
|
||||
const VALUE_MISSING = 0b0000000001;
|
||||
|
@ -41,12 +29,41 @@ bitflags! {
|
|||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for ValidationFlags {
|
||||
fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||
let flag_to_message = [
|
||||
(ValidationFlags::VALUE_MISSING, "Value missing"),
|
||||
(ValidationFlags::TYPE_MISMATCH, "Type mismatch"),
|
||||
(ValidationFlags::PATTERN_MISMATCH, "Pattern mismatch"),
|
||||
(ValidationFlags::TOO_LONG, "Too long"),
|
||||
(ValidationFlags::TOO_SHORT, "Too short"),
|
||||
(ValidationFlags::RANGE_UNDERFLOW, "Range underflow"),
|
||||
(ValidationFlags::RANGE_OVERFLOW, "Range overflow"),
|
||||
(ValidationFlags::STEP_MISMATCH, "Step mismatch"),
|
||||
(ValidationFlags::BAD_INPUT, "Bad input"),
|
||||
(ValidationFlags::CUSTOM_ERROR, "Custom error"),
|
||||
];
|
||||
|
||||
flag_to_message
|
||||
.iter()
|
||||
.filter_map(|&(flag, flag_str)| {
|
||||
if self.contains(flag) {
|
||||
Some(flag_str)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.format(", ")
|
||||
.fmt(formatter)
|
||||
}
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/#validitystate
|
||||
#[dom_struct]
|
||||
pub struct ValidityState {
|
||||
reflector_: Reflector,
|
||||
element: Dom<Element>,
|
||||
state: ValidityStatus,
|
||||
custom_error_message: DomRefCell<DOMString>,
|
||||
}
|
||||
|
||||
impl ValidityState {
|
||||
|
@ -54,68 +71,100 @@ impl ValidityState {
|
|||
ValidityState {
|
||||
reflector_: Reflector::new(),
|
||||
element: Dom::from_ref(element),
|
||||
state: ValidityStatus::Valid,
|
||||
custom_error_message: DomRefCell::new(DOMString::new()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new(window: &Window, element: &Element) -> DomRoot<ValidityState> {
|
||||
reflect_dom_object(Box::new(ValidityState::new_inherited(element)), window)
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/#custom-validity-error-message
|
||||
pub fn custom_error_message(&self) -> Ref<DOMString> {
|
||||
self.custom_error_message.borrow()
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/#custom-validity-error-message
|
||||
pub fn set_custom_error_message(&self, error: DOMString) {
|
||||
*self.custom_error_message.borrow_mut() = error;
|
||||
}
|
||||
}
|
||||
|
||||
impl ValidityStateMethods for ValidityState {
|
||||
// https://html.spec.whatwg.org/multipage/#dom-validitystate-valuemissing
|
||||
fn ValueMissing(&self) -> bool {
|
||||
false
|
||||
self.element.as_maybe_validatable().map_or(false, |e| {
|
||||
!e.validate(ValidationFlags::VALUE_MISSING).is_empty()
|
||||
})
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/#dom-validitystate-typemismatch
|
||||
fn TypeMismatch(&self) -> bool {
|
||||
false
|
||||
self.element.as_maybe_validatable().map_or(false, |e| {
|
||||
!e.validate(ValidationFlags::TYPE_MISMATCH).is_empty()
|
||||
})
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/#dom-validitystate-patternmismatch
|
||||
fn PatternMismatch(&self) -> bool {
|
||||
false
|
||||
self.element.as_maybe_validatable().map_or(false, |e| {
|
||||
!e.validate(ValidationFlags::PATTERN_MISMATCH).is_empty()
|
||||
})
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/#dom-validitystate-toolong
|
||||
fn TooLong(&self) -> bool {
|
||||
false
|
||||
self.element
|
||||
.as_maybe_validatable()
|
||||
.map_or(false, |e| !e.validate(ValidationFlags::TOO_LONG).is_empty())
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/#dom-validitystate-tooshort
|
||||
fn TooShort(&self) -> bool {
|
||||
false
|
||||
self.element.as_maybe_validatable().map_or(false, |e| {
|
||||
!e.validate(ValidationFlags::TOO_SHORT).is_empty()
|
||||
})
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/#dom-validitystate-rangeunderflow
|
||||
fn RangeUnderflow(&self) -> bool {
|
||||
false
|
||||
self.element.as_maybe_validatable().map_or(false, |e| {
|
||||
!e.validate(ValidationFlags::RANGE_UNDERFLOW).is_empty()
|
||||
})
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/#dom-validitystate-rangeoverflow
|
||||
fn RangeOverflow(&self) -> bool {
|
||||
false
|
||||
self.element.as_maybe_validatable().map_or(false, |e| {
|
||||
!e.validate(ValidationFlags::RANGE_OVERFLOW).is_empty()
|
||||
})
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/#dom-validitystate-stepmismatch
|
||||
fn StepMismatch(&self) -> bool {
|
||||
false
|
||||
self.element.as_maybe_validatable().map_or(false, |e| {
|
||||
!e.validate(ValidationFlags::STEP_MISMATCH).is_empty()
|
||||
})
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/#dom-validitystate-badinput
|
||||
fn BadInput(&self) -> bool {
|
||||
false
|
||||
self.element.as_maybe_validatable().map_or(false, |e| {
|
||||
!e.validate(ValidationFlags::BAD_INPUT).is_empty()
|
||||
})
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/#dom-validitystate-customerror
|
||||
fn CustomError(&self) -> bool {
|
||||
false
|
||||
self.element.as_maybe_validatable().map_or(false, |e| {
|
||||
!e.validate(ValidationFlags::CUSTOM_ERROR).is_empty()
|
||||
})
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/#dom-validitystate-valid
|
||||
fn Valid(&self) -> bool {
|
||||
false
|
||||
self.element
|
||||
.as_maybe_validatable()
|
||||
.map_or(true, |e| e.validate(ValidationFlags::all()).is_empty())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,12 +30,12 @@ interface HTMLButtonElement : HTMLElement {
|
|||
attribute DOMString value;
|
||||
// attribute HTMLMenuElement? menu;
|
||||
|
||||
//readonly attribute boolean willValidate;
|
||||
readonly attribute boolean willValidate;
|
||||
readonly attribute ValidityState validity;
|
||||
//readonly attribute DOMString validationMessage;
|
||||
//boolean checkValidity();
|
||||
//boolean reportValidity();
|
||||
//void setCustomValidity(DOMString error);
|
||||
readonly attribute DOMString validationMessage;
|
||||
boolean checkValidity();
|
||||
boolean reportValidity();
|
||||
void setCustomValidity(DOMString error);
|
||||
|
||||
readonly attribute NodeList labels;
|
||||
};
|
||||
|
|
|
@ -17,10 +17,10 @@ interface HTMLFieldSetElement : HTMLElement {
|
|||
|
||||
[SameObject] readonly attribute HTMLCollection elements;
|
||||
|
||||
//readonly attribute boolean willValidate;
|
||||
readonly attribute boolean willValidate;
|
||||
[SameObject] readonly attribute ValidityState validity;
|
||||
//readonly attribute DOMString validationMessage;
|
||||
//boolean checkValidity();
|
||||
//boolean reportValidity();
|
||||
//void setCustomValidity(DOMString error);
|
||||
readonly attribute DOMString validationMessage;
|
||||
boolean checkValidity();
|
||||
boolean reportValidity();
|
||||
void setCustomValidity(DOMString error);
|
||||
};
|
||||
|
|
|
@ -34,8 +34,8 @@ interface HTMLFormElement : HTMLElement {
|
|||
void submit();
|
||||
[CEReactions]
|
||||
void reset();
|
||||
//boolean checkValidity();
|
||||
//boolean reportValidity();
|
||||
boolean checkValidity();
|
||||
boolean reportValidity();
|
||||
};
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/#selectionmode
|
||||
|
|
|
@ -82,12 +82,12 @@ interface HTMLInputElement : HTMLElement {
|
|||
[Throws] void stepUp(optional long n = 1);
|
||||
[Throws] void stepDown(optional long n = 1);
|
||||
|
||||
//readonly attribute boolean willValidate;
|
||||
//readonly attribute ValidityState validity;
|
||||
//readonly attribute DOMString validationMessage;
|
||||
//boolean checkValidity();
|
||||
//boolean reportValidity();
|
||||
//void setCustomValidity(DOMString error);
|
||||
readonly attribute boolean willValidate;
|
||||
readonly attribute ValidityState validity;
|
||||
readonly attribute DOMString validationMessage;
|
||||
boolean checkValidity();
|
||||
boolean reportValidity();
|
||||
void setCustomValidity(DOMString error);
|
||||
|
||||
readonly attribute NodeList? labels;
|
||||
|
||||
|
|
|
@ -25,12 +25,12 @@ interface HTMLObjectElement : HTMLElement {
|
|||
//readonly attribute Document? contentDocument;
|
||||
//readonly attribute WindowProxy? contentWindow;
|
||||
|
||||
//readonly attribute boolean willValidate;
|
||||
readonly attribute boolean willValidate;
|
||||
readonly attribute ValidityState validity;
|
||||
//readonly attribute DOMString validationMessage;
|
||||
//boolean checkValidity();
|
||||
//boolean reportValidity();
|
||||
//void setCustomValidity(DOMString error);
|
||||
readonly attribute DOMString validationMessage;
|
||||
boolean checkValidity();
|
||||
boolean reportValidity();
|
||||
void setCustomValidity(DOMString error);
|
||||
|
||||
//legacycaller any (any... arguments);
|
||||
|
||||
|
|
|
@ -18,12 +18,12 @@ interface HTMLOutputElement : HTMLElement {
|
|||
[CEReactions]
|
||||
attribute DOMString value;
|
||||
|
||||
// readonly attribute boolean willValidate;
|
||||
readonly attribute boolean willValidate;
|
||||
readonly attribute ValidityState validity;
|
||||
// readonly attribute DOMString validationMessage;
|
||||
// boolean checkValidity();
|
||||
// boolean reportValidity();
|
||||
// void setCustomValidity(DOMString error);
|
||||
readonly attribute DOMString validationMessage;
|
||||
boolean checkValidity();
|
||||
boolean reportValidity();
|
||||
void setCustomValidity(DOMString error);
|
||||
|
||||
readonly attribute NodeList labels;
|
||||
};
|
||||
|
|
|
@ -16,8 +16,8 @@ interface HTMLSelectElement : HTMLElement {
|
|||
attribute boolean multiple;
|
||||
[CEReactions]
|
||||
attribute DOMString name;
|
||||
// [CEReactions]
|
||||
// attribute boolean required;
|
||||
[CEReactions]
|
||||
attribute boolean required;
|
||||
[CEReactions]
|
||||
attribute unsigned long size;
|
||||
|
||||
|
@ -41,12 +41,12 @@ interface HTMLSelectElement : HTMLElement {
|
|||
attribute long selectedIndex;
|
||||
attribute DOMString value;
|
||||
|
||||
// readonly attribute boolean willValidate;
|
||||
readonly attribute boolean willValidate;
|
||||
readonly attribute ValidityState validity;
|
||||
// readonly attribute DOMString validationMessage;
|
||||
// boolean checkValidity();
|
||||
// boolean reportValidity();
|
||||
// void setCustomValidity(DOMString error);
|
||||
readonly attribute DOMString validationMessage;
|
||||
boolean checkValidity();
|
||||
boolean reportValidity();
|
||||
void setCustomValidity(DOMString error);
|
||||
|
||||
readonly attribute NodeList labels;
|
||||
};
|
||||
|
|
|
@ -43,12 +43,12 @@ interface HTMLTextAreaElement : HTMLElement {
|
|||
attribute [TreatNullAs=EmptyString] DOMString value;
|
||||
readonly attribute unsigned long textLength;
|
||||
|
||||
// readonly attribute boolean willValidate;
|
||||
// readonly attribute ValidityState validity;
|
||||
// readonly attribute DOMString validationMessage;
|
||||
// boolean checkValidity();
|
||||
// boolean reportValidity();
|
||||
// void setCustomValidity(DOMString error);
|
||||
readonly attribute boolean willValidate;
|
||||
readonly attribute ValidityState validity;
|
||||
readonly attribute DOMString validationMessage;
|
||||
boolean checkValidity();
|
||||
boolean reportValidity();
|
||||
void setCustomValidity(DOMString error);
|
||||
|
||||
readonly attribute NodeList labels;
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue