diff options
Diffstat (limited to 'components/script/dom')
20 files changed, 1107 insertions, 213 deletions
diff --git a/components/script/dom/bindings/str.rs b/components/script/dom/bindings/str.rs index 986554206b6..0905c157143 100644 --- a/components/script/dom/bindings/str.rs +++ b/components/script/dom/bindings/str.rs @@ -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 { diff --git a/components/script/dom/element.rs b/components/script/dom/element.rs index d7728165f94..0c991998f0e 100644 --- a/components/script/dom/element.rs +++ b/components/script/dom/element.rs @@ -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 diff --git a/components/script/dom/htmlbuttonelement.rs b/components/script/dom/htmlbuttonelement.rs index 45d1630e6d4..cb6e1d0c056 100755 --- a/components/script/dom/htmlbuttonelement.rs +++ b/components/script/dom/htmlbuttonelement.rs @@ -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 validity_state(&self) -> DomRoot<ValidityState> { + self.validity_state + .or_init(|| ValidityState::new(&window_from_node(self), 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 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()) } } diff --git a/components/script/dom/htmlfieldsetelement.rs b/components/script/dom/htmlfieldsetelement.rs index a277cf8e2ec..b2b05cbc5c4 100644 --- a/components/script/dom/htmlfieldsetelement.rs +++ b/components/script/dom/htmlfieldsetelement.rs @@ -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 + } +} diff --git a/components/script/dom/htmlformelement.rs b/components/script/dom/htmlformelement.rs index 08f2cf0499c..d3a23a2e3b0 100644 --- a/components/script/dom/htmlformelement.rs +++ b/components/script/dom/htmlformelement.rs @@ -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), diff --git a/components/script/dom/htmlinputelement.rs b/components/script/dom/htmlinputelement.rs index 22260f30d9d..6044048b0d7 100755 --- a/components/script/dom/htmlinputelement.rs +++ b/components/script/dom/htmlinputelement.rs @@ -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 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/#candidate-for-constraint-validation - true + // 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 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 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(()) + } + } +} diff --git a/components/script/dom/htmlobjectelement.rs b/components/script/dom/htmlobjectelement.rs index a218259e8cb..928c593cec6 100755 --- a/components/script/dom/htmlobjectelement.rs +++ b/components/script/dom/htmlobjectelement.rs @@ -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 validity_state(&self) -> DomRoot<ValidityState> { + self.validity_state + .or_init(|| ValidityState::new(&window_from_node(self), 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 is_instance_validatable(&self) -> bool { + // https://html.spec.whatwg.org/multipage/#the-object-element%3Abarred-from-constraint-validation + false } } diff --git a/components/script/dom/htmloutputelement.rs b/components/script/dom/htmloutputelement.rs index ac21b2e97a0..e20dbcc29b2 100644 --- a/components/script/dom/htmloutputelement.rs +++ b/components/script/dom/htmloutputelement.rs @@ -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 + } +} diff --git a/components/script/dom/htmlselectelement.rs b/components/script/dom/htmlselectelement.rs index 97474969ab2..caca2fddfc2 100755 --- a/components/script/dom/htmlselectelement.rs +++ b/components/script/dom/htmlselectelement.rs @@ -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 validity_state(&self) -> DomRoot<ValidityState> { + self.validity_state + .or_init(|| ValidityState::new(&window_from_node(self), 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 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 } } diff --git a/components/script/dom/htmltextareaelement.rs b/components/script/dom/htmltextareaelement.rs index 38fa642ca4c..b7d224db123 100755 --- a/components/script/dom/htmltextareaelement.rs +++ b/components/script/dom/htmltextareaelement.rs @@ -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 + } +} diff --git a/components/script/dom/validation.rs b/components/script/dom/validation.rs index b7380f75480..e6b36ec809d 100755 --- a/components/script/dom/validation.rs +++ b/components/script/dom/validation.rs @@ -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()) } } diff --git a/components/script/dom/validitystate.rs b/components/script/dom/validitystate.rs index 7237ca391e0..b7f4da79530 100755 --- a/components/script/dom/validitystate.rs +++ b/components/script/dom/validitystate.rs @@ -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()) } } diff --git a/components/script/dom/webidls/HTMLButtonElement.webidl b/components/script/dom/webidls/HTMLButtonElement.webidl index a5eaebef19e..a221f6669cf 100644 --- a/components/script/dom/webidls/HTMLButtonElement.webidl +++ b/components/script/dom/webidls/HTMLButtonElement.webidl @@ -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; }; diff --git a/components/script/dom/webidls/HTMLFieldSetElement.webidl b/components/script/dom/webidls/HTMLFieldSetElement.webidl index d0a6f14a77a..1257540adb3 100644 --- a/components/script/dom/webidls/HTMLFieldSetElement.webidl +++ b/components/script/dom/webidls/HTMLFieldSetElement.webidl @@ -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); }; diff --git a/components/script/dom/webidls/HTMLFormElement.webidl b/components/script/dom/webidls/HTMLFormElement.webidl index 744f6d84ca9..c7ea91a472c 100644 --- a/components/script/dom/webidls/HTMLFormElement.webidl +++ b/components/script/dom/webidls/HTMLFormElement.webidl @@ -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 diff --git a/components/script/dom/webidls/HTMLInputElement.webidl b/components/script/dom/webidls/HTMLInputElement.webidl index f86e924b415..6b8c557e83d 100644 --- a/components/script/dom/webidls/HTMLInputElement.webidl +++ b/components/script/dom/webidls/HTMLInputElement.webidl @@ -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; diff --git a/components/script/dom/webidls/HTMLObjectElement.webidl b/components/script/dom/webidls/HTMLObjectElement.webidl index c8779cd9921..9d60c4a4f92 100644 --- a/components/script/dom/webidls/HTMLObjectElement.webidl +++ b/components/script/dom/webidls/HTMLObjectElement.webidl @@ -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); diff --git a/components/script/dom/webidls/HTMLOutputElement.webidl b/components/script/dom/webidls/HTMLOutputElement.webidl index 28d853a0bbf..c44f2a7f44c 100644 --- a/components/script/dom/webidls/HTMLOutputElement.webidl +++ b/components/script/dom/webidls/HTMLOutputElement.webidl @@ -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; }; diff --git a/components/script/dom/webidls/HTMLSelectElement.webidl b/components/script/dom/webidls/HTMLSelectElement.webidl index 1fe48bcefb2..91258f88ee8 100644 --- a/components/script/dom/webidls/HTMLSelectElement.webidl +++ b/components/script/dom/webidls/HTMLSelectElement.webidl @@ -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; }; diff --git a/components/script/dom/webidls/HTMLTextAreaElement.webidl b/components/script/dom/webidls/HTMLTextAreaElement.webidl index b09200f1dc5..b726946bc48 100644 --- a/components/script/dom/webidls/HTMLTextAreaElement.webidl +++ b/components/script/dom/webidls/HTMLTextAreaElement.webidl @@ -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; |