servo: Merge #10834 - added support for :read-only and :read-write pseudo-classes (from yoava333:master); r=SimonSapin
authorYoav Alon <yoava333@gmail.com>
Fri, 29 Apr 2016 08:12:18 -0700
changeset 338663 ceee6f2a1ab66537339fc1442a820b9be37462e2
parent 338662 469552340c1654bec34dc3dc90ad4e71f6c1db70
child 338664 396f911a7d1be9232afa49cc120e9725d5c991f1
push id31307
push usergszorc@mozilla.com
push dateSat, 04 Feb 2017 00:59:06 +0000
treeherdermozilla-central@94079d43835f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersSimonSapin
servo: Merge #10834 - added support for :read-only and :read-write pseudo-classes (from yoava333:master); r=SimonSapin partial fix for https://github.com/servo/servo/issues/10732 It's not a full fix because: 1. there's a bug in wpt-test https://github.com/w3c/web-platform-tests/issues/2889#issuecomment-214144420 2. we don't fully support all input types (namely image, color, hidden and range), which are defaulted to input text. this means that :read-write which is applicable to input text is mis-handled in those cases. 3. we don't support contenteditable, which is also possibly :read-write Source-Repo: https://github.com/servo/servo Source-Revision: ac8406f4aebe1e8571319a1d56fc627ea5782e60
servo/components/layout/wrapper.rs
servo/components/script/dom/element.rs
servo/components/script/dom/htmlinputelement.rs
servo/components/script/dom/htmltextareaelement.rs
servo/components/style/element_state.rs
servo/components/style/selector_impl.rs
servo/ports/geckolib/selector_impl.rs
servo/ports/geckolib/wrapper.rs
--- a/servo/components/layout/wrapper.rs
+++ b/servo/components/layout/wrapper.rs
@@ -545,23 +545,27 @@ impl<'le> ::selectors::Element for Servo
 
             NonTSPseudoClass::ServoNonZeroBorder => unsafe {
                 match (*self.element.unsafe_get()).get_attr_for_layout(&ns!(), &atom!("border")) {
                     None | Some(&AttrValue::UInt(_, 0)) => false,
                     _ => true,
                 }
             },
 
+            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())
         }
     }
 
     #[inline]
     fn get_id(&self) -> Option<Atom> {
         unsafe {
             (*self.element.id_attribute()).clone()
--- a/servo/components/script/dom/element.rs
+++ b/servo/components/script/dom/element.rs
@@ -2156,23 +2156,27 @@ impl<'a> ::selectors::Element for Root<E
                         match this.get_border() {
                             None | Some(0) => false,
                             Some(_) => true,
                         }
                     }
                 }
             },
 
+            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()),
         }
     }
 
     fn get_id(&self) -> Option<Atom> {
         self.id_attribute.borrow().clone()
     }
 
@@ -2425,16 +2429,24 @@ impl Element {
 
     pub fn disabled_state(&self) -> bool {
         self.state.get().contains(IN_DISABLED_STATE)
     }
 
     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 {
     pub fn check_ancestors_disabled_state_for_form_control(&self) {
         let node = self.upcast::<Node>();
         if self.disabled_state() {
             return;
         }
--- a/servo/components/script/dom/htmlinputelement.rs
+++ b/servo/components/script/dom/htmlinputelement.rs
@@ -120,17 +120,17 @@ impl InputActivationState {
 static DEFAULT_INPUT_SIZE: u32 = 20;
 static DEFAULT_MAX_LENGTH: i32 = -1;
 
 impl HTMLInputElement {
     fn new_inherited(localName: Atom, prefix: Option<DOMString>, document: &Document) -> HTMLInputElement {
         let chan = document.window().constellation_chan().clone();
         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()),
             checked_changed: Cell::new(false),
             value_changed: Cell::new(false),
             maxlength: Cell::new(DEFAULT_MAX_LENGTH),
             size: Cell::new(DEFAULT_INPUT_SIZE),
             textinput: DOMRefCell::new(TextInput::new(Single, DOMString::new(), chan, None, SelectionDirection::None)),
@@ -708,16 +708,21 @@ impl VirtualMethods for HTMLInputElement
                        return;
                     },
                     AttributeMutation::Removed => false,
                 };
                 let el = self.upcast::<Element>();
                 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 {
                     AttributeMutation::Set(None) => true,
                     AttributeMutation::Set(Some(_)) => {
                        // Input was already checked before.
                        return;
                     },
@@ -743,16 +748,25 @@ impl VirtualMethods for HTMLInputElement
                             &atom!("checkbox") => InputType::InputCheckbox,
                             &atom!("password") => InputType::InputPassword,
                             _ => InputType::InputText,
                         };
 
                         // 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::<Element>();
+                        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) {
 
                             // Step 1
                             (&ValueMode::Value, false, ValueMode::Default) |
                             (&ValueMode::Value, false, ValueMode::DefaultOn) => {
                                 self.SetValue(old_idl_value)
@@ -787,16 +801,20 @@ impl VirtualMethods for HTMLInputElement
                     },
                     AttributeMutation::Removed => {
                         if self.input_type.get() == InputType::InputRadio {
                             broadcast_radio_checked(
                                 self,
                                 self.radio_group_name().as_ref());
                         }
                         self.input_type.set(InputType::InputText);
+                        let el = self.upcast::<Element>();
+
+                        let read_write = !(self.ReadOnly() || el.disabled_state());
+                        el.set_read_write_state(read_write);
                     }
                 }
             },
             &atom!("value") if !self.value_changed.get() => {
                 let value = mutation.new_value(attr).map(|value| (**value).to_owned());
                 self.textinput.borrow_mut().set_content(
                     value.map_or(DOMString::new(), DOMString::from));
             },
@@ -820,16 +838,27 @@ impl VirtualMethods for HTMLInputElement
                 // FIXME(ajeffrey): Should we do in-place mutation of the placeholder?
                 let mut placeholder = self.placeholder.borrow_mut();
                 placeholder.clear();
                 if let AttributeMutation::Set(_) = mutation {
                     placeholder.extend(
                         attr.value().chars().filter(|&c| c != '\n' && c != '\r'));
                 }
             },
+            &atom!("readonly") if self.input_type.get() == InputType::InputText => {
+                let el = self.upcast::<Element>();
+                match mutation {
+                    AttributeMutation::Set(_) => {
+                        el.set_read_write_state(false);
+                    },
+                    AttributeMutation::Removed => {
+                        el.set_read_write_state(!el.disabled_state());
+                    }
+                }
+            }
             _ => {},
         }
     }
 
     fn parse_plain_attribute(&self, name: &Atom, value: DOMString) -> AttrValue {
         match name {
             &atom!("name") => AttrValue::from_atomic(value),
             &atom!("size") => AttrValue::from_limited_u32(value, DEFAULT_INPUT_SIZE),
--- a/servo/components/script/dom/htmltextareaelement.rs
+++ b/servo/components/script/dom/htmltextareaelement.rs
@@ -98,17 +98,17 @@ static DEFAULT_ROWS: u32 = 2;
 
 impl HTMLTextAreaElement {
     fn new_inherited(localName: Atom,
                      prefix: Option<DOMString>,
                      document: &Document) -> HTMLTextAreaElement {
         let chan = document.window().constellation_chan().clone();
         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)),
             value_changed: Cell::new(false),
         }
     }
 
     #[allow(unrooted_must_root)]
@@ -284,24 +284,41 @@ impl VirtualMethods for HTMLTextAreaElem
         self.super_type().unwrap().attribute_mutated(attr, mutation);
         match *attr.local_name() {
             atom!("disabled") => {
                 let el = self.upcast::<Element>();
                 match mutation {
                     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::<Element>();
+                match mutation {
+                    AttributeMutation::Set(_) => {
+                        el.set_read_write_state(false);
+                    },
+                    AttributeMutation::Removed => {
+                        el.set_read_write_state(!el.disabled_state());
+                    }
+                }
+            }
             _ => {},
         }
     }
 
     fn bind_to_tree(&self, tree_in_doc: bool) {
         if let Some(ref s) = self.super_type() {
             s.bind_to_tree(tree_in_doc);
         }
--- a/servo/components/style/element_state.rs
+++ b/servo/components/style/element_state.rs
@@ -22,10 +22,12 @@ bitflags! {
         #[doc = "Content is disabled. \
                  http://www.whatwg.org/html/#selector-disabled"]
         const IN_DISABLED_STATE = 0x10,
         #[doc = "Content is checked. \
                  https://html.spec.whatwg.org/multipage/#selector-checked"]
         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,
     }
 }
--- a/servo/components/style/selector_impl.rs
+++ b/servo/components/style/selector_impl.rs
@@ -39,30 +39,33 @@ pub enum NonTSPseudoClass {
     Active,
     Focus,
     Hover,
     Enabled,
     Disabled,
     Checked,
     Indeterminate,
     ServoNonZeroBorder,
+    ReadWrite,
+    ReadOnly
 }
 
 impl NonTSPseudoClass {
     pub fn state_flag(&self) -> ElementState {
         use element_state::*;
         use self::NonTSPseudoClass::*;
         match *self {
             Active => IN_ACTIVE_STATE,
             Focus => IN_FOCUS_STATE,
             Hover => IN_HOVER_STATE,
             Enabled => IN_ENABLED_STATE,
             Disabled => IN_DISABLED_STATE,
             Checked => IN_CHECKED_STATE,
             Indeterminate => IN_INDETERMINATE_STATE,
+            ReadOnly | ReadWrite => IN_READ_WRITE_STATE,
 
             AnyLink |
             Link |
             Visited |
             ServoNonZeroBorder => ElementState::empty(),
         }
     }
 }
@@ -83,16 +86,18 @@ impl SelectorImpl for ServoSelectorImpl 
             "visited" => Visited,
             "active" => Active,
             "focus" => Focus,
             "hover" => Hover,
             "enabled" => Enabled,
             "disabled" => Disabled,
             "checked" => Checked,
             "indeterminate" => Indeterminate,
+            "read-write" => ReadWrite,
+            "read-only" => ReadOnly,
             "-servo-nonzero-border" => {
                 if !context.in_user_agent_stylesheet {
                     return Err(());
                 }
                 ServoNonZeroBorder
             },
             _ => return Err(())
         };
--- a/servo/ports/geckolib/selector_impl.rs
+++ b/servo/ports/geckolib/selector_impl.rs
@@ -96,30 +96,33 @@ pub enum NonTSPseudoClass {
     Visited,
     Active,
     Focus,
     Hover,
     Enabled,
     Disabled,
     Checked,
     Indeterminate,
+    ReadWrite,
+    ReadOnly,
 }
 
 impl NonTSPseudoClass {
     pub fn state_flag(&self) -> ElementState {
         use self::NonTSPseudoClass::*;
         use style::element_state::*;
         match *self {
             Active => IN_ACTIVE_STATE,
             Focus => IN_FOCUS_STATE,
             Hover => IN_HOVER_STATE,
             Enabled => IN_ENABLED_STATE,
             Disabled => IN_DISABLED_STATE,
             Checked => IN_CHECKED_STATE,
             Indeterminate => IN_INDETERMINATE_STATE,
+            ReadOnly | ReadWrite => IN_READ_WRITE_STATE,
 
             AnyLink |
             Link |
             Visited => ElementState::empty(),
         }
     }
 }
 
@@ -135,16 +138,18 @@ impl SelectorImpl for GeckoSelectorImpl 
             "visited" => Visited,
             "active" => Active,
             "focus" => Focus,
             "hover" => Hover,
             "enabled" => Enabled,
             "disabled" => Disabled,
             "checked" => Checked,
             "indeterminate" => Indeterminate,
+            "read-write" => ReadWrite,
+            "read-only" => ReadOnly,
             _ => return Err(())
         };
 
         Ok(pseudo_class)
     }
 
     fn parse_pseudo_element(_context: &ParserContext,
                             name: &str) -> Result<PseudoElement, ()> {
--- a/servo/ports/geckolib/wrapper.rs
+++ b/servo/ports/geckolib/wrapper.rs
@@ -432,19 +432,23 @@ impl<'le> ::selectors::Element for Gecko
             NonTSPseudoClass::Link => unsafe { Gecko_IsUnvisitedLink(self.element) },
             NonTSPseudoClass::Visited => unsafe { Gecko_IsVisitedLink(self.element) },
             NonTSPseudoClass::Active |
             NonTSPseudoClass::Focus |
             NonTSPseudoClass::Hover |
             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())
+            }
         }
     }
 
     fn get_id(&self) -> Option<Atom> {
         // FIXME(bholley): Servo caches the id atom directly on the element to
         // make this blazing fast. Assuming that was a measured optimization, doing
         // the dumb thing like we do below will almost certainly be a bottleneck.
         self.get_attr(&ns!(), &atom!("id")).map(|s| Atom::from(s))