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 394094 4dffb174c94d3f999a7fd2be303ba5f6c0be5058
parent 394093 8894970bdee6fd843deb6f50ec0f7bc33b4214c0
child 394095 1aecc3e0302853e1bdcffc924d50ecdff8e26101
push id1468
push userasasaki@mozilla.com
push dateMon, 05 Jun 2017 19:31:07 +0000
treeherdermozilla-release@0641fc6ee9d1 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersemilio
bugs15464
milestone54.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
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() {