servo: Merge #15464 - Support lang pseudo class (from KiChjang:pseudo-lang); r=emilio
authorKeith Yeung <kungfukeith11@gmail.com>
Mon, 27 Feb 2017 17:21:50 -0800
changeset 374144 4dffb174c94d3f999a7fd2be303ba5f6c0be5058
parent 374143 8894970bdee6fd843deb6f50ec0f7bc33b4214c0
child 374145 1aecc3e0302853e1bdcffc924d50ecdff8e26101
push id10863
push userjlorenzo@mozilla.com
push dateMon, 06 Mar 2017 23:02:23 +0000
treeherdermozilla-aurora@0931190cd725 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersemilio
milestone54.0a1
servo: Merge #15464 - Support lang pseudo class (from KiChjang:pseudo-lang); r=emilio Fixes #8219. Source-Repo: https://github.com/servo/servo Source-Revision: 4b394312153c56812113c149df77a313939de30f
servo/components/script/dom/element.rs
servo/components/script/layout_wrapper.rs
servo/components/style/servo/selector_parser.rs
--- a/servo/components/script/dom/element.rs
+++ b/servo/components/script/dom/element.rs
@@ -349,16 +349,17 @@ pub trait LayoutElementHelpers {
     #[allow(unsafe_code)]
     unsafe fn get_rowspan(self) -> u32;
     #[allow(unsafe_code)]
     unsafe fn html_element_in_html_document_for_layout(&self) -> bool;
     fn id_attribute(&self) -> *const Option<Atom>;
     fn style_attribute(&self) -> *const Option<Arc<RwLock<PropertyDeclarationBlock>>>;
     fn local_name(&self) -> &LocalName;
     fn namespace(&self) -> &Namespace;
+    fn get_lang_for_layout(&self) -> String;
     fn get_checked_state_for_layout(&self) -> bool;
     fn get_indeterminate_state_for_layout(&self) -> bool;
     fn get_state_for_layout(&self) -> ElementState;
     fn insert_selector_flags(&self, flags: ElementSelectorFlags);
     fn has_selector_flags(&self, flags: ElementSelectorFlags) -> bool;
 }
 
 impl LayoutElementHelpers for LayoutJS<Element> {
@@ -688,16 +689,40 @@ impl LayoutElementHelpers for LayoutJS<E
 
     #[allow(unsafe_code)]
     fn namespace(&self) -> &Namespace {
         unsafe {
             &(*self.unsafe_get()).namespace
         }
     }
 
+    #[allow(unsafe_code)]
+    fn get_lang_for_layout(&self) -> String {
+        unsafe {
+            let mut current_node = Some(self.upcast::<Node>());
+            while let Some(node) = current_node {
+                current_node = node.parent_node_ref();
+                match node.downcast::<Element>().map(|el| el.unsafe_get()) {
+                    Some(elem) => {
+                        if let Some(attr) = (*elem).get_attr_val_for_layout(&ns!(xml), &local_name!("lang")) {
+                            return attr.to_owned();
+                        }
+                        if let Some(attr) = (*elem).get_attr_val_for_layout(&ns!(), &local_name!("lang")) {
+                            return attr.to_owned();
+                        }
+                    }
+                    None => continue
+                }
+            }
+            // TODO: Check meta tags for a pragma-set default language
+            // TODO: Check HTTP Content-Language header
+            String::new()
+        }
+    }
+
     #[inline]
     #[allow(unsafe_code)]
     fn get_checked_state_for_layout(&self) -> bool {
         // TODO option and menuitem can also have a checked state.
         match self.downcast::<HTMLInputElement>() {
             Some(input) => unsafe {
                 input.checked_state_for_layout()
             },
@@ -2367,16 +2392,20 @@ impl<'a> ::selectors::Element for Root<E
                         match this.get_border() {
                             None | Some(0) => false,
                             Some(_) => true,
                         }
                     }
                 }
             },
 
+            // FIXME(#15746): This is wrong, we need to instead use extended filtering as per RFC4647
+            //                https://tools.ietf.org/html/rfc4647#section-3.3.2
+            NonTSPseudoClass::Lang(ref lang) => lang.eq_ignore_ascii_case(&self.get_lang()),
+
             NonTSPseudoClass::ReadOnly =>
                 !Element::state(self).contains(pseudo_class.state_flag()),
 
             NonTSPseudoClass::Active |
             NonTSPseudoClass::Focus |
             NonTSPseudoClass::Fullscreen |
             NonTSPseudoClass::Hover |
             NonTSPseudoClass::Enabled |
@@ -2571,16 +2600,29 @@ impl Element {
             None => {
                 event.fire(target);
             }
         }
         // Step 7
         self.set_click_in_progress(false);
     }
 
+    // https://html.spec.whatwg.org/multipage/#language
+    pub fn get_lang(&self) -> String {
+        self.upcast::<Node>().inclusive_ancestors().filter_map(|node| {
+            node.downcast::<Element>().and_then(|el| {
+                el.get_attribute(&ns!(xml), &local_name!("lang")).or_else(|| {
+                    el.get_attribute(&ns!(), &local_name!("lang"))
+                }).map(|attr| String::from(attr.Value()))
+            })
+        // TODO: Check meta tags for a pragma-set default language
+        // TODO: Check HTTP Content-Language header
+        }).next().unwrap_or(String::new())
+    }
+
     pub fn state(&self) -> ElementState {
         self.state.get()
     }
 
     pub fn set_state(&self, which: ElementState, value: bool) {
         let mut state = self.state.get();
         if state.contains(which) == value {
             return;
--- a/servo/components/script/layout_wrapper.rs
+++ b/servo/components/script/layout_wrapper.rs
@@ -48,16 +48,17 @@ use range::Range;
 use script_layout_interface::{HTMLCanvasData, LayoutNodeType, SVGSVGData, TrustedNodeAddress};
 use script_layout_interface::{OpaqueStyleAndLayoutData, PartialPersistentLayoutData};
 use script_layout_interface::wrapper_traits::{DangerousThreadSafeLayoutNode, GetLayoutData, LayoutNode};
 use script_layout_interface::wrapper_traits::{PseudoElementType, ThreadSafeLayoutElement, ThreadSafeLayoutNode};
 use selectors::matching::ElementSelectorFlags;
 use selectors::parser::{AttrSelector, NamespaceConstraint};
 use servo_atoms::Atom;
 use servo_url::ServoUrl;
+use std::ascii::AsciiExt;
 use std::fmt;
 use std::fmt::Debug;
 use std::marker::PhantomData;
 use std::mem::transmute;
 use std::sync::Arc;
 use std::sync::atomic::Ordering;
 use style::attr::AttrValue;
 use style::computed_values::display;
@@ -613,16 +614,20 @@ impl<'le> ::selectors::Element for Servo
                     NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLAreaElement)) |
                     NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLLinkElement)) =>
                         (*self.element.unsafe_get()).get_attr_val_for_layout(&ns!(), &local_name!("href")).is_some(),
                     _ => false,
                 }
             },
             NonTSPseudoClass::Visited => false,
 
+            // FIXME(#15746): This is wrong, we need to instead use extended filtering as per RFC4647
+            //                https://tools.ietf.org/html/rfc4647#section-3.3.2
+            NonTSPseudoClass::Lang(ref lang) => lang.eq_ignore_ascii_case(&self.element.get_lang_for_layout()),
+
             NonTSPseudoClass::ServoNonZeroBorder => unsafe {
                 match (*self.element.unsafe_get()).get_attr_for_layout(&ns!(), &local_name!("border")) {
                     None | Some(&AttrValue::UInt(_, 0)) => false,
                     _ => true,
                 }
             },
 
             NonTSPseudoClass::ReadOnly =>
--- a/servo/components/style/servo/selector_parser.rs
+++ b/servo/components/style/servo/selector_parser.rs
@@ -3,17 +3,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #![deny(missing_docs)]
 
 //! Servo's selector parser.
 
 use {Atom, Prefix, Namespace, LocalName};
 use attr::{AttrIdentifier, AttrValue};
-use cssparser::ToCss;
+use cssparser::{Parser as CssParser, ToCss, serialize_identifier};
 use element_state::ElementState;
 use restyle_hints::ElementSnapshot;
 use selector_parser::{ElementExt, PseudoElementCascadeType, SelectorParser};
 use selector_parser::{attr_equals_selector_is_shareable, attr_exists_selector_is_shareable};
 use selectors::{Element, MatchAttrGeneric};
 use selectors::parser::AttrSelector;
 use std::borrow::Cow;
 use std::fmt;
@@ -95,54 +95,62 @@ impl PseudoElement {
 }
 
 /// A non tree-structural pseudo-class.
 /// See https://drafts.csswg.org/selectors-4/#structural-pseudos
 #[derive(Clone, Debug, PartialEq, Eq, Hash)]
 #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
 #[allow(missing_docs)]
 pub enum NonTSPseudoClass {
+    Active,
     AnyLink,
-    Link,
-    Visited,
-    Active,
+    Checked,
+    Disabled,
+    Enabled,
     Focus,
     Fullscreen,
     Hover,
-    Enabled,
-    Disabled,
-    Checked,
     Indeterminate,
-    ServoNonZeroBorder,
+    Lang(Box<str>),
+    Link,
+    PlaceholderShown,
     ReadWrite,
     ReadOnly,
-    PlaceholderShown,
+    ServoNonZeroBorder,
     Target,
+    Visited,
 }
 
 impl ToCss for NonTSPseudoClass {
     fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
         use self::NonTSPseudoClass::*;
+        if let Lang(ref lang) = *self {
+            dest.write_str(":lang(")?;
+            serialize_identifier(lang, dest)?;
+            return dest.write_str(")");
+        }
+
         dest.write_str(match *self {
+            Active => ":active",
             AnyLink => ":any-link",
-            Link => ":link",
-            Visited => ":visited",
-            Active => ":active",
+            Checked => ":checked",
+            Disabled => ":disabled",
+            Enabled => ":enabled",
             Focus => ":focus",
             Fullscreen => ":fullscreen",
             Hover => ":hover",
-            Enabled => ":enabled",
-            Disabled => ":disabled",
-            Checked => ":checked",
             Indeterminate => ":indeterminate",
+            Lang(_) => unreachable!(),
+            Link => ":link",
+            PlaceholderShown => ":placeholder-shown",
             ReadWrite => ":read-write",
             ReadOnly => ":read-only",
-            PlaceholderShown => ":placeholder-shown",
+            ServoNonZeroBorder => ":-servo-nonzero-border",
             Target => ":target",
-            ServoNonZeroBorder => ":-servo-nonzero-border",
+            Visited => ":visited",
         })
     }
 }
 
 impl NonTSPseudoClass {
     /// Gets a given state flag for this pseudo-class. This is used to do
     /// selector matching, and it's set from the DOM.
     pub fn state_flag(&self) -> ElementState {
@@ -157,16 +165,17 @@ impl NonTSPseudoClass {
             Disabled => IN_DISABLED_STATE,
             Checked => IN_CHECKED_STATE,
             Indeterminate => IN_INDETERMINATE_STATE,
             ReadOnly | ReadWrite => IN_READ_WRITE_STATE,
             PlaceholderShown => IN_PLACEHOLDER_SHOWN_STATE,
             Target => IN_TARGET_STATE,
 
             AnyLink |
+            Lang(_) |
             Link |
             Visited |
             ServoNonZeroBorder => ElementState::empty(),
         }
     }
 }
 
 /// The abstract struct we implement the selector parser implementation on top
@@ -199,43 +208,56 @@ impl ::selectors::SelectorImpl for Selec
 }
 
 impl<'a> ::selectors::Parser for SelectorParser<'a> {
     type Impl = SelectorImpl;
 
     fn parse_non_ts_pseudo_class(&self, name: Cow<str>) -> Result<NonTSPseudoClass, ()> {
         use self::NonTSPseudoClass::*;
         let pseudo_class = match_ignore_ascii_case! { &name,
+            "active" => Active,
             "any-link" => AnyLink,
-            "link" => Link,
-            "visited" => Visited,
-            "active" => Active,
+            "checked" => Checked,
+            "disabled" => Disabled,
+            "enabled" => Enabled,
             "focus" => Focus,
             "fullscreen" => Fullscreen,
             "hover" => Hover,
-            "enabled" => Enabled,
-            "disabled" => Disabled,
-            "checked" => Checked,
             "indeterminate" => Indeterminate,
+            "link" => Link,
+            "placeholder-shown" => PlaceholderShown,
             "read-write" => ReadWrite,
             "read-only" => ReadOnly,
-            "placeholder-shown" => PlaceholderShown,
             "target" => Target,
+            "visited" => Visited,
             "-servo-nonzero-border" => {
                 if !self.in_user_agent_stylesheet() {
                     return Err(());
                 }
                 ServoNonZeroBorder
             },
             _ => return Err(())
         };
 
         Ok(pseudo_class)
     }
 
+    fn parse_non_ts_functional_pseudo_class(&self,
+                                            name: Cow<str>,
+                                            parser: &mut CssParser)
+                                            -> Result<NonTSPseudoClass, ()> {
+        use self::NonTSPseudoClass::*;
+        let pseudo_class = match_ignore_ascii_case!{ &name,
+            "lang" => Lang(String::from(try!(parser.expect_ident_or_string())).into_boxed_str()),
+            _ => return Err(())
+        };
+
+        Ok(pseudo_class)
+    }
+
     fn parse_pseudo_element(&self, name: Cow<str>) -> Result<PseudoElement, ()> {
         use self::PseudoElement::*;
         let pseudo_element = match_ignore_ascii_case! { &name,
             "before" => Before,
             "after" => After,
             "selection" => Selection,
             "-servo-details-summary" => {
                 if !self.in_user_agent_stylesheet() {