Bug 1341086 - Part 2: stylo: Support all non-ts pseudos with an argument; r?emilio draft
authorManish Goregaokar <manishearth@gmail.com>
Thu, 16 Mar 2017 14:10:22 -0700
changeset 501745 5303a457482c2fd52680e95afadb4953fc3b09e5
parent 501728 8c81a7eb329139571b8a94f0a63824eeed584303
child 501756 1fc0043637b82fc22c325ed59fe3c18e77f8b1b1
child 501757 0a5a6d4e38cf9be6e23963e01f28676987c56f16
child 501877 fe65027400a252c7fe1785735c4e888d246e8074
push id50106
push userbmo:manishearth@gmail.com
push dateMon, 20 Mar 2017 22:44:40 +0000
reviewersemilio
bugs1341086
milestone55.0a1
Bug 1341086 - Part 2: stylo: Support all non-ts pseudos with an argument; r?emilio MozReview-Commit-ID: IHjX2Q3k8eD
dom/base/nsDocument.cpp
dom/base/nsDocument.h
dom/base/nsIDocument.h
layout/style/ServoBindings.cpp
layout/style/ServoBindings.h
layout/style/ServoStyleSet.cpp
layout/style/nsCSSRuleProcessor.cpp
layout/style/nsCSSRuleProcessor.h
servo/components/selectors/matching.rs
servo/components/selectors/tree.rs
servo/components/style/gecko/non_ts_pseudo_class_list.rs
servo/components/style/gecko/selector_parser.rs
servo/components/style/gecko/wrapper.rs
servo/components/style/gecko_bindings/bindings.rs
servo/components/style/restyle_hints.rs
servo/components/style/servo/selector_parser.rs
--- a/dom/base/nsDocument.cpp
+++ b/dom/base/nsDocument.cpp
@@ -9585,36 +9585,49 @@ nsDocument::ForgetImagePreload(nsIURI* a
     mPreloadingImages.Remove(aURI, getter_AddRefs(req));
     if (req) {
       // Make sure to cancel the request so imagelib knows it's gone.
       req->CancelAndForgetObserver(NS_BINDING_ABORTED);
     }
   }
 }
 
-EventStates
-nsDocument::GetDocumentState()
+void
+nsDocument::UpdatePossiblyStaleDocumentState()
 {
   if (!mGotDocumentState.HasState(NS_DOCUMENT_STATE_RTL_LOCALE)) {
     if (IsDocumentRightToLeft()) {
       mDocumentState |= NS_DOCUMENT_STATE_RTL_LOCALE;
     }
     mGotDocumentState |= NS_DOCUMENT_STATE_RTL_LOCALE;
   }
   if (!mGotDocumentState.HasState(NS_DOCUMENT_STATE_WINDOW_INACTIVE)) {
     nsIPresShell* shell = GetShell();
     if (shell && shell->GetPresContext() &&
         shell->GetPresContext()->IsTopLevelWindowInactive()) {
       mDocumentState |= NS_DOCUMENT_STATE_WINDOW_INACTIVE;
     }
     mGotDocumentState |= NS_DOCUMENT_STATE_WINDOW_INACTIVE;
   }
+}
+
+EventStates
+nsDocument::ThreadSafeGetDocumentState() const
+{
+  MOZ_ASSERT(mGotDocumentState.HasState(NS_DOCUMENT_STATE_ALL_BITS));
   return mDocumentState;
 }
 
+EventStates
+nsDocument::GetDocumentState()
+{
+  UpdatePossiblyStaleDocumentState();
+  return ThreadSafeGetDocumentState();
+}
+
 namespace {
 
 /**
  * Stub for LoadSheet(), since all we want is to get the sheet into
  * the CSSLoader's style cache
  */
 class StubCSSLoaderObserver final : public nsICSSLoaderObserver {
   ~StubCSSLoaderObserver() {}
--- a/dom/base/nsDocument.h
+++ b/dom/base/nsDocument.h
@@ -973,17 +973,22 @@ public:
                             ReferrerPolicy aReferrerPolicy,
                             const nsAString& aIntegrity) override;
 
   virtual nsresult LoadChromeSheetSync(nsIURI* uri, bool isAgentSheet,
                                        RefPtr<mozilla::StyleSheet>* aSheet) override;
 
   virtual nsISupports* GetCurrentContentSink() override;
 
-  virtual mozilla::EventStates GetDocumentState() override;
+  virtual mozilla::EventStates GetDocumentState() final;
+  // GetDocumentState() mutates the state due to lazy resolution;
+  // and can't be used during parallel traversal. Use this instead,
+  // and ensure GetDocumentState() has been called first.
+  // This will assert if the state is stale.
+  virtual mozilla::EventStates ThreadSafeGetDocumentState() const final;
 
   // Only BlockOnload should call this!
   void AsyncBlockOnload();
 
   virtual void SetScrollToRef(nsIURI *aDocumentURI) override;
   virtual void ScrollToRef() override;
   virtual void ResetScrolledToRefAlready() override;
   virtual void SetChangeScrollPosWhenScrollingToRef(bool aValue) override;
@@ -1384,16 +1389,17 @@ protected:
   // non-null when this document is in fullscreen mode.
   nsWeakPtr mFullscreenRoot;
 
   mozilla::dom::FlashClassification mFlashClassification;
   // Do not use this value directly. Call the |IsThirdParty()| method, which
   // caches its result here.
   mozilla::Maybe<bool> mIsThirdParty;
 private:
+  void UpdatePossiblyStaleDocumentState();
   static bool CustomElementConstructor(JSContext* aCx, unsigned aArgc, JS::Value* aVp);
 
   /**
    * Check if the passed custom element name, aOptions.mIs, is a registered
    * custom element type or not, then return the custom element name for future
    * usage.
    *
    * If there is no existing custom element definition for this name, throw a
--- a/dom/base/nsIDocument.h
+++ b/dom/base/nsIDocument.h
@@ -194,16 +194,18 @@ enum class HSTSPrimingState {
 
 // Document states
 
 // RTL locale: specific to the XUL localedir attribute
 #define NS_DOCUMENT_STATE_RTL_LOCALE              NS_DEFINE_EVENT_STATE_MACRO(0)
 // Window activation status
 #define NS_DOCUMENT_STATE_WINDOW_INACTIVE         NS_DEFINE_EVENT_STATE_MACRO(1)
 
+#define NS_DOCUMENT_STATE_ALL_BITS NS_DOCUMENT_STATE_RTL_LOCALE | NS_DOCUMENT_STATE_WINDOW_INACTIVE
+
 // Some function forward-declarations
 class nsContentList;
 
 //----------------------------------------------------------------------
 
 // Document interface.  This is implemented by all document objects in
 // Gecko.
 class nsIDocument : public nsINode,
@@ -2373,16 +2375,17 @@ public:
   virtual int GetDocumentLWTheme() { return Doc_Theme_None; }
 
   /**
    * Returns the document state.
    * Document state bits have the form NS_DOCUMENT_STATE_* and are declared in
    * nsIDocument.h.
    */
   virtual mozilla::EventStates GetDocumentState() = 0;
+  virtual mozilla::EventStates ThreadSafeGetDocumentState() const = 0;
 
   virtual nsISupports* GetCurrentContentSink() = 0;
 
   virtual void SetScrollToRef(nsIURI *aDocumentURI) = 0;
   virtual void ScrollToRef() = 0;
   virtual void ResetScrolledToRefAlready() = 0;
   virtual void SetChangeScrollPosWhenScrollingToRef(bool aValue) = 0;
 
--- a/layout/style/ServoBindings.cpp
+++ b/layout/style/ServoBindings.cpp
@@ -491,16 +491,29 @@ nscolor Gecko_GetLookAndFeelSystemColor(
 {
   bool useStandinsForNativeColors = aPresContext && !aPresContext->IsChrome();
   nscolor result;
   LookAndFeel::ColorID colorId = static_cast<LookAndFeel::ColorID>(aId);
   LookAndFeel::GetColor(colorId, useStandinsForNativeColors, &result);
   return result;
 }
 
+bool
+Gecko_MatchStringArgPseudo(RawGeckoElementBorrowed aElement,
+                           CSSPseudoClassType aType,
+                           const char16_t* aIdent,
+                           bool* aSetSlowSelectorFlag)
+{
+  MOZ_ASSERT(aElement->OwnerDoc() == aElement->GetComposedDoc());
+  EventStates dummyMask; // mask is never read because we pass aDependence=nullptr
+  return nsCSSRuleProcessor::StringPseudoMatches(aElement, aType, aIdent,
+                                                 aElement->OwnerDoc(), true,
+                                                 dummyMask, aSetSlowSelectorFlag, nullptr);
+}
+
 template <typename Implementor>
 static nsIAtom*
 AtomAttrValue(Implementor* aElement, nsIAtom* aName)
 {
   const nsAttrValue* attr = aElement->GetParsedAttr(aName);
   return attr ? attr->GetAtomValue() : nullptr;
 }
 
--- a/layout/style/ServoBindings.h
+++ b/layout/style/ServoBindings.h
@@ -371,16 +371,21 @@ void Gecko_nsStyleFont_CopyLangFrom(nsSt
 
 const nsMediaFeature* Gecko_GetMediaFeatures();
 
 // We use an int32_t here instead of a LookAndFeel::ColorID
 // because forward-declaring a nested enum/struct is impossible
 nscolor Gecko_GetLookAndFeelSystemColor(int32_t color_id,
                                         RawGeckoPresContextBorrowed pres_context);
 
+bool Gecko_MatchStringArgPseudo(RawGeckoElementBorrowed element,
+                                mozilla::CSSPseudoClassType type,
+                                const char16_t* ident,
+                                bool* set_slow_selector);
+
 // Style-struct management.
 #define STYLE_STRUCT(name, checkdata_cb)                                       \
   void Gecko_Construct_Default_nsStyle##name(                                  \
     nsStyle##name* ptr,                                                        \
     RawGeckoPresContextBorrowed pres_context);                                 \
   void Gecko_CopyConstruct_nsStyle##name(nsStyle##name* ptr,                   \
                                          const nsStyle##name* other);          \
   void Gecko_Destroy_nsStyle##name(nsStyle##name* ptr);
--- a/layout/style/ServoStyleSet.cpp
+++ b/layout/style/ServoStyleSet.cpp
@@ -178,16 +178,20 @@ ServoStyleSet::ResolveMappedAttrDeclarat
   mPresContext->Document()->ResolveScheduledSVGPresAttrs();
 }
 
 void
 ServoStyleSet::PreTraverse()
 {
   ResolveMappedAttrDeclarationBlocks();
 
+  // This is lazily computed and pseudo matching needs to access
+  // it so force computation early.
+  mPresContext->Document()->GetDocumentState();
+
   // Process animation stuff that we should avoid doing during the parallel
   // traversal.
   mPresContext->EffectCompositor()->PreTraverse();
 }
 
 bool
 ServoStyleSet::PrepareAndTraverseSubtree(RawGeckoElementBorrowed aRoot,
                                          mozilla::TraversalRootBehavior aRootBehavior)
--- a/layout/style/nsCSSRuleProcessor.cpp
+++ b/layout/style/nsCSSRuleProcessor.cpp
@@ -1633,30 +1633,32 @@ StateSelectorMatches(Element* aElement,
                               statesToCheck)) {
       return false;
     }
   }
   return true;
 }
 
 /* static */ bool
-nsCSSRuleProcessor::StringPseudoMatches(mozilla::dom::Element* aElement,
+nsCSSRuleProcessor::StringPseudoMatches(const mozilla::dom::Element* aElement,
                                         CSSPseudoClassType aPseudo,
-                                        char16_t* aString,
-                                        nsIDocument* aDocument,
+                                        const char16_t* aString,
+                                        const nsIDocument* aDocument,
                                         bool aForStyling,
                                         EventStates aStateMask,
+                                        bool* aSetSlowSelectorFlag,
                                         bool* const aDependence)
 {
+  MOZ_ASSERT(aSetSlowSelectorFlag);
+
   switch (aPseudo) {
     case CSSPseudoClassType::mozLocaleDir:
       {
         bool docIsRTL =
-          aDocument->GetDocumentState().
-            HasState(NS_DOCUMENT_STATE_RTL_LOCALE);
+          aDocument->ThreadSafeGetDocumentState().HasState(NS_DOCUMENT_STATE_RTL_LOCALE);
 
         nsDependentString dirString(aString);
 
         if (dirString.EqualsLiteral("rtl")) {
           if (!docIsRTL) {
             return false;
           }
         } else if (dirString.EqualsLiteral("ltr")) {
@@ -1686,17 +1688,17 @@ nsCSSRuleProcessor::StringPseudoMatches(
         int32_t index = -1;
 
         if (aForStyling) {
           // FIXME:  This isn't sufficient to handle:
           //   :-moz-empty-except-children-with-localname() + E
           //   :-moz-empty-except-children-with-localname() ~ E
           // because we don't know to restyle the grandparent of the
           // inserted/removed element (as in bug 534804 for :empty).
-          aElement->SetFlags(NODE_HAS_SLOW_SELECTOR);
+          *aSetSlowSelectorFlag = true;
         }
         do {
           child = aElement->GetChildAt(++index);
         } while (child &&
                   (!IsSignificantChild(child, true, false) ||
                   (child->GetNameSpaceID() == aElement->GetNameSpaceID() &&
                     child->NodeInfo()->NameAtom()->Equals(nsDependentString(aString)))));
         if (child) {
@@ -2136,23 +2138,30 @@ static bool SelectorMatches(Element* aEl
           return false;
         }
       }
       break;
 
     default:
       {
         MOZ_ASSERT(nsCSSPseudoClasses::HasStringArg(pseudoClass->mType));
+        bool setSlowSelectorFlag = false;
+        // force computation of document state
+        aTreeMatchContext.mDocument->GetDocumentState();
         bool matched = nsCSSRuleProcessor::StringPseudoMatches(aElement,
                                                                pseudoClass->mType,
                                                                pseudoClass->u.mString,
                                                                aTreeMatchContext.mDocument,
                                                                aTreeMatchContext.mForStyling,
                                                                aNodeMatchContext.mStateMask,
+                                                               &setSlowSelectorFlag,
                                                                aDependence);
+        if (setSlowSelectorFlag) {
+          aElement->SetFlags(NODE_HAS_SLOW_SELECTOR);
+        }
 
         if (!matched) {
           return false;
         }
       }
     }
   }
 
--- a/layout/style/nsCSSRuleProcessor.h
+++ b/layout/style/nsCSSRuleProcessor.h
@@ -151,22 +151,23 @@ public:
    * @param aDocument The document
    * @param aForStyling Is this matching operation for the creation of a style context?
    *                    (For setting the slow selector flag)
    * @param aStateMask Mask containing states which we should exclude.
    *                   Ignored if aDependence is null
    * @param aDependence Pointer to be set to true if we ignored a state due to
    *                    aStateMask. Can be null.
    */
-  static bool StringPseudoMatches(mozilla::dom::Element* aElement,
+  static bool StringPseudoMatches(const mozilla::dom::Element* aElement,
                                   mozilla::CSSPseudoClassType aPseudo,
-                                  char16_t* aString,
-                                  nsIDocument* aDocument,
+                                  const char16_t* aString,
+                                  const nsIDocument* aDocument,
                                   bool aForStyling,
                                   mozilla::EventStates aStateMask,
+                                  bool* aSetSlowSelectorFlag,
                                   bool* const aDependence = nullptr);
 
   // nsIStyleRuleProcessor
   virtual void RulesMatching(ElementRuleProcessorData* aData) override;
 
   virtual void RulesMatching(PseudoElementRuleProcessorData* aData) override;
 
   virtual void RulesMatching(AnonBoxRuleProcessorData* aData) override;
--- a/servo/components/selectors/matching.rs
+++ b/servo/components/selectors/matching.rs
@@ -408,17 +408,17 @@ fn matches_simple_selector<E>(
         }
         SimpleSelector::AttrIncludesNeverMatch(..) |
         SimpleSelector::AttrPrefixNeverMatch(..) |
         SimpleSelector::AttrSubstringNeverMatch(..) |
         SimpleSelector::AttrSuffixNeverMatch(..) => {
             false
         }
         SimpleSelector::NonTSPseudoClass(ref pc) => {
-            relation_if!(element.match_non_ts_pseudo_class(pc),
+            relation_if!(element.match_non_ts_pseudo_class(pc, flags),
                          AFFECTED_BY_STATE)
         }
         SimpleSelector::FirstChild => {
             relation_if!(matches_first_child(element, flags), AFFECTED_BY_CHILD_INDEX)
         }
         SimpleSelector::LastChild => {
             relation_if!(matches_last_child(element, flags), AFFECTED_BY_CHILD_INDEX)
         }
--- a/servo/components/selectors/tree.rs
+++ b/servo/components/selectors/tree.rs
@@ -1,15 +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/. */
 
 //! Traits that nodes must implement. Breaks the otherwise-cyclic dependency between layout and
 //! style.
 
+use matching::ElementSelectorFlags;
 use parser::{AttrSelector, SelectorImpl};
 use std::ascii::AsciiExt;
 
 /// The definition of whitespace per CSS Selectors Level 3 ยง 4.
 pub static SELECTOR_WHITESPACE: &'static [char] = &[' ', '\t', '\n', '\r', '\x0C'];
 
 // Attribute matching routines. Consumers with simple implementations can implement
 // MatchAttrGeneric instead.
@@ -133,17 +134,18 @@ pub trait Element: MatchAttr + Sized {
 
     // Skips non-element nodes
     fn next_sibling_element(&self) -> Option<Self>;
 
     fn is_html_element_in_html_document(&self) -> bool;
     fn get_local_name(&self) -> &<Self::Impl as SelectorImpl>::BorrowedLocalName;
     fn get_namespace(&self) -> &<Self::Impl as SelectorImpl>::BorrowedNamespaceUrl;
 
-    fn match_non_ts_pseudo_class(&self, pc: &<Self::Impl as SelectorImpl>::NonTSPseudoClass) -> bool;
+    fn match_non_ts_pseudo_class(&self, pc: &<Self::Impl as SelectorImpl>::NonTSPseudoClass,
+                                 flags: &mut ElementSelectorFlags) -> bool;
 
     fn get_id(&self) -> Option<<Self::Impl as SelectorImpl>::Identifier>;
     fn has_class(&self, name: &<Self::Impl as SelectorImpl>::ClassName) -> bool;
 
     /// Returns whether this element matches `:empty`.
     ///
     /// That is, whether it does not contain any child element or any non-zero-length text node.
     /// See http://dev.w3.org/csswg/selectors-3/#empty-pseudo
--- a/servo/components/style/gecko/non_ts_pseudo_class_list.rs
+++ b/servo/components/style/gecko/non_ts_pseudo_class_list.rs
@@ -47,12 +47,16 @@ macro_rules! apply_non_ts_list {
                 ("read-write", ReadWrite, _, IN_READ_WRITE_STATE, _),
                 ("read-only", ReadOnly, _, IN_READ_WRITE_STATE, _),
 
                 ("-moz-browser-frame", MozBrowserFrame, mozBrowserFrame, _, PSEUDO_CLASS_INTERNAL),
                 ("-moz-table-border-nonzero", MozTableBorderNonzero, mozTableBorderNonzero, _, PSEUDO_CLASS_INTERNAL),
             ],
             string: [
                 ("-moz-system-metric", MozSystemMetric, mozSystemMetric, _, PSEUDO_CLASS_INTERNAL),
+                ("-moz-locale-dir", MozLocaleDir, mozLocaleDir, _, PSEUDO_CLASS_INTERNAL),
+                ("-moz-empty-except-children-with-localname", MozEmptyExceptChildrenWithLocalname, mozEmptyExceptChildrenWithLocalname, _, PSEUDO_CLASS_INTERNAL),
+                ("dir", Dir, dir, _, _),
+                ("lang", Lang, lang, _, _),
             ]
         }
     }
 }
--- a/servo/components/style/gecko/selector_parser.rs
+++ b/servo/components/style/gecko/selector_parser.rs
@@ -145,32 +145,39 @@ macro_rules! pseudo_class_name {
         #[derive(Clone, Debug, PartialEq, Eq, Hash)]
         pub enum NonTSPseudoClass {
             $(
                 #[doc = $css]
                 $name,
             )*
             $(
                 #[doc = $s_css]
-                $s_name(Box<str>),
+                $s_name(Box<[u16]>),
             )*
         }
     }
 }
 apply_non_ts_list!(pseudo_class_name);
 
 impl ToCss for NonTSPseudoClass {
     fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
+        use cssparser::CssStringWriter;
+        use fmt::Write;
         macro_rules! pseudo_class_serialize {
             (bare: [$(($css:expr, $name:ident, $gecko_type:tt, $state:tt, $flags:tt),)*],
              string: [$(($s_css:expr, $s_name:ident, $s_gecko_type:tt, $s_state:tt, $s_flags:tt),)*]) => {
                 match *self {
                     $(NonTSPseudoClass::$name => concat!(":", $css),)*
                     $(NonTSPseudoClass::$s_name(ref s) => {
-                        return dest.write_str(&format!(":{}({})", $s_css, s))
+                        write!(dest, ":{}(", $s_css)?;
+                        {
+                            let mut css = CssStringWriter::new(dest);
+                            css.write_str(&String::from_utf16(&s).unwrap())?;
+                        }
+                        return dest.write_str(")")
                     }, )*
                 }
             }
         }
         let ser = apply_non_ts_list!(pseudo_class_serialize);
         dest.write_str(ser)
     }
 }
@@ -285,18 +292,21 @@ impl<'a> ::selectors::Parser for Selecto
                                             name: Cow<str>,
                                             parser: &mut Parser)
                                             -> Result<NonTSPseudoClass, ()> {
         macro_rules! pseudo_class_string_parse {
             (bare: [$(($css:expr, $name:ident, $gecko_type:tt, $state:tt, $flags:tt),)*],
              string: [$(($s_css:expr, $s_name:ident, $s_gecko_type:tt, $s_state:tt, $s_flags:tt),)*]) => {
                 match_ignore_ascii_case! { &name,
                     $($s_css => {
-                        let name = String::from(parser.expect_ident_or_string()?).into_boxed_str();
-                        NonTSPseudoClass::$s_name(name)
+                        let name = parser.expect_ident_or_string()?;
+                        // convert to null terminated utf16 string
+                        // since that's what Gecko deals with
+                        let utf16: Vec<u16> = name.encode_utf16().chain(Some(0u16)).collect();
+                        NonTSPseudoClass::$s_name(utf16.into_boxed_slice())
                     }, )*
                     _ => return Err(())
                 }
             }
         }
         let pseudo_class = apply_non_ts_list!(pseudo_class_string_parse);
         if !pseudo_class.is_internal() || self.in_user_agent_stylesheet() {
             Ok(pseudo_class)
--- a/servo/components/style/gecko/wrapper.rs
+++ b/servo/components/style/gecko/wrapper.rs
@@ -29,16 +29,17 @@ use gecko_bindings::bindings::{Gecko_IsL
 use gecko_bindings::bindings::{Gecko_IsUnvisitedLink, Gecko_IsVisitedLink, Gecko_Namespace};
 use gecko_bindings::bindings::{Gecko_SetNodeFlags, Gecko_UnsetNodeFlags};
 use gecko_bindings::bindings::Gecko_ClassOrClassList;
 use gecko_bindings::bindings::Gecko_ElementHasCSSAnimations;
 use gecko_bindings::bindings::Gecko_GetAnimationRule;
 use gecko_bindings::bindings::Gecko_GetHTMLPresentationAttrDeclarationBlock;
 use gecko_bindings::bindings::Gecko_GetStyleAttrDeclarationBlock;
 use gecko_bindings::bindings::Gecko_GetStyleContext;
+use gecko_bindings::bindings::Gecko_MatchStringArgPseudo;
 use gecko_bindings::bindings::Gecko_UpdateAnimations;
 use gecko_bindings::structs;
 use gecko_bindings::structs::{RawGeckoElement, RawGeckoNode};
 use gecko_bindings::structs::{nsIAtom, nsIContent, nsStyleContext};
 use gecko_bindings::structs::EffectCompositor_CascadeLevel as CascadeLevel;
 use gecko_bindings::structs::NODE_HAS_DIRTY_DESCENDANTS_FOR_SERVO;
 use gecko_bindings::structs::NODE_IS_IN_NATIVE_ANONYMOUS_SUBTREE;
 use gecko_bindings::sugar::ownership::HasArcFFI;
@@ -632,17 +633,17 @@ impl<'le> ::selectors::Element for Gecko
     }
 
     fn get_namespace(&self) -> &WeakNamespace {
         unsafe {
             WeakNamespace::new(Gecko_Namespace(self.0))
         }
     }
 
-    fn match_non_ts_pseudo_class(&self, pseudo_class: &NonTSPseudoClass) -> bool {
+    fn match_non_ts_pseudo_class(&self, pseudo_class: &NonTSPseudoClass, flags: &mut ElementSelectorFlags) -> bool {
         match *pseudo_class {
             // https://github.com/servo/servo/issues/8718
             NonTSPseudoClass::AnyLink => unsafe { Gecko_IsLink(self.0) },
             NonTSPseudoClass::Link => unsafe { Gecko_IsUnvisitedLink(self.0) },
             NonTSPseudoClass::Visited => unsafe { Gecko_IsVisitedLink(self.0) },
             NonTSPseudoClass::Active |
             NonTSPseudoClass::Focus |
             NonTSPseudoClass::Hover |
@@ -657,17 +658,33 @@ impl<'le> ::selectors::Element for Gecko
             NonTSPseudoClass::ReadOnly => {
                 !self.get_state().contains(pseudo_class.state_flag())
             }
 
             NonTSPseudoClass::MozTableBorderNonzero |
             NonTSPseudoClass::MozBrowserFrame => unsafe {
                 Gecko_MatchesElement(pseudo_class.to_gecko_pseudoclasstype().unwrap(), self.0)
             },
-            _ => false
+            NonTSPseudoClass::MozSystemMetric(ref s) |
+            NonTSPseudoClass::MozLocaleDir(ref s) |
+            NonTSPseudoClass::MozEmptyExceptChildrenWithLocalname(ref s) |
+            NonTSPseudoClass::Dir(ref s) |
+            NonTSPseudoClass::Lang(ref s) => {
+                use selectors::matching::HAS_SLOW_SELECTOR;
+                unsafe {
+                    let mut set_slow_selector = false;
+                    let matches = Gecko_MatchStringArgPseudo(self.0,
+                                       pseudo_class.to_gecko_pseudoclasstype().unwrap(),
+                                       s.as_ptr(), &mut set_slow_selector);
+                    if set_slow_selector {
+                        *flags |= HAS_SLOW_SELECTOR;
+                    }
+                    matches
+                }
+            }
         }
     }
 
     fn get_id(&self) -> Option<Atom> {
         let ptr = unsafe {
             bindings::Gecko_AtomAttrValue(self.0,
                                           atom!("id").as_ptr())
         };
@@ -795,16 +812,16 @@ impl<'le> ::selectors::MatchAttr for Gec
                                           value.as_ptr())
         }
     }
 }
 
 impl<'le> ElementExt for GeckoElement<'le> {
     #[inline]
     fn is_link(&self) -> bool {
-        self.match_non_ts_pseudo_class(&NonTSPseudoClass::AnyLink)
+        self.match_non_ts_pseudo_class(&NonTSPseudoClass::AnyLink, &mut ElementSelectorFlags::empty())
     }
 
     #[inline]
     fn matches_user_and_author_rules(&self) -> bool {
         self.flags() & (NODE_IS_IN_NATIVE_ANONYMOUS_SUBTREE as u32) == 0
     }
 }
--- a/servo/components/style/gecko_bindings/bindings.rs
+++ b/servo/components/style/gecko_bindings/bindings.rs
@@ -982,16 +982,22 @@ extern "C" {
 }
 extern "C" {
     pub fn Gecko_GetLookAndFeelSystemColor(color_id: i32,
                                            pres_context:
                                                RawGeckoPresContextBorrowed)
      -> nscolor;
 }
 extern "C" {
+    pub fn Gecko_MatchStringArgPseudo(element: RawGeckoElementBorrowed,
+                                      type_: CSSPseudoClassType,
+                                      ident: *const u16,
+                                      set_slow_selector: *mut bool) -> bool;
+}
+extern "C" {
     pub fn Gecko_Construct_Default_nsStyleFont(ptr: *mut nsStyleFont,
                                                pres_context:
                                                    RawGeckoPresContextBorrowed);
 }
 extern "C" {
     pub fn Gecko_CopyConstruct_nsStyleFont(ptr: *mut nsStyleFont,
                                            other: *const nsStyleFont);
 }
--- a/servo/components/style/restyle_hints.rs
+++ b/servo/components/style/restyle_hints.rs
@@ -264,24 +264,24 @@ impl<'a, E> MatchAttr for ElementWrapper
             _   => self.element.match_attr_suffix(attr, value)
         }
     }
 }
 
 impl<'a, E> Element for ElementWrapper<'a, E>
     where E: TElement,
 {
-    fn match_non_ts_pseudo_class(&self, pseudo_class: &NonTSPseudoClass) -> bool {
+    fn match_non_ts_pseudo_class(&self, pseudo_class: &NonTSPseudoClass, flags: &mut ElementSelectorFlags) -> bool {
         let flag = SelectorImpl::pseudo_class_state_flag(pseudo_class);
         if flag == ElementState::empty() {
-            self.element.match_non_ts_pseudo_class(pseudo_class)
+            self.element.match_non_ts_pseudo_class(pseudo_class, flags)
         } else {
             match self.snapshot.and_then(|s| s.state()) {
                 Some(snapshot_state) => snapshot_state.contains(flag),
-                _   => self.element.match_non_ts_pseudo_class(pseudo_class)
+                _   => self.element.match_non_ts_pseudo_class(pseudo_class, flags)
             }
         }
     }
 
     fn parent_element(&self) -> Option<Self> {
         self.element.parent_element().map(ElementWrapper::new)
     }
 
--- a/servo/components/style/servo/selector_parser.rs
+++ b/servo/components/style/servo/selector_parser.rs
@@ -9,16 +9,17 @@
 use {Atom, Prefix, Namespace, LocalName};
 use attr::{AttrIdentifier, AttrValue};
 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::matching::ElementSelectorFlags;
 use selectors::parser::AttrSelector;
 use std::borrow::Cow;
 use std::fmt;
 use std::fmt::Debug;
 
 /// A pseudo-element, both public and private.
 ///
 /// NB: If you add to this list, be sure to update `each_pseudo_element` too.
@@ -445,16 +446,16 @@ impl MatchAttrGeneric for ServoElementSn
             NamespaceConstraint::Specific(ref ns) => self.get_attr(&ns.url, local_name),
             NamespaceConstraint::Any => self.get_attr_ignore_ns(local_name),
         }.map_or(false, |v| test(v))
     }
 }
 
 impl<E: Element<Impl=SelectorImpl> + Debug> ElementExt for E {
     fn is_link(&self) -> bool {
-        self.match_non_ts_pseudo_class(&NonTSPseudoClass::AnyLink)
+        self.match_non_ts_pseudo_class(&NonTSPseudoClass::AnyLink, &mut ElementSelectorFlags::empty())
     }
 
     #[inline]
     fn matches_user_and_author_rules(&self) -> bool {
         true
     }
 }