mirror of
https://github.com/servo/servo.git
synced 2025-07-26 00:30:22 +01:00
Implement :valid :invalid pseudo classes (#26729)
Signed-off-by: Martin Robinson <mrobinson@igalia.com>
This commit is contained in:
parent
2b67392fd5
commit
5f2c6c09cd
30 changed files with 324 additions and 241 deletions
|
@ -3248,6 +3248,8 @@ impl<'a> SelectorsElement for DomRoot<Element> {
|
|||
NonTSPseudoClass::Enabled |
|
||||
NonTSPseudoClass::Disabled |
|
||||
NonTSPseudoClass::Checked |
|
||||
NonTSPseudoClass::Valid |
|
||||
NonTSPseudoClass::Invalid |
|
||||
NonTSPseudoClass::Indeterminate |
|
||||
NonTSPseudoClass::ReadWrite |
|
||||
NonTSPseudoClass::PlaceholderShown |
|
||||
|
|
|
@ -20,7 +20,7 @@ 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::{is_barred_by_datalist_ancestor, Validatable};
|
||||
use crate::dom::validitystate::ValidityState;
|
||||
use crate::dom::validitystate::{ValidationFlags, ValidityState};
|
||||
use crate::dom::virtualmethods::VirtualMethods;
|
||||
use dom_struct::dom_struct;
|
||||
use html5ever::{LocalName, Prefix};
|
||||
|
@ -242,6 +242,8 @@ impl VirtualMethods for HTMLButtonElement {
|
|||
},
|
||||
}
|
||||
el.update_sequentially_focusable_status();
|
||||
self.validity_state()
|
||||
.perform_validation_and_update(ValidationFlags::all());
|
||||
},
|
||||
&local_name!("type") => match mutation {
|
||||
AttributeMutation::Set(_) => {
|
||||
|
@ -251,6 +253,8 @@ impl VirtualMethods for HTMLButtonElement {
|
|||
_ => ButtonType::Submit,
|
||||
};
|
||||
self.button_type.set(value);
|
||||
self.validity_state()
|
||||
.perform_validation_and_update(ValidationFlags::all());
|
||||
},
|
||||
AttributeMutation::Removed => {
|
||||
self.button_type.set(ButtonType::Submit);
|
||||
|
@ -258,6 +262,8 @@ impl VirtualMethods for HTMLButtonElement {
|
|||
},
|
||||
&local_name!("form") => {
|
||||
self.form_attribute_mutated(mutation);
|
||||
self.validity_state()
|
||||
.perform_validation_and_update(ValidationFlags::empty());
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
|
|
|
@ -38,7 +38,7 @@ impl HTMLFieldSetElement {
|
|||
) -> HTMLFieldSetElement {
|
||||
HTMLFieldSetElement {
|
||||
htmlelement: HTMLElement::new_inherited_with_state(
|
||||
ElementState::IN_ENABLED_STATE,
|
||||
ElementState::IN_ENABLED_STATE | ElementState::IN_VALID_STATE,
|
||||
local_name,
|
||||
prefix,
|
||||
document,
|
||||
|
@ -63,6 +63,26 @@ impl HTMLFieldSetElement {
|
|||
proto,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn update_validity(&self) {
|
||||
let has_invalid_child = self
|
||||
.upcast::<Node>()
|
||||
.traverse_preorder(ShadowIncluding::No)
|
||||
.flat_map(DomRoot::downcast::<Element>)
|
||||
.any(|element| {
|
||||
if let Some(validatable) = element.as_maybe_validatable() {
|
||||
validatable.is_instance_validatable() &&
|
||||
!validatable.validity_state().invalid_flags().is_empty()
|
||||
} else {
|
||||
false
|
||||
}
|
||||
});
|
||||
|
||||
self.upcast::<Element>()
|
||||
.set_state(ElementState::IN_VALID_STATE, !has_invalid_child);
|
||||
self.upcast::<Element>()
|
||||
.set_state(ElementState::IN_INVALID_STATE, has_invalid_child);
|
||||
}
|
||||
}
|
||||
|
||||
impl HTMLFieldSetElementMethods for HTMLFieldSetElement {
|
||||
|
|
|
@ -72,6 +72,7 @@ use servo_rand::random;
|
|||
use std::borrow::ToOwned;
|
||||
use std::cell::Cell;
|
||||
use style::attr::AttrValue;
|
||||
use style::element_state::ElementState;
|
||||
use style::str::split_html_space_chars;
|
||||
|
||||
use crate::dom::bindings::codegen::UnionTypes::RadioNodeListOrElement;
|
||||
|
@ -104,7 +105,12 @@ impl HTMLFormElement {
|
|||
document: &Document,
|
||||
) -> HTMLFormElement {
|
||||
HTMLFormElement {
|
||||
htmlelement: HTMLElement::new_inherited(local_name, prefix, document),
|
||||
htmlelement: HTMLElement::new_inherited_with_state(
|
||||
ElementState::IN_VALID_STATE,
|
||||
local_name,
|
||||
prefix,
|
||||
document,
|
||||
),
|
||||
marked_for_reset: Cell::new(false),
|
||||
constructing_entry_list: Cell::new(false),
|
||||
elements: Default::default(),
|
||||
|
@ -674,6 +680,23 @@ impl HTMLFormElement {
|
|||
result
|
||||
}
|
||||
|
||||
pub fn update_validity(&self) {
|
||||
let controls = self.controls.borrow();
|
||||
|
||||
let is_any_invalid = controls
|
||||
.iter()
|
||||
.filter_map(|control| control.as_maybe_validatable())
|
||||
.any(|validatable| {
|
||||
validatable.is_instance_validatable() &&
|
||||
!validatable.validity_state().invalid_flags().is_empty()
|
||||
});
|
||||
|
||||
self.upcast::<Element>()
|
||||
.set_state(ElementState::IN_VALID_STATE, !is_any_invalid);
|
||||
self.upcast::<Element>()
|
||||
.set_state(ElementState::IN_INVALID_STATE, is_any_invalid);
|
||||
}
|
||||
|
||||
/// [Form submission](https://html.spec.whatwg.org/multipage/#concept-form-submit)
|
||||
pub fn submit(&self, submit_method_flag: SubmittedFrom, submitter: FormSubmitter) {
|
||||
// Step 1
|
||||
|
@ -1034,9 +1057,12 @@ impl HTMLFormElement {
|
|||
Some(v) => v,
|
||||
None => return None,
|
||||
};
|
||||
validatable
|
||||
.validity_state()
|
||||
.perform_validation_and_update(ValidationFlags::all());
|
||||
if !validatable.is_instance_validatable() {
|
||||
None
|
||||
} else if validatable.validate(ValidationFlags::all()).is_empty() {
|
||||
} else if validatable.validity_state().invalid_flags().is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(DomRoot::from_ref(el))
|
||||
|
@ -1287,14 +1313,17 @@ impl HTMLFormElement {
|
|||
}
|
||||
|
||||
fn add_control<T: ?Sized + FormControl>(&self, control: &T) {
|
||||
{
|
||||
let root = self.upcast::<Element>().root_element();
|
||||
let root = root.upcast::<Node>();
|
||||
|
||||
let mut controls = self.controls.borrow_mut();
|
||||
controls.insert_pre_order(control.to_element(), root);
|
||||
}
|
||||
self.update_validity();
|
||||
}
|
||||
|
||||
fn remove_control<T: ?Sized + FormControl>(&self, control: &T) {
|
||||
{
|
||||
let control = control.to_element();
|
||||
let mut controls = self.controls.borrow_mut();
|
||||
controls
|
||||
|
@ -1309,6 +1338,8 @@ impl HTMLFormElement {
|
|||
let mut past_names_map = self.past_names_map.borrow_mut();
|
||||
past_names_map.retain(|_k, v| v.0 != control);
|
||||
}
|
||||
self.update_validity();
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, JSTraceable, MallocSizeOf)]
|
||||
|
|
|
@ -1296,6 +1296,8 @@ impl HTMLInputElementMethods for HTMLInputElement {
|
|||
},
|
||||
}
|
||||
|
||||
self.validity_state()
|
||||
.perform_validation_and_update(ValidationFlags::all());
|
||||
self.upcast::<Node>().dirty(NodeDamage::OtherNodeDamage);
|
||||
Ok(())
|
||||
}
|
||||
|
@ -2340,7 +2342,6 @@ impl VirtualMethods for HTMLInputElement {
|
|||
}
|
||||
|
||||
let new_value_mode = self.value_mode();
|
||||
|
||||
match (&old_value_mode, old_idl_value.is_empty(), new_value_mode) {
|
||||
// Step 1
|
||||
(&ValueMode::Value, false, ValueMode::Default) |
|
||||
|
@ -2452,7 +2453,8 @@ impl VirtualMethods for HTMLInputElement {
|
|||
}
|
||||
self.update_placeholder_shown_state();
|
||||
},
|
||||
&local_name!("readonly") if self.input_type().is_textual() => {
|
||||
&local_name!("readonly") => {
|
||||
if self.input_type().is_textual() {
|
||||
let el = self.upcast::<Element>();
|
||||
match mutation {
|
||||
AttributeMutation::Set(_) => {
|
||||
|
@ -2462,12 +2464,16 @@ impl VirtualMethods for HTMLInputElement {
|
|||
el.set_read_write_state(!el.disabled_state());
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
&local_name!("form") => {
|
||||
self.form_attribute_mutated(mutation);
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
|
||||
self.validity_state()
|
||||
.perform_validation_and_update(ValidationFlags::all());
|
||||
}
|
||||
|
||||
fn parse_plain_attribute(&self, name: &LocalName, value: DOMString) -> AttrValue {
|
||||
|
@ -2494,6 +2500,9 @@ impl VirtualMethods for HTMLInputElement {
|
|||
}
|
||||
self.upcast::<Element>()
|
||||
.check_ancestors_disabled_state_for_form_control();
|
||||
|
||||
self.validity_state()
|
||||
.perform_validation_and_update(ValidationFlags::all());
|
||||
}
|
||||
|
||||
fn unbind_from_tree(&self, context: &UnbindContext) {
|
||||
|
@ -2509,6 +2518,9 @@ impl VirtualMethods for HTMLInputElement {
|
|||
} else {
|
||||
el.check_disabled_attribute();
|
||||
}
|
||||
|
||||
self.validity_state()
|
||||
.perform_validation_and_update(ValidationFlags::all());
|
||||
}
|
||||
|
||||
// This represents behavior for which the UIEvents spec and the
|
||||
|
@ -2608,6 +2620,9 @@ impl VirtualMethods for HTMLInputElement {
|
|||
event.mark_as_handled();
|
||||
}
|
||||
}
|
||||
|
||||
self.validity_state()
|
||||
.perform_validation_and_update(ValidationFlags::all());
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/#the-input-element%3Aconcept-node-clone-ext
|
||||
|
@ -2628,6 +2643,8 @@ impl VirtualMethods for HTMLInputElement {
|
|||
elem.textinput
|
||||
.borrow_mut()
|
||||
.set_content(self.textinput.borrow().get_content());
|
||||
elem.validity_state()
|
||||
.perform_validation_and_update(ValidationFlags::all());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -10,7 +10,10 @@ use crate::dom::document::Document;
|
|||
use crate::dom::element::{AttributeMutation, Element};
|
||||
use crate::dom::htmlelement::HTMLElement;
|
||||
use crate::dom::htmloptionelement::HTMLOptionElement;
|
||||
use crate::dom::node::Node;
|
||||
use crate::dom::htmlselectelement::HTMLSelectElement;
|
||||
use crate::dom::node::{BindContext, Node, ShadowIncluding, UnbindContext};
|
||||
use crate::dom::validation::Validatable;
|
||||
use crate::dom::validitystate::ValidationFlags;
|
||||
use crate::dom::virtualmethods::VirtualMethods;
|
||||
use dom_struct::dom_struct;
|
||||
use html5ever::{LocalName, Prefix};
|
||||
|
@ -53,6 +56,19 @@ impl HTMLOptGroupElement {
|
|||
proto,
|
||||
)
|
||||
}
|
||||
|
||||
fn update_select_validity(&self) {
|
||||
if let Some(select) = self
|
||||
.upcast::<Node>()
|
||||
.ancestors()
|
||||
.filter_map(DomRoot::downcast::<HTMLSelectElement>)
|
||||
.next()
|
||||
{
|
||||
select
|
||||
.validity_state()
|
||||
.perform_validation_and_update(ValidationFlags::all());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl HTMLOptGroupElementMethods for HTMLOptGroupElement {
|
||||
|
@ -104,4 +120,27 @@ impl VirtualMethods for HTMLOptGroupElement {
|
|||
_ => {},
|
||||
}
|
||||
}
|
||||
|
||||
fn bind_to_tree(&self, context: &BindContext) {
|
||||
if let Some(ref s) = self.super_type() {
|
||||
s.bind_to_tree(context);
|
||||
}
|
||||
|
||||
self.update_select_validity();
|
||||
}
|
||||
|
||||
fn unbind_from_tree(&self, context: &UnbindContext) {
|
||||
self.super_type().unwrap().unbind_from_tree(context);
|
||||
|
||||
if let Some(select) = context
|
||||
.parent
|
||||
.inclusive_ancestors(ShadowIncluding::No)
|
||||
.filter_map(DomRoot::downcast::<HTMLSelectElement>)
|
||||
.next()
|
||||
{
|
||||
select
|
||||
.validity_state()
|
||||
.perform_validation_and_update(ValidationFlags::all());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,6 +22,8 @@ use crate::dom::htmlscriptelement::HTMLScriptElement;
|
|||
use crate::dom::htmlselectelement::HTMLSelectElement;
|
||||
use crate::dom::node::{BindContext, Node, ShadowIncluding, UnbindContext};
|
||||
use crate::dom::text::Text;
|
||||
use crate::dom::validation::Validatable;
|
||||
use crate::dom::validitystate::ValidationFlags;
|
||||
use crate::dom::virtualmethods::VirtualMethods;
|
||||
use crate::dom::window::Window;
|
||||
use dom_struct::dom_struct;
|
||||
|
@ -108,6 +110,7 @@ impl HTMLOptionElement {
|
|||
|
||||
option.SetDefaultSelected(default_selected);
|
||||
option.set_selectedness(selected);
|
||||
option.update_select_validity();
|
||||
Ok(option)
|
||||
}
|
||||
|
||||
|
@ -167,6 +170,19 @@ impl HTMLOptionElement {
|
|||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn update_select_validity(&self) {
|
||||
if let Some(select) = self
|
||||
.upcast::<Node>()
|
||||
.ancestors()
|
||||
.filter_map(DomRoot::downcast::<HTMLSelectElement>)
|
||||
.next()
|
||||
{
|
||||
select
|
||||
.validity_state()
|
||||
.perform_validation_and_update(ValidationFlags::all());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME(ajeffrey): Provide a way of buffering DOMStrings other than using Strings
|
||||
|
@ -264,6 +280,7 @@ impl HTMLOptionElementMethods for HTMLOptionElement {
|
|||
self.dirtiness.set(true);
|
||||
self.selectedness.set(selected);
|
||||
self.pick_if_selected_and_reset();
|
||||
self.update_select_validity();
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/#dom-option-index
|
||||
|
@ -293,6 +310,7 @@ impl VirtualMethods for HTMLOptionElement {
|
|||
el.check_parent_disabled_state_for_option();
|
||||
},
|
||||
}
|
||||
self.update_select_validity();
|
||||
},
|
||||
&local_name!("selected") => {
|
||||
match mutation {
|
||||
|
@ -309,6 +327,7 @@ impl VirtualMethods for HTMLOptionElement {
|
|||
}
|
||||
},
|
||||
}
|
||||
self.update_select_validity();
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
|
@ -323,6 +342,7 @@ impl VirtualMethods for HTMLOptionElement {
|
|||
.check_parent_disabled_state_for_option();
|
||||
|
||||
self.pick_if_selected_and_reset();
|
||||
self.update_select_validity();
|
||||
}
|
||||
|
||||
fn unbind_from_tree(&self, context: &UnbindContext) {
|
||||
|
@ -334,6 +354,9 @@ impl VirtualMethods for HTMLOptionElement {
|
|||
.filter_map(DomRoot::downcast::<HTMLSelectElement>)
|
||||
.next()
|
||||
{
|
||||
select
|
||||
.validity_state()
|
||||
.perform_validation_and_update(ValidationFlags::all());
|
||||
select.ask_for_reset();
|
||||
}
|
||||
|
||||
|
|
|
@ -76,7 +76,7 @@ impl HTMLSelectElement {
|
|||
) -> HTMLSelectElement {
|
||||
HTMLSelectElement {
|
||||
htmlelement: HTMLElement::new_inherited_with_state(
|
||||
ElementState::IN_ENABLED_STATE,
|
||||
ElementState::IN_ENABLED_STATE | ElementState::IN_VALID_STATE,
|
||||
local_name,
|
||||
prefix,
|
||||
document,
|
||||
|
@ -347,6 +347,9 @@ impl HTMLSelectElementMethods for HTMLSelectElement {
|
|||
for opt in opt_iter {
|
||||
opt.set_selectedness(false);
|
||||
}
|
||||
|
||||
self.validity_state()
|
||||
.perform_validation_and_update(ValidationFlags::VALUE_MISSING);
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/#dom-select-selectedindex
|
||||
|
@ -414,6 +417,10 @@ impl VirtualMethods for HTMLSelectElement {
|
|||
fn attribute_mutated(&self, attr: &Attr, mutation: AttributeMutation) {
|
||||
self.super_type().unwrap().attribute_mutated(attr, mutation);
|
||||
match attr.local_name() {
|
||||
&local_name!("required") => {
|
||||
self.validity_state()
|
||||
.perform_validation_and_update(ValidationFlags::VALUE_MISSING);
|
||||
},
|
||||
&local_name!("disabled") => {
|
||||
let el = self.upcast::<Element>();
|
||||
match mutation {
|
||||
|
@ -427,6 +434,9 @@ impl VirtualMethods for HTMLSelectElement {
|
|||
el.check_ancestors_disabled_state_for_form_control();
|
||||
},
|
||||
}
|
||||
|
||||
self.validity_state()
|
||||
.perform_validation_and_update(ValidationFlags::VALUE_MISSING);
|
||||
},
|
||||
&local_name!("form") => {
|
||||
self.form_attribute_mutated(mutation);
|
||||
|
|
|
@ -323,6 +323,7 @@ impl HTMLTextAreaElementMethods for HTMLTextAreaElement {
|
|||
|
||||
// https://html.spec.whatwg.org/multipage/#dom-textarea-value
|
||||
fn SetValue(&self, value: DOMString) {
|
||||
{
|
||||
let mut textinput = self.textinput.borrow_mut();
|
||||
|
||||
// Step 1
|
||||
|
@ -338,7 +339,10 @@ impl HTMLTextAreaElementMethods for HTMLTextAreaElement {
|
|||
// Step 4
|
||||
textinput.clear_selection_to_limit(Direction::Forward);
|
||||
}
|
||||
}
|
||||
|
||||
self.validity_state()
|
||||
.perform_validation_and_update(ValidationFlags::all());
|
||||
self.upcast::<Node>().dirty(NodeDamage::OtherNodeDamage);
|
||||
}
|
||||
|
||||
|
@ -533,6 +537,9 @@ impl VirtualMethods for HTMLTextAreaElement {
|
|||
},
|
||||
_ => {},
|
||||
}
|
||||
|
||||
self.validity_state()
|
||||
.perform_validation_and_update(ValidationFlags::all());
|
||||
}
|
||||
|
||||
fn bind_to_tree(&self, context: &BindContext) {
|
||||
|
@ -542,6 +549,9 @@ impl VirtualMethods for HTMLTextAreaElement {
|
|||
|
||||
self.upcast::<Element>()
|
||||
.check_ancestors_disabled_state_for_form_control();
|
||||
|
||||
self.validity_state()
|
||||
.perform_validation_and_update(ValidationFlags::all());
|
||||
}
|
||||
|
||||
fn parse_plain_attribute(&self, name: &LocalName, value: DOMString) -> AttrValue {
|
||||
|
@ -574,6 +584,9 @@ impl VirtualMethods for HTMLTextAreaElement {
|
|||
} else {
|
||||
el.check_disabled_attribute();
|
||||
}
|
||||
|
||||
self.validity_state()
|
||||
.perform_validation_and_update(ValidationFlags::all());
|
||||
}
|
||||
|
||||
// The cloning steps for textarea elements must propagate the raw value
|
||||
|
@ -589,9 +602,13 @@ impl VirtualMethods for HTMLTextAreaElement {
|
|||
}
|
||||
let el = copy.downcast::<HTMLTextAreaElement>().unwrap();
|
||||
el.value_dirty.set(self.value_dirty.get());
|
||||
{
|
||||
let mut textinput = el.textinput.borrow_mut();
|
||||
textinput.set_content(self.textinput.borrow().get_content());
|
||||
}
|
||||
el.validity_state()
|
||||
.perform_validation_and_update(ValidationFlags::all());
|
||||
}
|
||||
|
||||
fn children_changed(&self, mutation: &ChildrenMutation) {
|
||||
if let Some(ref s) = self.super_type() {
|
||||
|
@ -661,6 +678,9 @@ impl VirtualMethods for HTMLTextAreaElement {
|
|||
event.mark_as_handled();
|
||||
}
|
||||
}
|
||||
|
||||
self.validity_state()
|
||||
.perform_validation_and_update(ValidationFlags::all());
|
||||
}
|
||||
|
||||
fn pop(&self) {
|
||||
|
|
|
@ -28,23 +28,9 @@ pub trait Validatable {
|
|||
ValidationFlags::empty()
|
||||
}
|
||||
|
||||
// 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() {
|
||||
if self.is_instance_validatable() && !self.validity_state().invalid_flags().is_empty() {
|
||||
self.as_element()
|
||||
.upcast::<EventTarget>()
|
||||
.fire_cancelable_event(atom!("invalid"));
|
||||
|
@ -61,7 +47,7 @@ pub trait Validatable {
|
|||
return true;
|
||||
}
|
||||
|
||||
let flags = self.validate(ValidationFlags::all());
|
||||
let flags = self.validity_state().invalid_flags();
|
||||
if flags.is_empty() {
|
||||
return true;
|
||||
}
|
||||
|
@ -90,7 +76,7 @@ pub trait Validatable {
|
|||
// 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());
|
||||
let flags = self.validity_state().invalid_flags();
|
||||
validation_message_for_flags(&self.validity_state(), flags)
|
||||
} else {
|
||||
DOMString::new()
|
||||
|
|
|
@ -4,17 +4,24 @@
|
|||
|
||||
use crate::dom::bindings::cell::{DomRefCell, Ref};
|
||||
use crate::dom::bindings::codegen::Bindings::ValidityStateBinding::ValidityStateMethods;
|
||||
use crate::dom::bindings::inheritance::Castable;
|
||||
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::htmlfieldsetelement::HTMLFieldSetElement;
|
||||
use crate::dom::htmlformelement::FormControlElementHelpers;
|
||||
use crate::dom::node::Node;
|
||||
use crate::dom::window::Window;
|
||||
use dom_struct::dom_struct;
|
||||
use itertools::Itertools;
|
||||
use std::cell::Cell;
|
||||
use std::fmt;
|
||||
use style::element_state::ElementState;
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/#validity-states
|
||||
bitflags! {
|
||||
#[derive(JSTraceable, MallocSizeOf)]
|
||||
pub struct ValidationFlags: u32 {
|
||||
const VALUE_MISSING = 0b0000000001;
|
||||
const TYPE_MISMATCH = 0b0000000010;
|
||||
|
@ -64,6 +71,7 @@ pub struct ValidityState {
|
|||
reflector_: Reflector,
|
||||
element: Dom<Element>,
|
||||
custom_error_message: DomRefCell<DOMString>,
|
||||
invalid_flags: Cell<ValidationFlags>,
|
||||
}
|
||||
|
||||
impl ValidityState {
|
||||
|
@ -72,6 +80,7 @@ impl ValidityState {
|
|||
reflector_: Reflector::new(),
|
||||
element: Dom::from_ref(element),
|
||||
custom_error_message: DomRefCell::new(DOMString::new()),
|
||||
invalid_flags: Cell::new(ValidationFlags::empty()),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -87,84 +96,130 @@ impl ValidityState {
|
|||
// 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;
|
||||
self.perform_validation_and_update(ValidationFlags::CUSTOM_ERROR);
|
||||
}
|
||||
|
||||
/// Given a set of [ValidationFlags], recalculate their value by performing
|
||||
/// validation on this [ValidityState]'s associated element. Additionally,
|
||||
/// if [ValidationFlags::CUSTOM_ERROR] is in `update_flags` and a custom
|
||||
/// error has been set on this [ValidityState], the state will be updated
|
||||
/// to reflect the existance of a custom error.
|
||||
pub fn perform_validation_and_update(&self, update_flags: ValidationFlags) {
|
||||
let mut invalid_flags = self.invalid_flags.get();
|
||||
invalid_flags.remove(update_flags);
|
||||
|
||||
if let Some(validatable) = self.element.as_maybe_validatable() {
|
||||
let new_flags = validatable.perform_validation(update_flags);
|
||||
invalid_flags.insert(new_flags);
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/#suffering-from-a-custom-error
|
||||
if update_flags.contains(ValidationFlags::CUSTOM_ERROR) &&
|
||||
!self.custom_error_message().is_empty()
|
||||
{
|
||||
invalid_flags.insert(ValidationFlags::CUSTOM_ERROR);
|
||||
}
|
||||
|
||||
self.invalid_flags.set(invalid_flags);
|
||||
self.update_pseudo_classes();
|
||||
}
|
||||
|
||||
pub fn invalid_flags(&self) -> ValidationFlags {
|
||||
self.invalid_flags.get()
|
||||
}
|
||||
|
||||
fn update_pseudo_classes(&self) {
|
||||
if let Some(validatable) = self.element.as_maybe_validatable() {
|
||||
if validatable.is_instance_validatable() {
|
||||
let is_valid = self.invalid_flags.get().is_empty();
|
||||
self.element
|
||||
.set_state(ElementState::IN_VALID_STATE, is_valid);
|
||||
self.element
|
||||
.set_state(ElementState::IN_INVALID_STATE, !is_valid);
|
||||
} else {
|
||||
self.element.set_state(ElementState::IN_VALID_STATE, false);
|
||||
self.element
|
||||
.set_state(ElementState::IN_INVALID_STATE, false);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(form_control) = self.element.as_maybe_form_control() {
|
||||
if let Some(form_owner) = form_control.form_owner() {
|
||||
form_owner.update_validity();
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(fieldset) = self
|
||||
.element
|
||||
.upcast::<Node>()
|
||||
.ancestors()
|
||||
.filter_map(DomRoot::downcast::<HTMLFieldSetElement>)
|
||||
.next()
|
||||
{
|
||||
fieldset.update_validity();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ValidityStateMethods for ValidityState {
|
||||
// https://html.spec.whatwg.org/multipage/#dom-validitystate-valuemissing
|
||||
fn ValueMissing(&self) -> bool {
|
||||
self.element.as_maybe_validatable().map_or(false, |e| {
|
||||
!e.validate(ValidationFlags::VALUE_MISSING).is_empty()
|
||||
})
|
||||
self.invalid_flags()
|
||||
.contains(ValidationFlags::VALUE_MISSING)
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/#dom-validitystate-typemismatch
|
||||
fn TypeMismatch(&self) -> bool {
|
||||
self.element.as_maybe_validatable().map_or(false, |e| {
|
||||
!e.validate(ValidationFlags::TYPE_MISMATCH).is_empty()
|
||||
})
|
||||
self.invalid_flags()
|
||||
.contains(ValidationFlags::TYPE_MISMATCH)
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/#dom-validitystate-patternmismatch
|
||||
fn PatternMismatch(&self) -> bool {
|
||||
self.element.as_maybe_validatable().map_or(false, |e| {
|
||||
!e.validate(ValidationFlags::PATTERN_MISMATCH).is_empty()
|
||||
})
|
||||
self.invalid_flags()
|
||||
.contains(ValidationFlags::PATTERN_MISMATCH)
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/#dom-validitystate-toolong
|
||||
fn TooLong(&self) -> bool {
|
||||
self.element
|
||||
.as_maybe_validatable()
|
||||
.map_or(false, |e| !e.validate(ValidationFlags::TOO_LONG).is_empty())
|
||||
self.invalid_flags().contains(ValidationFlags::TOO_LONG)
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/#dom-validitystate-tooshort
|
||||
fn TooShort(&self) -> bool {
|
||||
self.element.as_maybe_validatable().map_or(false, |e| {
|
||||
!e.validate(ValidationFlags::TOO_SHORT).is_empty()
|
||||
})
|
||||
self.invalid_flags().contains(ValidationFlags::TOO_SHORT)
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/#dom-validitystate-rangeunderflow
|
||||
fn RangeUnderflow(&self) -> bool {
|
||||
self.element.as_maybe_validatable().map_or(false, |e| {
|
||||
!e.validate(ValidationFlags::RANGE_UNDERFLOW).is_empty()
|
||||
})
|
||||
self.invalid_flags()
|
||||
.contains(ValidationFlags::RANGE_UNDERFLOW)
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/#dom-validitystate-rangeoverflow
|
||||
fn RangeOverflow(&self) -> bool {
|
||||
self.element.as_maybe_validatable().map_or(false, |e| {
|
||||
!e.validate(ValidationFlags::RANGE_OVERFLOW).is_empty()
|
||||
})
|
||||
self.invalid_flags()
|
||||
.contains(ValidationFlags::RANGE_OVERFLOW)
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/#dom-validitystate-stepmismatch
|
||||
fn StepMismatch(&self) -> bool {
|
||||
self.element.as_maybe_validatable().map_or(false, |e| {
|
||||
!e.validate(ValidationFlags::STEP_MISMATCH).is_empty()
|
||||
})
|
||||
self.invalid_flags()
|
||||
.contains(ValidationFlags::STEP_MISMATCH)
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/#dom-validitystate-badinput
|
||||
fn BadInput(&self) -> bool {
|
||||
self.element.as_maybe_validatable().map_or(false, |e| {
|
||||
!e.validate(ValidationFlags::BAD_INPUT).is_empty()
|
||||
})
|
||||
self.invalid_flags().contains(ValidationFlags::BAD_INPUT)
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/#dom-validitystate-customerror
|
||||
fn CustomError(&self) -> bool {
|
||||
self.element.as_maybe_validatable().map_or(false, |e| {
|
||||
!e.validate(ValidationFlags::CUSTOM_ERROR).is_empty()
|
||||
})
|
||||
self.invalid_flags().contains(ValidationFlags::CUSTOM_ERROR)
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/#dom-validitystate-valid
|
||||
fn Valid(&self) -> bool {
|
||||
self.element
|
||||
.as_maybe_validatable()
|
||||
.map_or(true, |e| e.validate(ValidationFlags::all()).is_empty())
|
||||
self.invalid_flags().is_empty()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -611,6 +611,8 @@ impl<'dom, LayoutDataType: LayoutDataTrait> ::selectors::Element
|
|||
NonTSPseudoClass::Enabled |
|
||||
NonTSPseudoClass::Disabled |
|
||||
NonTSPseudoClass::Checked |
|
||||
NonTSPseudoClass::Valid |
|
||||
NonTSPseudoClass::Invalid |
|
||||
NonTSPseudoClass::Indeterminate |
|
||||
NonTSPseudoClass::ReadWrite |
|
||||
NonTSPseudoClass::PlaceholderShown |
|
||||
|
|
|
@ -281,6 +281,8 @@ pub enum NonTSPseudoClass {
|
|||
Active,
|
||||
AnyLink,
|
||||
Checked,
|
||||
Valid,
|
||||
Invalid,
|
||||
Defined,
|
||||
Disabled,
|
||||
Enabled,
|
||||
|
@ -338,6 +340,8 @@ impl ToCss for NonTSPseudoClass {
|
|||
Active => ":active",
|
||||
AnyLink => ":any-link",
|
||||
Checked => ":checked",
|
||||
Valid => ":valid",
|
||||
Invalid => ":invalid",
|
||||
Defined => ":defined",
|
||||
Disabled => ":disabled",
|
||||
Enabled => ":enabled",
|
||||
|
@ -371,6 +375,8 @@ impl NonTSPseudoClass {
|
|||
Enabled => ElementState::IN_ENABLED_STATE,
|
||||
Disabled => ElementState::IN_DISABLED_STATE,
|
||||
Checked => ElementState::IN_CHECKED_STATE,
|
||||
Valid => ElementState::IN_VALID_STATE,
|
||||
Invalid => ElementState::IN_INVALID_STATE,
|
||||
Indeterminate => ElementState::IN_INDETERMINATE_STATE,
|
||||
ReadOnly | ReadWrite => ElementState::IN_READWRITE_STATE,
|
||||
PlaceholderShown => ElementState::IN_PLACEHOLDER_SHOWN_STATE,
|
||||
|
@ -425,6 +431,8 @@ impl<'a, 'i> ::selectors::Parser<'i> for SelectorParser<'a> {
|
|||
"active" => Active,
|
||||
"any-link" => AnyLink,
|
||||
"checked" => Checked,
|
||||
"valid" => Valid,
|
||||
"invalid" => Invalid,
|
||||
"defined" => Defined,
|
||||
"disabled" => Disabled,
|
||||
"enabled" => Enabled,
|
||||
|
|
|
@ -1,9 +1,4 @@
|
|||
[Element-closest.html]
|
||||
type: testharness
|
||||
[Element.closest with context node 'test11' and selector ':invalid']
|
||||
bug: https://github.com/servo/servo/issues/10781
|
||||
expected: FAIL
|
||||
|
||||
[Element.closest with context node 'test4' and selector ':has(> :scope)']
|
||||
expected: FAIL
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
[infinite_backtracking.html]
|
||||
expected: TIMEOUT
|
||||
[Infinite backtracking pattern terminates]
|
||||
expected: FAIL
|
||||
|
||||
|
|
|
@ -1,4 +0,0 @@
|
|||
[input-number-validity-dynamic-value-no-change.html]
|
||||
[number input number validation is updated correctly after value attribute change which doesn't change input value]
|
||||
expected: FAIL
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
[input-pattern-dynamic-value.html]
|
||||
[input validation is updated after pattern attribute change]
|
||||
expected: FAIL
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
[radio-valueMissing.html]
|
||||
[One of the radios is required and another one is checked]
|
||||
expected: FAIL
|
|
@ -1,4 +0,0 @@
|
|||
[form-submit-iframe-then-location-navigate.html]
|
||||
expected: TIMEOUT
|
||||
[Verifies that location navigations take precedence when following form submissions.]
|
||||
expected: TIMEOUT
|
|
@ -5,9 +5,6 @@
|
|||
[text/plain: Basic File test (normal form)]
|
||||
expected: FAIL
|
||||
|
||||
[text/plain: 0x00 in name (normal form)]
|
||||
expected: FAIL
|
||||
|
||||
[text/plain: 0x00 in value (normal form)]
|
||||
expected: FAIL
|
||||
|
||||
|
|
|
@ -1,6 +0,0 @@
|
|||
[form-requestsubmit.html]
|
||||
[The value of the submitter should be appended, and form* attributes of the submitter should be handled.]
|
||||
expected: FAIL
|
||||
|
||||
[requestSubmit() should trigger interactive form validation]
|
||||
expected: FAIL
|
|
@ -2,18 +2,6 @@
|
|||
[pattern attribute support on input element]
|
||||
expected: FAIL
|
||||
|
||||
[basic <input pattern> support]
|
||||
expected: FAIL
|
||||
|
||||
[<input pattern> is Unicode code point-aware]
|
||||
expected: FAIL
|
||||
|
||||
[<input pattern> supports Unicode property escape syntax]
|
||||
expected: FAIL
|
||||
|
||||
[<input pattern> supports Unicode property escape syntax for properties of strings]
|
||||
expected: FAIL
|
||||
|
||||
[<input pattern> supports set difference syntax]
|
||||
expected: FAIL
|
||||
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
[radio.html]
|
||||
[Radio buttons in an orphan tree should make a group]
|
||||
expected: FAIL
|
|
@ -1,3 +0,0 @@
|
|||
[output-validity.html]
|
||||
[:valid and :invalid pseudo-class on output element]
|
||||
expected: FAIL
|
|
@ -0,0 +1,3 @@
|
|||
[select-validity.html]
|
||||
[Remove and add back the placeholder label option]
|
||||
expected: FAIL
|
|
@ -1,6 +0,0 @@
|
|||
[valid-invalid-fieldset-disconnected.html]
|
||||
[<input> element becomes invalid inside disconnected <fieldset>]
|
||||
expected: FAIL
|
||||
|
||||
[<select> element becomes valid inside disconnected <fieldset>]
|
||||
expected: FAIL
|
|
@ -1,87 +0,0 @@
|
|||
[valid-invalid.html]
|
||||
type: testharness
|
||||
bug: https://github.com/servo/servo/issues/10781
|
||||
[':valid' matches elements that satisfy their constraints]
|
||||
expected: FAIL
|
||||
|
||||
[':valid' matches form elements that are not the form owner of any elements that themselves are candidates for constraint validation but do not satisfy their constraints]
|
||||
expected: FAIL
|
||||
|
||||
[':valid' matches fieldset elements that have no descendant elements that themselves are candidates for constraint validation but do not satisfy their constraints]
|
||||
expected: FAIL
|
||||
|
||||
[':valid' matches elements that satisfy their pattern constraints]
|
||||
expected: FAIL
|
||||
|
||||
[':valid' matches elements that satisfy their number constraints]
|
||||
expected: FAIL
|
||||
|
||||
[':invalid' matches elements that do not satisfy their simple text constraints]
|
||||
expected: FAIL
|
||||
|
||||
[':invalid' matches form elements that are the form owner of one or more elements that themselves are candidates for constraint validation but do not satisfy their constraints]
|
||||
expected: FAIL
|
||||
|
||||
[':invalid' matches fieldset elements that have of one or more descendant elements that themselves are candidates for constraint validation but do not satisfy their constraints]
|
||||
expected: FAIL
|
||||
|
||||
[':invalid' matches elements that do not satisfy their pattern constraints]
|
||||
expected: FAIL
|
||||
|
||||
[':invalid' matches elements that do not satisfy their number constraints]
|
||||
expected: FAIL
|
||||
|
||||
[':valid' matches new elements that satisfy their constraints]
|
||||
expected: FAIL
|
||||
|
||||
[':invalid' doesn't match new elements that satisfy their constraints]
|
||||
expected: FAIL
|
||||
|
||||
[':valid' doesn't match new elements that do not satisfy their constraints]
|
||||
expected: FAIL
|
||||
|
||||
[':invalid' matches new elements that do not satisfy their constraints]
|
||||
expected: FAIL
|
||||
|
||||
[empty form correctly styled on page-load]
|
||||
expected: FAIL
|
||||
|
||||
[valid form correctly styled on page-load]
|
||||
expected: FAIL
|
||||
|
||||
[invalid form correctly styled on page-load]
|
||||
expected: FAIL
|
||||
|
||||
[programmatically adding valid to empty form results in correct style]
|
||||
expected: FAIL
|
||||
|
||||
[programmatically adding invalid to empty form results in correct style]
|
||||
expected: FAIL
|
||||
|
||||
[programmatically-invalidated form correctly styled]
|
||||
expected: FAIL
|
||||
|
||||
[programmatically-validated form correctly styled]
|
||||
expected: FAIL
|
||||
|
||||
[empty fieldset correctly styled on page-load]
|
||||
expected: FAIL
|
||||
|
||||
[valid fieldset correctly styled on page-load]
|
||||
expected: FAIL
|
||||
|
||||
[invalid fieldset correctly styled on page-load]
|
||||
expected: FAIL
|
||||
|
||||
[programmatically adding valid to empty fieldset results in correct style]
|
||||
expected: FAIL
|
||||
|
||||
[programmatically adding invalid to empty fieldset results in correct style]
|
||||
expected: FAIL
|
||||
|
||||
[programmatically-invalidated fieldset correctly styled]
|
||||
expected: FAIL
|
||||
|
||||
[programmatically-validated fieldset correctly styled]
|
||||
expected: FAIL
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
[input-pattern-dynamic-value.html]
|
||||
[input validation is updated after pattern attribute change]
|
||||
expected: FAIL
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
[form-double-submit-2.html]
|
||||
[preventDefault should allow onclick submit() to succeed]
|
||||
expected: FAIL
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
[form-double-submit.html]
|
||||
[default submit action should supersede onclick submit()]
|
||||
expected: FAIL
|
||||
|
Loading…
Add table
Add a link
Reference in a new issue