Bug 1341086 - Part 2: stylo: Support all non-ts pseudos with an argument; r?bholley draft
authorManish Goregaokar <manishearth@gmail.com>
Thu, 16 Mar 2017 14:10:22 -0700
changeset 500286 a4ff1ac4830d9732296abea6b60c5ddaded7cb2b
parent 500285 05e7de4e82a4e6278b2f32a93cef77f8e34e8607
child 549592 0e6a691df793c777fa8338d7393131916597ad6e
push id49673
push userbmo:manishearth@gmail.com
push dateFri, 17 Mar 2017 00:03:54 +0000
reviewersbholley
bugs1341086
milestone55.0a1
Bug 1341086 - Part 2: stylo: Support all non-ts pseudos with an argument; r?bholley 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/nsCSSRuleProcessor.cpp
layout/style/nsCSSRuleProcessor.h
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
--- a/dom/base/nsDocument.cpp
+++ b/dom/base/nsDocument.cpp
@@ -9585,36 +9585,48 @@ 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::GetPossiblyStaleDocumentState() const
+{
   return mDocumentState;
 }
 
+EventStates
+nsDocument::GetDocumentState()
+{
+  UpdatePossiblyStaleDocumentState();
+  return GetPossiblyStaleDocumentState();
+}
+
 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
@@ -974,16 +974,18 @@ public:
                             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 GetPossiblyStaleDocumentState() const override;
+  virtual void UpdatePossiblyStaleDocumentState() override;
 
   // 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;
--- a/dom/base/nsIDocument.h
+++ b/dom/base/nsIDocument.h
@@ -2373,16 +2373,18 @@ 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 GetPossiblyStaleDocumentState() const = 0;
+  virtual void UpdatePossiblyStaleDocumentState() = 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,31 @@ 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,
+                           RawGeckoDocumentBorrowed aDocument,
+                           bool* aSetSlowSelectorFlag)
+{
+  EventStates dummyMask; // mask is never read because we pass aDependence=nullptr
+  auto matches = nsCSSRuleProcessor::StringPseudoMatches(aElement, aType, aIdent,
+                                                         aDocument, true,
+                                                         dummyMask, nullptr, aSetSlowSelectorFlag);
+  MOZ_ASSERT(matches, "Servo should never try to match against a non-string-arg pseudo");
+  return *matches;
+}
+
 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,22 @@ 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,
+                                RawGeckoDocumentBorrowed document,
+                                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/nsCSSRuleProcessor.cpp
+++ b/layout/style/nsCSSRuleProcessor.cpp
@@ -1634,29 +1634,30 @@ StateSelectorMatches(Element* aElement,
       return false;
     }
   }
   return true;
 }
 
 /* static */
 mozilla::Maybe<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)
 {
   switch (aPseudo) {
     case CSSPseudoClassType::mozLocaleDir:
       {
         bool docIsRTL =
-          aDocument->GetDocumentState().
+          aDocument->GetPossiblyStaleDocumentState().
             HasState(NS_DOCUMENT_STATE_RTL_LOCALE);
 
         nsDependentString dirString(aString);
 
         if (dirString.EqualsLiteral("rtl")) {
           if (!docIsRTL) {
             return Some(false);
           }
@@ -1687,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 != nullptr) {
           return Some(false);
@@ -2136,23 +2137,29 @@ static bool SelectorMatches(Element* aEl
           return false;
         }
       }
       break;
 
     default:
       {
         if (nsCSSPseudoClasses::HasStringArg(pseudoClass->mType)) {
+          bool setSlowSelectorFlag = false;
+          aTreeMatchContext.mDocument->UpdatePossiblyStaleDocumentState();
           auto result = nsCSSRuleProcessor::StringPseudoMatches(aElement,
                                                                 pseudoClass->mType,
                                                                 pseudoClass->u.mString,
                                                                 aTreeMatchContext.mDocument,
                                                                 aTreeMatchContext.mForStyling,
                                                                 aNodeMatchContext.mStateMask,
+                                                                &setSlowSelectorFlag,
                                                                 aDependence);
+          if (setSlowSelectorFlag) {
+            aElement->SetFlags(NODE_HAS_SLOW_SELECTOR);
+          }
 
           // Did we find a pseudo successfully?
           if (result) {
             if (!*result) {
               return false;
             }
             // else continue, we have a match!
           } else {
--- a/layout/style/nsCSSRuleProcessor.h
+++ b/layout/style/nsCSSRuleProcessor.h
@@ -135,22 +135,23 @@ public:
                                         TreeMatchContext& aTreeMatchContext);
   /**
    * Checks if a function-like ident-containing pseudo (:pseudo(ident))
    * matches a given element.
    *
    * Returns Some(true) if it parses and matches, Some(false) if it
    * parses but does not match, and Nothing() if it fails to parse.
    */
-  static mozilla::Maybe<bool> StringPseudoMatches(mozilla::dom::Element* aElement,
+  static mozilla::Maybe<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);
 
   // static mozilla::Maybe<bool> StringPseudoMatches(mozilla::dom::Element* aElement,
   //                                                 mozilla::CSSPseudoClassType aPseudo,
   //                                                 char16_t* aString,
   //                                                 TreeMatchContext& aTreeMatchContext);
 
   // nsIStyleRuleProcessor
--- 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,32 @@ 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 {
         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))
+                        return dest.write_str(&format!(":{}({})", $s_css, String::from_utf16(&s).unwrap()))
                     }, )*
                 }
             }
         }
         let ser = apply_non_ts_list!(pseudo_class_serialize);
         dest.write_str(ser)
     }
 }
@@ -285,18 +285,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;
@@ -657,17 +658,34 @@ 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(),
+                                       self.as_node().owner_doc(), &mut set_slow_selector);
+                    if set_slow_selector {
+                        self.set_selector_flags(HAS_SLOW_SELECTOR);
+                    }
+                    matches
+                }
+            }
         }
     }
 
     fn get_id(&self) -> Option<Atom> {
         let ptr = unsafe {
             bindings::Gecko_AtomAttrValue(self.0,
                                           atom!("id").as_ptr())
         };
--- a/servo/components/style/gecko_bindings/bindings.rs
+++ b/servo/components/style/gecko_bindings/bindings.rs
@@ -982,16 +982,23 @@ 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,
+                                      document: RawGeckoDocumentBorrowed,
+                                      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);
 }