diff --git a/components/layout/wrapper.rs b/components/layout/wrapper.rs index 00c776317077..aea7476cb2d6 100644 --- a/components/layout/wrapper.rs +++ b/components/layout/wrapper.rs @@ -550,13 +550,17 @@ impl<'le> ::selectors::Element for ServoLayoutElement<'le> { } }, + NonTSPseudoClass::ReadOnly => + !self.element.get_state_for_layout().contains(pseudo_class.state_flag()), + NonTSPseudoClass::Active | NonTSPseudoClass::Focus | NonTSPseudoClass::Hover | NonTSPseudoClass::Enabled | NonTSPseudoClass::Disabled | NonTSPseudoClass::Checked | - NonTSPseudoClass::Indeterminate => + NonTSPseudoClass::Indeterminate | + NonTSPseudoClass::ReadWrite => self.element.get_state_for_layout().contains(pseudo_class.state_flag()) } } diff --git a/components/script/dom/element.rs b/components/script/dom/element.rs index d5151dc7dfd7..d2149fbe70d6 100644 --- a/components/script/dom/element.rs +++ b/components/script/dom/element.rs @@ -2161,13 +2161,17 @@ impl<'a> ::selectors::Element for Root { } }, + NonTSPseudoClass::ReadOnly => + !Element::state(self).contains(pseudo_class.state_flag()), + NonTSPseudoClass::Active | NonTSPseudoClass::Focus | NonTSPseudoClass::Hover | NonTSPseudoClass::Enabled | NonTSPseudoClass::Disabled | NonTSPseudoClass::Checked | - NonTSPseudoClass::Indeterminate => + NonTSPseudoClass::Indeterminate | + NonTSPseudoClass::ReadWrite => Element::state(self).contains(pseudo_class.state_flag()), } } @@ -2430,6 +2434,14 @@ impl Element { pub fn set_disabled_state(&self, value: bool) { self.set_state(IN_DISABLED_STATE, value) } + + pub fn read_write_state(&self) -> bool { + self.state.get().contains(IN_READ_WRITE_STATE) + } + + pub fn set_read_write_state(&self, value: bool) { + self.set_state(IN_READ_WRITE_STATE, value) + } } impl Element { diff --git a/components/script/dom/htmlinputelement.rs b/components/script/dom/htmlinputelement.rs index ab753053b8f2..5e14506e0b81 100644 --- a/components/script/dom/htmlinputelement.rs +++ b/components/script/dom/htmlinputelement.rs @@ -124,7 +124,7 @@ impl HTMLInputElement { let chan = document.window().constellation_chan(); HTMLInputElement { htmlelement: - HTMLElement::new_inherited_with_state(IN_ENABLED_STATE, + HTMLElement::new_inherited_with_state(IN_ENABLED_STATE | IN_READ_WRITE_STATE, localName, prefix, document), input_type: Cell::new(InputType::InputText), placeholder: DOMRefCell::new(DOMString::new()), @@ -710,6 +710,11 @@ impl VirtualMethods for HTMLInputElement { el.set_disabled_state(disabled_state); el.set_enabled_state(!disabled_state); el.check_ancestors_disabled_state_for_form_control(); + + if self.input_type.get() == InputType::InputText { + let read_write = !(self.ReadOnly() || el.disabled_state()); + el.set_read_write_state(read_write); + } }, &atom!("checked") if !self.checked_changed.get() => { let checked_state = match mutation { @@ -745,6 +750,15 @@ impl VirtualMethods for HTMLInputElement { // https://html.spec.whatwg.org/multipage/#input-type-change let (old_value_mode, old_idl_value) = (self.value_mode(), self.Value()); self.input_type.set(new_type); + + let el = self.upcast::(); + if new_type == InputType::InputText { + let read_write = !(self.ReadOnly() || el.disabled_state()); + el.set_read_write_state(read_write); + } else { + el.set_read_write_state(false); + } + let new_value_mode = self.value_mode(); match (&old_value_mode, old_idl_value.is_empty(), new_value_mode) { @@ -789,6 +803,10 @@ impl VirtualMethods for HTMLInputElement { self.radio_group_name().as_ref()); } self.input_type.set(InputType::InputText); + let el = self.upcast::(); + + let read_write = !(self.ReadOnly() || el.disabled_state()); + el.set_read_write_state(read_write); } } }, @@ -822,6 +840,17 @@ impl VirtualMethods for HTMLInputElement { attr.value().chars().filter(|&c| c != '\n' && c != '\r')); } }, + &atom!("readonly") if self.input_type.get() == InputType::InputText => { + let el = self.upcast::(); + match mutation { + AttributeMutation::Set(_) => { + el.set_read_write_state(false); + }, + AttributeMutation::Removed => { + el.set_read_write_state(!el.disabled_state()); + } + } + } _ => {}, } } diff --git a/components/script/dom/htmltextareaelement.rs b/components/script/dom/htmltextareaelement.rs index d3eb78270312..82d33c873c7b 100644 --- a/components/script/dom/htmltextareaelement.rs +++ b/components/script/dom/htmltextareaelement.rs @@ -104,7 +104,7 @@ impl HTMLTextAreaElement { let chan = document.window().constellation_chan(); HTMLTextAreaElement { htmlelement: - HTMLElement::new_inherited_with_state(IN_ENABLED_STATE, + HTMLElement::new_inherited_with_state(IN_ENABLED_STATE | IN_READ_WRITE_STATE, localName, prefix, document), textinput: DOMRefCell::new(TextInput::new( Lines::Multiple, DOMString::new(), chan, None, SelectionDirection::None)), @@ -290,14 +290,31 @@ impl VirtualMethods for HTMLTextAreaElement { AttributeMutation::Set(_) => { el.set_disabled_state(true); el.set_enabled_state(false); + + el.set_read_write_state(false); }, AttributeMutation::Removed => { el.set_disabled_state(false); el.set_enabled_state(true); el.check_ancestors_disabled_state_for_form_control(); + + if !el.disabled_state() && !el.read_write_state() { + el.set_read_write_state(true); + } } } }, + atom!("readonly") => { + let el = self.upcast::(); + match mutation { + AttributeMutation::Set(_) => { + el.set_read_write_state(false); + }, + AttributeMutation::Removed => { + el.set_read_write_state(!el.disabled_state()); + } + } + } _ => {}, } } diff --git a/components/style/element_state.rs b/components/style/element_state.rs index 41846085fc82..26a2198f3f18 100644 --- a/components/style/element_state.rs +++ b/components/style/element_state.rs @@ -27,5 +27,7 @@ bitflags! { const IN_CHECKED_STATE = 0x20, #[doc = "https://html.spec.whatwg.org/multipage/#selector-indeterminate"] const IN_INDETERMINATE_STATE = 0x40, + #[doc = "https://html.spec.whatwg.org/multipage/#selector-read-write"] + const IN_READ_WRITE_STATE = 0x80, } } diff --git a/components/style/selector_impl.rs b/components/style/selector_impl.rs index 7d6c7efc2672..d70287f9ddfd 100644 --- a/components/style/selector_impl.rs +++ b/components/style/selector_impl.rs @@ -44,6 +44,8 @@ pub enum NonTSPseudoClass { Checked, Indeterminate, ServoNonZeroBorder, + ReadWrite, + ReadOnly } impl NonTSPseudoClass { @@ -58,6 +60,7 @@ impl NonTSPseudoClass { Disabled => IN_DISABLED_STATE, Checked => IN_CHECKED_STATE, Indeterminate => IN_INDETERMINATE_STATE, + ReadOnly | ReadWrite => IN_READ_WRITE_STATE, AnyLink | Link | @@ -88,6 +91,8 @@ impl SelectorImpl for ServoSelectorImpl { "disabled" => Disabled, "checked" => Checked, "indeterminate" => Indeterminate, + "read-write" => ReadWrite, + "read-only" => ReadOnly, "-servo-nonzero-border" => { if !context.in_user_agent_stylesheet { return Err(()); diff --git a/ports/geckolib/selector_impl.rs b/ports/geckolib/selector_impl.rs index 0bd84ab9782b..d892b68ca312 100644 --- a/ports/geckolib/selector_impl.rs +++ b/ports/geckolib/selector_impl.rs @@ -101,6 +101,8 @@ pub enum NonTSPseudoClass { Disabled, Checked, Indeterminate, + ReadWrite, + ReadOnly, } impl NonTSPseudoClass { @@ -115,6 +117,7 @@ impl NonTSPseudoClass { Disabled => IN_DISABLED_STATE, Checked => IN_CHECKED_STATE, Indeterminate => IN_INDETERMINATE_STATE, + ReadOnly | ReadWrite => IN_READ_WRITE_STATE, AnyLink | Link | @@ -140,6 +143,8 @@ impl SelectorImpl for GeckoSelectorImpl { "disabled" => Disabled, "checked" => Checked, "indeterminate" => Indeterminate, + "read-write" => ReadWrite, + "read-only" => ReadOnly, _ => return Err(()) }; diff --git a/ports/geckolib/wrapper.rs b/ports/geckolib/wrapper.rs index 7262f91e47e0..74c4a6ec46a9 100644 --- a/ports/geckolib/wrapper.rs +++ b/ports/geckolib/wrapper.rs @@ -437,9 +437,13 @@ impl<'le> ::selectors::Element for GeckoElement<'le> { NonTSPseudoClass::Enabled | NonTSPseudoClass::Disabled | NonTSPseudoClass::Checked | + NonTSPseudoClass::ReadWrite | NonTSPseudoClass::Indeterminate => { self.get_state().contains(pseudo_class.state_flag()) }, + NonTSPseudoClass::ReadOnly => { + !self.get_state().contains(pseudo_class.state_flag()) + } } } diff --git a/tests/wpt/metadata/html/semantics/selectors/pseudo-classes/readwrite-readonly.html.ini b/tests/wpt/metadata/html/semantics/selectors/pseudo-classes/readwrite-readonly.html.ini index 090483d537ef..568d614c625d 100644 --- a/tests/wpt/metadata/html/semantics/selectors/pseudo-classes/readwrite-readonly.html.ini +++ b/tests/wpt/metadata/html/semantics/selectors/pseudo-classes/readwrite-readonly.html.ini @@ -1,42 +1,15 @@ [readwrite-readonly.html] type: testharness bug: https://github.com/servo/servo/issues/10732 - [The :read-write pseudo-class must match input elements to which the readonly attribute applies, and that are mutable] - expected: FAIL - [The :read-only pseudo-class must not match input elements to which the readonly attribute applies, and that are mutable] expected: FAIL - [The :read-write pseudo-class must not match input elements after the readonly attribute has been added] - expected: FAIL - [The :read-only pseudo-class must match input elements after the readonly attribute has been added] expected: FAIL - [The :read-write pseudo-class must not match input elements after the readonly attribute has been removed] - expected: FAIL - [The :read-only pseudo-class must match input elements after the readonly attribute has been removed] expected: FAIL - [The :read-write pseudo-class must match textarea elements that do not have a readonly attribute, and that are not disabled] - expected: FAIL - - [The :read-only pseudo-class must match textarea elements that have a readonly attribute, or that are disabled] - expected: FAIL - - [The :read-write pseudo-class must match textarea elements after the readonly attribute has been added] - expected: FAIL - - [The :read-only pseudo-class must match textarea elements after the readonly attribute has been added] - expected: FAIL - - [The :read-write pseudo-class must not match textarea elements that are disabled] - expected: FAIL - - [The :read-only pseudo-class must match textarea elements that are disabled] - expected: FAIL - [The :read-write pseudo-class must match elements that are editable] expected: FAIL