mirror of
https://github.com/servo/servo.git
synced 2025-08-03 12:40:06 +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::Enabled |
|
||||||
NonTSPseudoClass::Disabled |
|
NonTSPseudoClass::Disabled |
|
||||||
NonTSPseudoClass::Checked |
|
NonTSPseudoClass::Checked |
|
||||||
|
NonTSPseudoClass::Valid |
|
||||||
|
NonTSPseudoClass::Invalid |
|
||||||
NonTSPseudoClass::Indeterminate |
|
NonTSPseudoClass::Indeterminate |
|
||||||
NonTSPseudoClass::ReadWrite |
|
NonTSPseudoClass::ReadWrite |
|
||||||
NonTSPseudoClass::PlaceholderShown |
|
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::node::{window_from_node, BindContext, Node, UnbindContext};
|
||||||
use crate::dom::nodelist::NodeList;
|
use crate::dom::nodelist::NodeList;
|
||||||
use crate::dom::validation::{is_barred_by_datalist_ancestor, Validatable};
|
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 crate::dom::virtualmethods::VirtualMethods;
|
||||||
use dom_struct::dom_struct;
|
use dom_struct::dom_struct;
|
||||||
use html5ever::{LocalName, Prefix};
|
use html5ever::{LocalName, Prefix};
|
||||||
|
@ -242,6 +242,8 @@ impl VirtualMethods for HTMLButtonElement {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
el.update_sequentially_focusable_status();
|
el.update_sequentially_focusable_status();
|
||||||
|
self.validity_state()
|
||||||
|
.perform_validation_and_update(ValidationFlags::all());
|
||||||
},
|
},
|
||||||
&local_name!("type") => match mutation {
|
&local_name!("type") => match mutation {
|
||||||
AttributeMutation::Set(_) => {
|
AttributeMutation::Set(_) => {
|
||||||
|
@ -251,6 +253,8 @@ impl VirtualMethods for HTMLButtonElement {
|
||||||
_ => ButtonType::Submit,
|
_ => ButtonType::Submit,
|
||||||
};
|
};
|
||||||
self.button_type.set(value);
|
self.button_type.set(value);
|
||||||
|
self.validity_state()
|
||||||
|
.perform_validation_and_update(ValidationFlags::all());
|
||||||
},
|
},
|
||||||
AttributeMutation::Removed => {
|
AttributeMutation::Removed => {
|
||||||
self.button_type.set(ButtonType::Submit);
|
self.button_type.set(ButtonType::Submit);
|
||||||
|
@ -258,6 +262,8 @@ impl VirtualMethods for HTMLButtonElement {
|
||||||
},
|
},
|
||||||
&local_name!("form") => {
|
&local_name!("form") => {
|
||||||
self.form_attribute_mutated(mutation);
|
self.form_attribute_mutated(mutation);
|
||||||
|
self.validity_state()
|
||||||
|
.perform_validation_and_update(ValidationFlags::empty());
|
||||||
},
|
},
|
||||||
_ => {},
|
_ => {},
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,7 +38,7 @@ impl HTMLFieldSetElement {
|
||||||
) -> HTMLFieldSetElement {
|
) -> HTMLFieldSetElement {
|
||||||
HTMLFieldSetElement {
|
HTMLFieldSetElement {
|
||||||
htmlelement: HTMLElement::new_inherited_with_state(
|
htmlelement: HTMLElement::new_inherited_with_state(
|
||||||
ElementState::IN_ENABLED_STATE,
|
ElementState::IN_ENABLED_STATE | ElementState::IN_VALID_STATE,
|
||||||
local_name,
|
local_name,
|
||||||
prefix,
|
prefix,
|
||||||
document,
|
document,
|
||||||
|
@ -63,6 +63,26 @@ impl HTMLFieldSetElement {
|
||||||
proto,
|
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 {
|
impl HTMLFieldSetElementMethods for HTMLFieldSetElement {
|
||||||
|
|
|
@ -72,6 +72,7 @@ use servo_rand::random;
|
||||||
use std::borrow::ToOwned;
|
use std::borrow::ToOwned;
|
||||||
use std::cell::Cell;
|
use std::cell::Cell;
|
||||||
use style::attr::AttrValue;
|
use style::attr::AttrValue;
|
||||||
|
use style::element_state::ElementState;
|
||||||
use style::str::split_html_space_chars;
|
use style::str::split_html_space_chars;
|
||||||
|
|
||||||
use crate::dom::bindings::codegen::UnionTypes::RadioNodeListOrElement;
|
use crate::dom::bindings::codegen::UnionTypes::RadioNodeListOrElement;
|
||||||
|
@ -104,7 +105,12 @@ impl HTMLFormElement {
|
||||||
document: &Document,
|
document: &Document,
|
||||||
) -> HTMLFormElement {
|
) -> HTMLFormElement {
|
||||||
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),
|
marked_for_reset: Cell::new(false),
|
||||||
constructing_entry_list: Cell::new(false),
|
constructing_entry_list: Cell::new(false),
|
||||||
elements: Default::default(),
|
elements: Default::default(),
|
||||||
|
@ -674,6 +680,23 @@ impl HTMLFormElement {
|
||||||
result
|
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)
|
/// [Form submission](https://html.spec.whatwg.org/multipage/#concept-form-submit)
|
||||||
pub fn submit(&self, submit_method_flag: SubmittedFrom, submitter: FormSubmitter) {
|
pub fn submit(&self, submit_method_flag: SubmittedFrom, submitter: FormSubmitter) {
|
||||||
// Step 1
|
// Step 1
|
||||||
|
@ -1034,9 +1057,12 @@ impl HTMLFormElement {
|
||||||
Some(v) => v,
|
Some(v) => v,
|
||||||
None => return None,
|
None => return None,
|
||||||
};
|
};
|
||||||
|
validatable
|
||||||
|
.validity_state()
|
||||||
|
.perform_validation_and_update(ValidationFlags::all());
|
||||||
if !validatable.is_instance_validatable() {
|
if !validatable.is_instance_validatable() {
|
||||||
None
|
None
|
||||||
} else if validatable.validate(ValidationFlags::all()).is_empty() {
|
} else if validatable.validity_state().invalid_flags().is_empty() {
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
Some(DomRoot::from_ref(el))
|
Some(DomRoot::from_ref(el))
|
||||||
|
@ -1287,27 +1313,32 @@ impl HTMLFormElement {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_control<T: ?Sized + FormControl>(&self, control: &T) {
|
fn add_control<T: ?Sized + FormControl>(&self, control: &T) {
|
||||||
let root = self.upcast::<Element>().root_element();
|
{
|
||||||
let root = root.upcast::<Node>();
|
let root = self.upcast::<Element>().root_element();
|
||||||
|
let root = root.upcast::<Node>();
|
||||||
let mut controls = self.controls.borrow_mut();
|
let mut controls = self.controls.borrow_mut();
|
||||||
controls.insert_pre_order(control.to_element(), root);
|
controls.insert_pre_order(control.to_element(), root);
|
||||||
|
}
|
||||||
|
self.update_validity();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn remove_control<T: ?Sized + FormControl>(&self, control: &T) {
|
fn remove_control<T: ?Sized + FormControl>(&self, control: &T) {
|
||||||
let control = control.to_element();
|
{
|
||||||
let mut controls = self.controls.borrow_mut();
|
let control = control.to_element();
|
||||||
controls
|
let mut controls = self.controls.borrow_mut();
|
||||||
.iter()
|
controls
|
||||||
.position(|c| &**c == control)
|
.iter()
|
||||||
.map(|idx| controls.remove(idx));
|
.position(|c| &**c == control)
|
||||||
|
.map(|idx| controls.remove(idx));
|
||||||
|
|
||||||
// https://html.spec.whatwg.org/multipage#forms.html#the-form-element:past-names-map-5
|
// https://html.spec.whatwg.org/multipage#forms.html#the-form-element:past-names-map-5
|
||||||
// "If an element listed in a form element's past names map
|
// "If an element listed in a form element's past names map
|
||||||
// changes form owner, then its entries must be removed
|
// changes form owner, then its entries must be removed
|
||||||
// from that map."
|
// from that map."
|
||||||
let mut past_names_map = self.past_names_map.borrow_mut();
|
let mut past_names_map = self.past_names_map.borrow_mut();
|
||||||
past_names_map.retain(|_k, v| v.0 != control);
|
past_names_map.retain(|_k, v| v.0 != control);
|
||||||
|
}
|
||||||
|
self.update_validity();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1296,6 +1296,8 @@ impl HTMLInputElementMethods for HTMLInputElement {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.validity_state()
|
||||||
|
.perform_validation_and_update(ValidationFlags::all());
|
||||||
self.upcast::<Node>().dirty(NodeDamage::OtherNodeDamage);
|
self.upcast::<Node>().dirty(NodeDamage::OtherNodeDamage);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -2340,7 +2342,6 @@ impl VirtualMethods for HTMLInputElement {
|
||||||
}
|
}
|
||||||
|
|
||||||
let new_value_mode = self.value_mode();
|
let new_value_mode = self.value_mode();
|
||||||
|
|
||||||
match (&old_value_mode, old_idl_value.is_empty(), new_value_mode) {
|
match (&old_value_mode, old_idl_value.is_empty(), new_value_mode) {
|
||||||
// Step 1
|
// Step 1
|
||||||
(&ValueMode::Value, false, ValueMode::Default) |
|
(&ValueMode::Value, false, ValueMode::Default) |
|
||||||
|
@ -2452,15 +2453,17 @@ impl VirtualMethods for HTMLInputElement {
|
||||||
}
|
}
|
||||||
self.update_placeholder_shown_state();
|
self.update_placeholder_shown_state();
|
||||||
},
|
},
|
||||||
&local_name!("readonly") if self.input_type().is_textual() => {
|
&local_name!("readonly") => {
|
||||||
let el = self.upcast::<Element>();
|
if self.input_type().is_textual() {
|
||||||
match mutation {
|
let el = self.upcast::<Element>();
|
||||||
AttributeMutation::Set(_) => {
|
match mutation {
|
||||||
el.set_read_write_state(false);
|
AttributeMutation::Set(_) => {
|
||||||
},
|
el.set_read_write_state(false);
|
||||||
AttributeMutation::Removed => {
|
},
|
||||||
el.set_read_write_state(!el.disabled_state());
|
AttributeMutation::Removed => {
|
||||||
},
|
el.set_read_write_state(!el.disabled_state());
|
||||||
|
},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
&local_name!("form") => {
|
&local_name!("form") => {
|
||||||
|
@ -2468,6 +2471,9 @@ impl VirtualMethods for HTMLInputElement {
|
||||||
},
|
},
|
||||||
_ => {},
|
_ => {},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.validity_state()
|
||||||
|
.perform_validation_and_update(ValidationFlags::all());
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_plain_attribute(&self, name: &LocalName, value: DOMString) -> AttrValue {
|
fn parse_plain_attribute(&self, name: &LocalName, value: DOMString) -> AttrValue {
|
||||||
|
@ -2494,6 +2500,9 @@ impl VirtualMethods for HTMLInputElement {
|
||||||
}
|
}
|
||||||
self.upcast::<Element>()
|
self.upcast::<Element>()
|
||||||
.check_ancestors_disabled_state_for_form_control();
|
.check_ancestors_disabled_state_for_form_control();
|
||||||
|
|
||||||
|
self.validity_state()
|
||||||
|
.perform_validation_and_update(ValidationFlags::all());
|
||||||
}
|
}
|
||||||
|
|
||||||
fn unbind_from_tree(&self, context: &UnbindContext) {
|
fn unbind_from_tree(&self, context: &UnbindContext) {
|
||||||
|
@ -2509,6 +2518,9 @@ impl VirtualMethods for HTMLInputElement {
|
||||||
} else {
|
} else {
|
||||||
el.check_disabled_attribute();
|
el.check_disabled_attribute();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.validity_state()
|
||||||
|
.perform_validation_and_update(ValidationFlags::all());
|
||||||
}
|
}
|
||||||
|
|
||||||
// This represents behavior for which the UIEvents spec and the
|
// This represents behavior for which the UIEvents spec and the
|
||||||
|
@ -2608,6 +2620,9 @@ impl VirtualMethods for HTMLInputElement {
|
||||||
event.mark_as_handled();
|
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
|
// https://html.spec.whatwg.org/multipage/#the-input-element%3Aconcept-node-clone-ext
|
||||||
|
@ -2628,6 +2643,8 @@ impl VirtualMethods for HTMLInputElement {
|
||||||
elem.textinput
|
elem.textinput
|
||||||
.borrow_mut()
|
.borrow_mut()
|
||||||
.set_content(self.textinput.borrow().get_content());
|
.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::element::{AttributeMutation, Element};
|
||||||
use crate::dom::htmlelement::HTMLElement;
|
use crate::dom::htmlelement::HTMLElement;
|
||||||
use crate::dom::htmloptionelement::HTMLOptionElement;
|
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 crate::dom::virtualmethods::VirtualMethods;
|
||||||
use dom_struct::dom_struct;
|
use dom_struct::dom_struct;
|
||||||
use html5ever::{LocalName, Prefix};
|
use html5ever::{LocalName, Prefix};
|
||||||
|
@ -53,6 +56,19 @@ impl HTMLOptGroupElement {
|
||||||
proto,
|
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 {
|
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::htmlselectelement::HTMLSelectElement;
|
||||||
use crate::dom::node::{BindContext, Node, ShadowIncluding, UnbindContext};
|
use crate::dom::node::{BindContext, Node, ShadowIncluding, UnbindContext};
|
||||||
use crate::dom::text::Text;
|
use crate::dom::text::Text;
|
||||||
|
use crate::dom::validation::Validatable;
|
||||||
|
use crate::dom::validitystate::ValidationFlags;
|
||||||
use crate::dom::virtualmethods::VirtualMethods;
|
use crate::dom::virtualmethods::VirtualMethods;
|
||||||
use crate::dom::window::Window;
|
use crate::dom::window::Window;
|
||||||
use dom_struct::dom_struct;
|
use dom_struct::dom_struct;
|
||||||
|
@ -108,6 +110,7 @@ impl HTMLOptionElement {
|
||||||
|
|
||||||
option.SetDefaultSelected(default_selected);
|
option.SetDefaultSelected(default_selected);
|
||||||
option.set_selectedness(selected);
|
option.set_selectedness(selected);
|
||||||
|
option.update_select_validity();
|
||||||
Ok(option)
|
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
|
// 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.dirtiness.set(true);
|
||||||
self.selectedness.set(selected);
|
self.selectedness.set(selected);
|
||||||
self.pick_if_selected_and_reset();
|
self.pick_if_selected_and_reset();
|
||||||
|
self.update_select_validity();
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://html.spec.whatwg.org/multipage/#dom-option-index
|
// https://html.spec.whatwg.org/multipage/#dom-option-index
|
||||||
|
@ -293,6 +310,7 @@ impl VirtualMethods for HTMLOptionElement {
|
||||||
el.check_parent_disabled_state_for_option();
|
el.check_parent_disabled_state_for_option();
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
self.update_select_validity();
|
||||||
},
|
},
|
||||||
&local_name!("selected") => {
|
&local_name!("selected") => {
|
||||||
match mutation {
|
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();
|
.check_parent_disabled_state_for_option();
|
||||||
|
|
||||||
self.pick_if_selected_and_reset();
|
self.pick_if_selected_and_reset();
|
||||||
|
self.update_select_validity();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn unbind_from_tree(&self, context: &UnbindContext) {
|
fn unbind_from_tree(&self, context: &UnbindContext) {
|
||||||
|
@ -334,6 +354,9 @@ impl VirtualMethods for HTMLOptionElement {
|
||||||
.filter_map(DomRoot::downcast::<HTMLSelectElement>)
|
.filter_map(DomRoot::downcast::<HTMLSelectElement>)
|
||||||
.next()
|
.next()
|
||||||
{
|
{
|
||||||
|
select
|
||||||
|
.validity_state()
|
||||||
|
.perform_validation_and_update(ValidationFlags::all());
|
||||||
select.ask_for_reset();
|
select.ask_for_reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -76,7 +76,7 @@ impl HTMLSelectElement {
|
||||||
) -> HTMLSelectElement {
|
) -> HTMLSelectElement {
|
||||||
HTMLSelectElement {
|
HTMLSelectElement {
|
||||||
htmlelement: HTMLElement::new_inherited_with_state(
|
htmlelement: HTMLElement::new_inherited_with_state(
|
||||||
ElementState::IN_ENABLED_STATE,
|
ElementState::IN_ENABLED_STATE | ElementState::IN_VALID_STATE,
|
||||||
local_name,
|
local_name,
|
||||||
prefix,
|
prefix,
|
||||||
document,
|
document,
|
||||||
|
@ -347,6 +347,9 @@ impl HTMLSelectElementMethods for HTMLSelectElement {
|
||||||
for opt in opt_iter {
|
for opt in opt_iter {
|
||||||
opt.set_selectedness(false);
|
opt.set_selectedness(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.validity_state()
|
||||||
|
.perform_validation_and_update(ValidationFlags::VALUE_MISSING);
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://html.spec.whatwg.org/multipage/#dom-select-selectedindex
|
// 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) {
|
fn attribute_mutated(&self, attr: &Attr, mutation: AttributeMutation) {
|
||||||
self.super_type().unwrap().attribute_mutated(attr, mutation);
|
self.super_type().unwrap().attribute_mutated(attr, mutation);
|
||||||
match attr.local_name() {
|
match attr.local_name() {
|
||||||
|
&local_name!("required") => {
|
||||||
|
self.validity_state()
|
||||||
|
.perform_validation_and_update(ValidationFlags::VALUE_MISSING);
|
||||||
|
},
|
||||||
&local_name!("disabled") => {
|
&local_name!("disabled") => {
|
||||||
let el = self.upcast::<Element>();
|
let el = self.upcast::<Element>();
|
||||||
match mutation {
|
match mutation {
|
||||||
|
@ -427,6 +434,9 @@ impl VirtualMethods for HTMLSelectElement {
|
||||||
el.check_ancestors_disabled_state_for_form_control();
|
el.check_ancestors_disabled_state_for_form_control();
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.validity_state()
|
||||||
|
.perform_validation_and_update(ValidationFlags::VALUE_MISSING);
|
||||||
},
|
},
|
||||||
&local_name!("form") => {
|
&local_name!("form") => {
|
||||||
self.form_attribute_mutated(mutation);
|
self.form_attribute_mutated(mutation);
|
||||||
|
|
|
@ -323,22 +323,26 @@ impl HTMLTextAreaElementMethods for HTMLTextAreaElement {
|
||||||
|
|
||||||
// https://html.spec.whatwg.org/multipage/#dom-textarea-value
|
// https://html.spec.whatwg.org/multipage/#dom-textarea-value
|
||||||
fn SetValue(&self, value: DOMString) {
|
fn SetValue(&self, value: DOMString) {
|
||||||
let mut textinput = self.textinput.borrow_mut();
|
{
|
||||||
|
let mut textinput = self.textinput.borrow_mut();
|
||||||
|
|
||||||
// Step 1
|
// Step 1
|
||||||
let old_value = textinput.get_content();
|
let old_value = textinput.get_content();
|
||||||
|
|
||||||
// Step 2
|
// Step 2
|
||||||
textinput.set_content(value);
|
textinput.set_content(value);
|
||||||
|
|
||||||
// Step 3
|
// Step 3
|
||||||
self.value_dirty.set(true);
|
self.value_dirty.set(true);
|
||||||
|
|
||||||
if old_value != textinput.get_content() {
|
if old_value != textinput.get_content() {
|
||||||
// Step 4
|
// Step 4
|
||||||
textinput.clear_selection_to_limit(Direction::Forward);
|
textinput.clear_selection_to_limit(Direction::Forward);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.validity_state()
|
||||||
|
.perform_validation_and_update(ValidationFlags::all());
|
||||||
self.upcast::<Node>().dirty(NodeDamage::OtherNodeDamage);
|
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) {
|
fn bind_to_tree(&self, context: &BindContext) {
|
||||||
|
@ -542,6 +549,9 @@ impl VirtualMethods for HTMLTextAreaElement {
|
||||||
|
|
||||||
self.upcast::<Element>()
|
self.upcast::<Element>()
|
||||||
.check_ancestors_disabled_state_for_form_control();
|
.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 {
|
fn parse_plain_attribute(&self, name: &LocalName, value: DOMString) -> AttrValue {
|
||||||
|
@ -574,6 +584,9 @@ impl VirtualMethods for HTMLTextAreaElement {
|
||||||
} else {
|
} else {
|
||||||
el.check_disabled_attribute();
|
el.check_disabled_attribute();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.validity_state()
|
||||||
|
.perform_validation_and_update(ValidationFlags::all());
|
||||||
}
|
}
|
||||||
|
|
||||||
// The cloning steps for textarea elements must propagate the raw value
|
// The cloning steps for textarea elements must propagate the raw value
|
||||||
|
@ -589,8 +602,12 @@ impl VirtualMethods for HTMLTextAreaElement {
|
||||||
}
|
}
|
||||||
let el = copy.downcast::<HTMLTextAreaElement>().unwrap();
|
let el = copy.downcast::<HTMLTextAreaElement>().unwrap();
|
||||||
el.value_dirty.set(self.value_dirty.get());
|
el.value_dirty.set(self.value_dirty.get());
|
||||||
let mut textinput = el.textinput.borrow_mut();
|
{
|
||||||
textinput.set_content(self.textinput.borrow().get_content());
|
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) {
|
fn children_changed(&self, mutation: &ChildrenMutation) {
|
||||||
|
@ -661,6 +678,9 @@ impl VirtualMethods for HTMLTextAreaElement {
|
||||||
event.mark_as_handled();
|
event.mark_as_handled();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.validity_state()
|
||||||
|
.perform_validation_and_update(ValidationFlags::all());
|
||||||
}
|
}
|
||||||
|
|
||||||
fn pop(&self) {
|
fn pop(&self) {
|
||||||
|
|
|
@ -28,23 +28,9 @@ pub trait Validatable {
|
||||||
ValidationFlags::empty()
|
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
|
// https://html.spec.whatwg.org/multipage/#check-validity-steps
|
||||||
fn check_validity(&self) -> bool {
|
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()
|
self.as_element()
|
||||||
.upcast::<EventTarget>()
|
.upcast::<EventTarget>()
|
||||||
.fire_cancelable_event(atom!("invalid"));
|
.fire_cancelable_event(atom!("invalid"));
|
||||||
|
@ -61,7 +47,7 @@ pub trait Validatable {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
let flags = self.validate(ValidationFlags::all());
|
let flags = self.validity_state().invalid_flags();
|
||||||
if flags.is_empty() {
|
if flags.is_empty() {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -90,7 +76,7 @@ pub trait Validatable {
|
||||||
// https://html.spec.whatwg.org/multipage/#dom-cva-validationmessage
|
// https://html.spec.whatwg.org/multipage/#dom-cva-validationmessage
|
||||||
fn validation_message(&self) -> DOMString {
|
fn validation_message(&self) -> DOMString {
|
||||||
if self.is_instance_validatable() {
|
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)
|
validation_message_for_flags(&self.validity_state(), flags)
|
||||||
} else {
|
} else {
|
||||||
DOMString::new()
|
DOMString::new()
|
||||||
|
|
|
@ -4,17 +4,24 @@
|
||||||
|
|
||||||
use crate::dom::bindings::cell::{DomRefCell, Ref};
|
use crate::dom::bindings::cell::{DomRefCell, Ref};
|
||||||
use crate::dom::bindings::codegen::Bindings::ValidityStateBinding::ValidityStateMethods;
|
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::reflector::{reflect_dom_object, Reflector};
|
||||||
use crate::dom::bindings::root::{Dom, DomRoot};
|
use crate::dom::bindings::root::{Dom, DomRoot};
|
||||||
use crate::dom::bindings::str::DOMString;
|
use crate::dom::bindings::str::DOMString;
|
||||||
use crate::dom::element::Element;
|
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 crate::dom::window::Window;
|
||||||
use dom_struct::dom_struct;
|
use dom_struct::dom_struct;
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
|
use std::cell::Cell;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
use style::element_state::ElementState;
|
||||||
|
|
||||||
// https://html.spec.whatwg.org/multipage/#validity-states
|
// https://html.spec.whatwg.org/multipage/#validity-states
|
||||||
bitflags! {
|
bitflags! {
|
||||||
|
#[derive(JSTraceable, MallocSizeOf)]
|
||||||
pub struct ValidationFlags: u32 {
|
pub struct ValidationFlags: u32 {
|
||||||
const VALUE_MISSING = 0b0000000001;
|
const VALUE_MISSING = 0b0000000001;
|
||||||
const TYPE_MISMATCH = 0b0000000010;
|
const TYPE_MISMATCH = 0b0000000010;
|
||||||
|
@ -64,6 +71,7 @@ pub struct ValidityState {
|
||||||
reflector_: Reflector,
|
reflector_: Reflector,
|
||||||
element: Dom<Element>,
|
element: Dom<Element>,
|
||||||
custom_error_message: DomRefCell<DOMString>,
|
custom_error_message: DomRefCell<DOMString>,
|
||||||
|
invalid_flags: Cell<ValidationFlags>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ValidityState {
|
impl ValidityState {
|
||||||
|
@ -72,6 +80,7 @@ impl ValidityState {
|
||||||
reflector_: Reflector::new(),
|
reflector_: Reflector::new(),
|
||||||
element: Dom::from_ref(element),
|
element: Dom::from_ref(element),
|
||||||
custom_error_message: DomRefCell::new(DOMString::new()),
|
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
|
// https://html.spec.whatwg.org/multipage/#custom-validity-error-message
|
||||||
pub fn set_custom_error_message(&self, error: DOMString) {
|
pub fn set_custom_error_message(&self, error: DOMString) {
|
||||||
*self.custom_error_message.borrow_mut() = error;
|
*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 {
|
impl ValidityStateMethods for ValidityState {
|
||||||
// https://html.spec.whatwg.org/multipage/#dom-validitystate-valuemissing
|
// https://html.spec.whatwg.org/multipage/#dom-validitystate-valuemissing
|
||||||
fn ValueMissing(&self) -> bool {
|
fn ValueMissing(&self) -> bool {
|
||||||
self.element.as_maybe_validatable().map_or(false, |e| {
|
self.invalid_flags()
|
||||||
!e.validate(ValidationFlags::VALUE_MISSING).is_empty()
|
.contains(ValidationFlags::VALUE_MISSING)
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://html.spec.whatwg.org/multipage/#dom-validitystate-typemismatch
|
// https://html.spec.whatwg.org/multipage/#dom-validitystate-typemismatch
|
||||||
fn TypeMismatch(&self) -> bool {
|
fn TypeMismatch(&self) -> bool {
|
||||||
self.element.as_maybe_validatable().map_or(false, |e| {
|
self.invalid_flags()
|
||||||
!e.validate(ValidationFlags::TYPE_MISMATCH).is_empty()
|
.contains(ValidationFlags::TYPE_MISMATCH)
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://html.spec.whatwg.org/multipage/#dom-validitystate-patternmismatch
|
// https://html.spec.whatwg.org/multipage/#dom-validitystate-patternmismatch
|
||||||
fn PatternMismatch(&self) -> bool {
|
fn PatternMismatch(&self) -> bool {
|
||||||
self.element.as_maybe_validatable().map_or(false, |e| {
|
self.invalid_flags()
|
||||||
!e.validate(ValidationFlags::PATTERN_MISMATCH).is_empty()
|
.contains(ValidationFlags::PATTERN_MISMATCH)
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://html.spec.whatwg.org/multipage/#dom-validitystate-toolong
|
// https://html.spec.whatwg.org/multipage/#dom-validitystate-toolong
|
||||||
fn TooLong(&self) -> bool {
|
fn TooLong(&self) -> bool {
|
||||||
self.element
|
self.invalid_flags().contains(ValidationFlags::TOO_LONG)
|
||||||
.as_maybe_validatable()
|
|
||||||
.map_or(false, |e| !e.validate(ValidationFlags::TOO_LONG).is_empty())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://html.spec.whatwg.org/multipage/#dom-validitystate-tooshort
|
// https://html.spec.whatwg.org/multipage/#dom-validitystate-tooshort
|
||||||
fn TooShort(&self) -> bool {
|
fn TooShort(&self) -> bool {
|
||||||
self.element.as_maybe_validatable().map_or(false, |e| {
|
self.invalid_flags().contains(ValidationFlags::TOO_SHORT)
|
||||||
!e.validate(ValidationFlags::TOO_SHORT).is_empty()
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://html.spec.whatwg.org/multipage/#dom-validitystate-rangeunderflow
|
// https://html.spec.whatwg.org/multipage/#dom-validitystate-rangeunderflow
|
||||||
fn RangeUnderflow(&self) -> bool {
|
fn RangeUnderflow(&self) -> bool {
|
||||||
self.element.as_maybe_validatable().map_or(false, |e| {
|
self.invalid_flags()
|
||||||
!e.validate(ValidationFlags::RANGE_UNDERFLOW).is_empty()
|
.contains(ValidationFlags::RANGE_UNDERFLOW)
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://html.spec.whatwg.org/multipage/#dom-validitystate-rangeoverflow
|
// https://html.spec.whatwg.org/multipage/#dom-validitystate-rangeoverflow
|
||||||
fn RangeOverflow(&self) -> bool {
|
fn RangeOverflow(&self) -> bool {
|
||||||
self.element.as_maybe_validatable().map_or(false, |e| {
|
self.invalid_flags()
|
||||||
!e.validate(ValidationFlags::RANGE_OVERFLOW).is_empty()
|
.contains(ValidationFlags::RANGE_OVERFLOW)
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://html.spec.whatwg.org/multipage/#dom-validitystate-stepmismatch
|
// https://html.spec.whatwg.org/multipage/#dom-validitystate-stepmismatch
|
||||||
fn StepMismatch(&self) -> bool {
|
fn StepMismatch(&self) -> bool {
|
||||||
self.element.as_maybe_validatable().map_or(false, |e| {
|
self.invalid_flags()
|
||||||
!e.validate(ValidationFlags::STEP_MISMATCH).is_empty()
|
.contains(ValidationFlags::STEP_MISMATCH)
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://html.spec.whatwg.org/multipage/#dom-validitystate-badinput
|
// https://html.spec.whatwg.org/multipage/#dom-validitystate-badinput
|
||||||
fn BadInput(&self) -> bool {
|
fn BadInput(&self) -> bool {
|
||||||
self.element.as_maybe_validatable().map_or(false, |e| {
|
self.invalid_flags().contains(ValidationFlags::BAD_INPUT)
|
||||||
!e.validate(ValidationFlags::BAD_INPUT).is_empty()
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://html.spec.whatwg.org/multipage/#dom-validitystate-customerror
|
// https://html.spec.whatwg.org/multipage/#dom-validitystate-customerror
|
||||||
fn CustomError(&self) -> bool {
|
fn CustomError(&self) -> bool {
|
||||||
self.element.as_maybe_validatable().map_or(false, |e| {
|
self.invalid_flags().contains(ValidationFlags::CUSTOM_ERROR)
|
||||||
!e.validate(ValidationFlags::CUSTOM_ERROR).is_empty()
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://html.spec.whatwg.org/multipage/#dom-validitystate-valid
|
// https://html.spec.whatwg.org/multipage/#dom-validitystate-valid
|
||||||
fn Valid(&self) -> bool {
|
fn Valid(&self) -> bool {
|
||||||
self.element
|
self.invalid_flags().is_empty()
|
||||||
.as_maybe_validatable()
|
|
||||||
.map_or(true, |e| e.validate(ValidationFlags::all()).is_empty())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -611,6 +611,8 @@ impl<'dom, LayoutDataType: LayoutDataTrait> ::selectors::Element
|
||||||
NonTSPseudoClass::Enabled |
|
NonTSPseudoClass::Enabled |
|
||||||
NonTSPseudoClass::Disabled |
|
NonTSPseudoClass::Disabled |
|
||||||
NonTSPseudoClass::Checked |
|
NonTSPseudoClass::Checked |
|
||||||
|
NonTSPseudoClass::Valid |
|
||||||
|
NonTSPseudoClass::Invalid |
|
||||||
NonTSPseudoClass::Indeterminate |
|
NonTSPseudoClass::Indeterminate |
|
||||||
NonTSPseudoClass::ReadWrite |
|
NonTSPseudoClass::ReadWrite |
|
||||||
NonTSPseudoClass::PlaceholderShown |
|
NonTSPseudoClass::PlaceholderShown |
|
||||||
|
|
|
@ -281,6 +281,8 @@ pub enum NonTSPseudoClass {
|
||||||
Active,
|
Active,
|
||||||
AnyLink,
|
AnyLink,
|
||||||
Checked,
|
Checked,
|
||||||
|
Valid,
|
||||||
|
Invalid,
|
||||||
Defined,
|
Defined,
|
||||||
Disabled,
|
Disabled,
|
||||||
Enabled,
|
Enabled,
|
||||||
|
@ -338,6 +340,8 @@ impl ToCss for NonTSPseudoClass {
|
||||||
Active => ":active",
|
Active => ":active",
|
||||||
AnyLink => ":any-link",
|
AnyLink => ":any-link",
|
||||||
Checked => ":checked",
|
Checked => ":checked",
|
||||||
|
Valid => ":valid",
|
||||||
|
Invalid => ":invalid",
|
||||||
Defined => ":defined",
|
Defined => ":defined",
|
||||||
Disabled => ":disabled",
|
Disabled => ":disabled",
|
||||||
Enabled => ":enabled",
|
Enabled => ":enabled",
|
||||||
|
@ -371,6 +375,8 @@ impl NonTSPseudoClass {
|
||||||
Enabled => ElementState::IN_ENABLED_STATE,
|
Enabled => ElementState::IN_ENABLED_STATE,
|
||||||
Disabled => ElementState::IN_DISABLED_STATE,
|
Disabled => ElementState::IN_DISABLED_STATE,
|
||||||
Checked => ElementState::IN_CHECKED_STATE,
|
Checked => ElementState::IN_CHECKED_STATE,
|
||||||
|
Valid => ElementState::IN_VALID_STATE,
|
||||||
|
Invalid => ElementState::IN_INVALID_STATE,
|
||||||
Indeterminate => ElementState::IN_INDETERMINATE_STATE,
|
Indeterminate => ElementState::IN_INDETERMINATE_STATE,
|
||||||
ReadOnly | ReadWrite => ElementState::IN_READWRITE_STATE,
|
ReadOnly | ReadWrite => ElementState::IN_READWRITE_STATE,
|
||||||
PlaceholderShown => ElementState::IN_PLACEHOLDER_SHOWN_STATE,
|
PlaceholderShown => ElementState::IN_PLACEHOLDER_SHOWN_STATE,
|
||||||
|
@ -425,6 +431,8 @@ impl<'a, 'i> ::selectors::Parser<'i> for SelectorParser<'a> {
|
||||||
"active" => Active,
|
"active" => Active,
|
||||||
"any-link" => AnyLink,
|
"any-link" => AnyLink,
|
||||||
"checked" => Checked,
|
"checked" => Checked,
|
||||||
|
"valid" => Valid,
|
||||||
|
"invalid" => Invalid,
|
||||||
"defined" => Defined,
|
"defined" => Defined,
|
||||||
"disabled" => Disabled,
|
"disabled" => Disabled,
|
||||||
"enabled" => Enabled,
|
"enabled" => Enabled,
|
||||||
|
|
|
@ -1,9 +1,4 @@
|
||||||
[Element-closest.html]
|
[Element-closest.html]
|
||||||
type: testharness
|
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)']
|
[Element.closest with context node 'test4' and selector ':has(> :scope)']
|
||||||
expected: FAIL
|
expected: FAIL
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
[infinite_backtracking.html]
|
[infinite_backtracking.html]
|
||||||
|
expected: TIMEOUT
|
||||||
[Infinite backtracking pattern terminates]
|
[Infinite backtracking pattern terminates]
|
||||||
expected: FAIL
|
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)]
|
[text/plain: Basic File test (normal form)]
|
||||||
expected: FAIL
|
expected: FAIL
|
||||||
|
|
||||||
[text/plain: 0x00 in name (normal form)]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[text/plain: 0x00 in value (normal form)]
|
[text/plain: 0x00 in value (normal form)]
|
||||||
expected: FAIL
|
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]
|
[pattern attribute support on input element]
|
||||||
expected: FAIL
|
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]
|
[<input pattern> supports set difference syntax]
|
||||||
expected: FAIL
|
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