servo: Merge #11572 - Implement :placeholder-shown (fixes #10561) (from nox:placeholder-shown); r=SimonSapin
authorAnthony Ramine <n.oxyde@gmail.com>
Mon, 06 Jun 2016 23:44:16 -0500
changeset 339031 b266d21164c0b2541216f526b81dd6ebc87b2805
parent 339030 ce84a29c89978d33b916086bd159022ebd10df47
child 339032 bdc56e5660c3e58e4d22a5a44a412b0376419e38
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 #11572 - Implement :placeholder-shown (fixes #10561) (from nox:placeholder-shown); r=SimonSapin Source-Repo: https://github.com/servo/servo Source-Revision: 0f1f99a4bf3be2f695b402e8676c3b0b935cbc5b
servo/components/layout/wrapper.rs
servo/components/script/dom/element.rs
servo/components/script/dom/htmlinputelement.rs
servo/components/script/textinput.rs
servo/components/style/element_state.rs
servo/components/style/selector_impl.rs
servo/ports/geckolib/wrapper.rs
--- a/servo/components/layout/wrapper.rs
+++ b/servo/components/layout/wrapper.rs
@@ -556,17 +556,18 @@ impl<'le> ::selectors::Element for Servo
 
             NonTSPseudoClass::Active |
             NonTSPseudoClass::Focus |
             NonTSPseudoClass::Hover |
             NonTSPseudoClass::Enabled |
             NonTSPseudoClass::Disabled |
             NonTSPseudoClass::Checked |
             NonTSPseudoClass::Indeterminate |
-            NonTSPseudoClass::ReadWrite =>
+            NonTSPseudoClass::ReadWrite |
+            NonTSPseudoClass::PlaceholderShown =>
                 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
@@ -2217,17 +2217,18 @@ impl<'a> ::selectors::Element for Root<E
 
             NonTSPseudoClass::Active |
             NonTSPseudoClass::Focus |
             NonTSPseudoClass::Hover |
             NonTSPseudoClass::Enabled |
             NonTSPseudoClass::Disabled |
             NonTSPseudoClass::Checked |
             NonTSPseudoClass::Indeterminate |
-            NonTSPseudoClass::ReadWrite =>
+            NonTSPseudoClass::ReadWrite |
+            NonTSPseudoClass::PlaceholderShown =>
                 Element::state(self).contains(pseudo_class.state_flag()),
         }
     }
 
     fn get_id(&self) -> Option<Atom> {
         self.id_attribute.borrow().clone()
     }
 
@@ -2488,16 +2489,27 @@ impl Element {
 
     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)
     }
+
+    pub fn placeholder_shown_state(&self) -> bool {
+        self.state.get().contains(IN_PLACEHOLDER_SHOWN_STATE)
+    }
+
+    pub fn set_placeholder_shown_state(&self, value: bool) {
+        if self.placeholder_shown_state() != value {
+            self.set_state(IN_PLACEHOLDER_SHOWN_STATE, value);
+            self.upcast::<Node>().dirty(NodeDamage::OtherNodeDamage);
+        }
+    }
 }
 
 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
@@ -704,16 +704,27 @@ impl HTMLInputElement {
         }
 
         self.SetValue(self.DefaultValue())
             .expect("Failed to reset input value to default.");
         self.value_dirty.set(false);
         self.value_changed.set(false);
         self.upcast::<Node>().dirty(NodeDamage::OtherNodeDamage);
     }
+
+    fn update_placeholder_shown_state(&self) {
+        match self.input_type.get() {
+            InputType::InputText | InputType::InputPassword => {},
+            _ => return,
+        }
+        let has_placeholder = !self.placeholder.borrow().is_empty();
+        let has_value = !self.textinput.borrow().is_empty();
+        let el = self.upcast::<Element>();
+        el.set_placeholder_shown_state(has_placeholder && !has_value);
+    }
 }
 
 impl VirtualMethods for HTMLInputElement {
     fn super_type(&self) -> Option<&VirtualMethods> {
         Some(self.upcast::<HTMLElement>() as &VirtualMethods)
     }
 
     fn attribute_mutated(&self, attr: &Attr, mutation: AttributeMutation) {
@@ -752,16 +763,17 @@ impl VirtualMethods for HTMLInputElement
             },
             &atom!("size") => {
                 let size = mutation.new_value(attr).map(|value| {
                     value.as_uint()
                 });
                 self.size.set(size.unwrap_or(DEFAULT_INPUT_SIZE));
             }
             &atom!("type") => {
+                let el = self.upcast::<Element>();
                 match mutation {
                     AttributeMutation::Set(_) => {
                         let new_type = match attr.value().as_atom() {
                             &atom!("button") => InputType::InputButton,
                             &atom!("submit") => InputType::InputSubmit,
                             &atom!("reset") => InputType::InputReset,
                             &atom!("file") => InputType::InputFile,
                             &atom!("radio") => InputType::InputRadio,
@@ -769,17 +781,16 @@ impl VirtualMethods for HTMLInputElement
                             &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();
@@ -826,21 +837,24 @@ impl VirtualMethods for HTMLInputElement
                         }
                         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);
                     }
                 }
+
+                self.update_placeholder_shown_state();
             },
             &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));
+                self.update_placeholder_shown_state();
             },
             &atom!("name") if self.input_type.get() == InputType::InputRadio => {
                 self.radio_group_updated(
                     mutation.new_value(attr).as_ref().map(|name| name.as_atom()));
             },
             &atom!("maxlength") => {
                 match *attr.value() {
                     AttrValue::Int(_, value) => {
@@ -849,23 +863,25 @@ impl VirtualMethods for HTMLInputElement
                         } else {
                             self.textinput.borrow_mut().max_length = Some(value as usize)
                         }
                     },
                     _ => panic!("Expected an AttrValue::Int"),
                 }
             }
             &atom!("placeholder") => {
-                // 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'));
+                {
+                    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'));
+                    }
                 }
+                self.update_placeholder_shown_state();
             },
             &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 => {
@@ -931,16 +947,17 @@ impl VirtualMethods for HTMLInputElement
                         TriggerDefaultAction => {
                             self.implicit_submission(keyevent.CtrlKey(),
                                                      keyevent.ShiftKey(),
                                                      keyevent.AltKey(),
                                                      keyevent.MetaKey());
                         },
                         DispatchInput => {
                             self.value_changed.set(true);
+                            self.update_placeholder_shown_state();
 
                             if event.IsTrusted() {
                                 let window = window_from_node(self);
                                 let _ = window.user_interaction_task_source().queue_event(
                                     &self.upcast(),
                                     atom!("input"),
                                     EventBubbles::Bubbles,
                                     EventCancelable::NotCancelable);
--- a/servo/components/script/textinput.rs
+++ b/servo/components/script/textinput.rs
@@ -544,16 +544,21 @@ impl<T: ClipboardProvider> TextInput<T> 
                 self.adjust_vertical(28, maybe_select);
                 KeyReaction::RedrawSelection
             }
             Key::Tab => KeyReaction::TriggerDefaultAction,
             _ => KeyReaction::Nothing,
         }
     }
 
+    /// Whether the content is empty.
+    pub fn is_empty(&self) -> bool {
+        self.lines.len() <= 1 && self.lines.get(0).map_or(true, |line| line.is_empty())
+    }
+
     /// The length of the content in bytes.
     pub fn len(&self) -> usize {
         self.lines.iter().fold(0, |m, l| {
             m + l.len() + 1 // + 1 for the '\n'
         }) - 1
     }
 
     /// The length of the content in bytes.
--- a/servo/components/style/element_state.rs
+++ b/servo/components/style/element_state.rs
@@ -1,16 +1,16 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 bitflags! {
     #[doc = "Event-based element states."]
     #[derive(HeapSizeOf)]
-    pub flags ElementState: u8 {
+    pub flags ElementState: u16 {
         #[doc = "The mouse is down on this element. \
                  https://html.spec.whatwg.org/multipage/#selector-active \
                  FIXME(#7333): set/unset this when appropriate"]
         const IN_ACTIVE_STATE = 0x01,
         #[doc = "This element has focus. \
                  https://html.spec.whatwg.org/multipage/#selector-focus"]
         const IN_FOCUS_STATE = 0x02,
         #[doc = "The mouse is hovering over this element. \
@@ -24,10 +24,12 @@ bitflags! {
         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,
+        #[doc = "https://html.spec.whatwg.org/multipage/#selector-placeholder-shown"]
+        const IN_PLACEHOLDER_SHOWN_STATE = 0x0100,
     }
 }
--- a/servo/components/style/selector_impl.rs
+++ b/servo/components/style/selector_impl.rs
@@ -126,32 +126,34 @@ pub enum NonTSPseudoClass {
     Focus,
     Hover,
     Enabled,
     Disabled,
     Checked,
     Indeterminate,
     ServoNonZeroBorder,
     ReadWrite,
-    ReadOnly
+    ReadOnly,
+    PlaceholderShown,
 }
 
 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,
+            PlaceholderShown => IN_PLACEHOLDER_SHOWN_STATE,
 
             AnyLink |
             Link |
             Visited |
             ServoNonZeroBorder => ElementState::empty(),
         }
     }
 }
@@ -174,16 +176,17 @@ impl SelectorImpl for ServoSelectorImpl 
             "focus" => Focus,
             "hover" => Hover,
             "enabled" => Enabled,
             "disabled" => Disabled,
             "checked" => Checked,
             "indeterminate" => Indeterminate,
             "read-write" => ReadWrite,
             "read-only" => ReadOnly,
+            "placeholder-shown" => PlaceholderShown,
             "-servo-nonzero-border" => {
                 if !context.in_user_agent_stylesheet {
                     return Err(());
                 }
                 ServoNonZeroBorder
             },
             _ => return Err(())
         };
--- a/servo/ports/geckolib/wrapper.rs
+++ b/servo/ports/geckolib/wrapper.rs
@@ -347,17 +347,17 @@ impl<'le> TElement for GeckoElement<'le>
         let extra_data = ParserContextExtraData::default();
         attr.map(|v| parse_style_attribute(&v, &base_url, Box::new(StdoutErrorReporter),
                                            extra_data))
         */
     }
 
     fn get_state(&self) -> ElementState {
         unsafe {
-            ElementState::from_bits_truncate(Gecko_ElementState(self.element))
+            ElementState::from_bits_truncate(Gecko_ElementState(self.element) as u16)
         }
     }
 
     #[inline]
     fn get_attr<'a>(&'a self, namespace: &Namespace, name: &Atom) -> Option<&'a str> {
         unsafe {
             let mut length: u32 = 0;
             let ptr = Gecko_GetAttrAsUTF8(self.element, namespace.0.as_ptr(), name.as_ptr(),