Bug 922669 - Part 1: Parse selectors with user action pseudo-classes after pseudo-elements. r=bz
authorCameron McCormack <cam@mcc.id.au>
Thu, 28 Nov 2013 17:46:38 +1100
changeset 157866 3315c6b05d2351ef95c17894f92ff705ceca632d
parent 157865 d22b3851aff01fad7a4a2cf6159e56f65730ead9
child 157867 da0224b9ef9f96bebe907bf711deb95116361c4c
push id36877
push usercmccormack@mozilla.com
push dateThu, 28 Nov 2013 06:47:44 +0000
treeherdermozilla-inbound@3f7925e3933b [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbz
bugs922669
milestone28.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
Bug 922669 - Part 1: Parse selectors with user action pseudo-classes after pseudo-elements. r=bz
dom/locales/en-US/chrome/layout/css.properties
layout/style/StyleRule.cpp
layout/style/nsCSSParser.cpp
layout/style/nsCSSPseudoClasses.cpp
layout/style/nsCSSPseudoClasses.h
--- a/dom/locales/en-US/chrome/layout/css.properties
+++ b/dom/locales/en-US/chrome/layout/css.properties
@@ -61,28 +61,29 @@ PEAttSelNoClose=Expected ']' to terminat
 PEAttSelBadValue=Expected identifier or string for value in attribute selector but found '%1$S'.
 PEPseudoSelEOF=name of pseudo-class or pseudo-element
 PEPseudoSelBadName=Expected identifier for pseudo-class or pseudo-element but found '%1$S'.
 PEPseudoSelNonFunc=Function token for non-function pseudo-class or pseudo-element, or the other way around, when reading '%1$S'.
 PEPseudoSelNotPE=Expected pseudo-element but found '%1$S'.
 PEPseudoSelDoubleNot=Negation pseudo-class can't be negated '%1$S'.
 PEPseudoSelPEInNot=Pseudo-elements can't be negated '%1$S'.
 PEPseudoSelNewStyleOnly=This pseudo-element must use the "::" form: '%1$S'.
-PEPseudoSelTrailing=Found trailing token after pseudo-element, which must be the last part of a selector:  '%1$S'.
+PEPseudoSelTrailing=Expected end of selector or a user action pseudo-class after pseudo-element but found '%1$S'.
 PEPseudoSelMultiplePE=Extra pseudo-element '%1$S'.
 PEPseudoSelUnknown=Unknown pseudo-class or pseudo-element '%1$S'.
 PENegationEOF=selector within negation
 PENegationBadInner=Malformed simple selector as negation pseudo-class argument '%1$S'.
 PENegationNoClose=Missing closing ')' in negation pseudo-class '%1$S'.
 PENegationBadArg=Missing argument in negation pseudo-class '%1$S'.
 PEPseudoClassArgEOF=argument to pseudo-class selector
 PEPseudoClassArgNotIdent=Expected identifier for pseudo-class parameter but found '%1$S'.
 PEPseudoClassArgNotNth=Expected part of argument to pseudo-class but found '%1$S'.
 PEPseudoClassNoClose=Missing closing ')' in pseudo-class, found '%1$S' instead.
 PEPseudoClassNoArg=Missing argument in pseudo-class '%1$S'.
+PEPseudoClassNotUserAction=Expected end of selector or a user action pseudo-class after pseudo-element but found pseudo-class '%1$S'.
 PESelectorEOF=selector
 PEBadDeclBlockStart=Expected '{' to begin declaration block but found '%1$S'.
 PEColorEOF=color
 PEColorNotColor=Expected color but found '%1$S'.
 PEColorComponentEOF=color component
 PEExpectedPercent=Expected a percentage but found '%1$S'.
 PEExpectedInt=Expected an integer but found '%1$S'.
 PEColorBadRGBContents=Expected number or percentage in rgb() but found '%1$S'.
--- a/layout/style/StyleRule.cpp
+++ b/layout/style/StyleRule.cpp
@@ -468,17 +468,23 @@ void nsCSSSelector::SetOperator(PRUnicha
 {
   mOperator = aOperator;
 }
 
 int32_t nsCSSSelector::CalcWeightWithoutNegations() const
 {
   int32_t weight = 0;
 
-  if (nullptr != mLowercaseTag) {
+  MOZ_ASSERT(!IsPseudoElement() ||
+             mPseudoType >= nsCSSPseudoElements::ePseudo_PseudoElementCount ||
+             (!mIDList && !mClassList && !mAttrList),
+             "if pseudo-elements can have ID, class or attribute selectors "
+             "after them, specificity calculation must be updated");
+
+  if (nullptr != mCasedTag) {
     weight += 0x000001;
   }
   nsAtomList* list = mIDList;
   while (nullptr != list) {
     weight += 0x010000;
     list = list->mNext;
   }
   list = mClassList;
--- a/layout/style/nsCSSParser.cpp
+++ b/layout/style/nsCSSParser.cpp
@@ -3655,16 +3655,18 @@ CSSParserImpl::ParsePseudoSelector(int32
   }
 
   // stash away some info about this pseudo so we only have to get it once.
   bool isTreePseudo = false;
   nsCSSPseudoElements::Type pseudoElementType =
     nsCSSPseudoElements::GetPseudoType(pseudo);
   nsCSSPseudoClasses::Type pseudoClassType =
     nsCSSPseudoClasses::GetPseudoType(pseudo);
+  bool pseudoClassIsUserAction =
+    nsCSSPseudoClasses::IsUserActionPseudoClass(pseudoClassType);
 
   if (!mUnsafeRulesEnabled &&
       (pseudoElementType == nsCSSPseudoElements::ePseudo_mozNumberWrapper ||
        pseudoElementType == nsCSSPseudoElements::ePseudo_mozNumberText ||
        pseudoElementType == nsCSSPseudoElements::ePseudo_mozNumberSpinBox ||
        pseudoElementType == nsCSSPseudoElements::ePseudo_mozNumberSpinUp ||
        pseudoElementType == nsCSSPseudoElements::ePseudo_mozNumberSpinDown)) {
     // Hide these pseudo-elements from content until we standardize them.
@@ -3754,16 +3756,23 @@ CSSParserImpl::ParsePseudoSelector(int32
     // CSS 3 Negation pseudo-class takes one simple selector as argument
     nsSelectorParsingStatus parsingStatus =
       ParseNegatedSimpleSelector(aDataMask, aSelector);
     if (eSelectorParsingStatus_Continue != parsingStatus) {
       return parsingStatus;
     }
   }
   else if (!parsingPseudoElement && isPseudoClass) {
+    if (aSelector.IsPseudoElement() && !pseudoClassIsUserAction) {
+      // CSS 4 Selectors says that pseudo-elements can only be followed by
+      // a user action pseudo-class.
+      REPORT_UNEXPECTED_TOKEN(PEPseudoClassNotUserAction);
+      UngetToken();
+      return eSelectorParsingStatus_Error;
+    }
     aDataMask |= SEL_MASK_PCLASS;
     if (eCSSToken_Function == mToken.mType) {
       nsSelectorParsingStatus parsingStatus;
       if (nsCSSPseudoClasses::HasStringArg(pseudoClassType)) {
         parsingStatus =
           ParsePseudoClassWithIdentArg(aSelector, pseudoClassType);
       }
       else if (nsCSSPseudoClasses::HasNthPairArg(pseudoClassType)) {
@@ -3822,20 +3831,26 @@ CSSParserImpl::ParsePseudoSelector(int32
         // item in the list to the pseudoclass list.  They will be pulled
         // from the list later along with the pseudo-element.
         if (!ParseTreePseudoElement(aPseudoElementArgs)) {
           return eSelectorParsingStatus_Error;
         }
       }
 #endif
 
-      // the next *non*whitespace token must be '{' or ',' or EOF
+      // Pseudo-elements can only be followed by user action pseudo-classes
+      // or be the end of the selector.  So the next non-whitespace token must
+      // be ':', '{' or ',' or EOF.
       if (!GetToken(true)) { // premature eof is ok (here!)
         return eSelectorParsingStatus_Done;
       }
+      if (parsingPseudoElement && mToken.IsSymbol(':')) {
+        UngetToken();
+        return eSelectorParsingStatus_Continue;
+      }
       if ((mToken.IsSymbol('{') || mToken.IsSymbol(','))) {
         UngetToken();
         return eSelectorParsingStatus_Done;
       }
       REPORT_UNEXPECTED_TOKEN(PEPseudoSelTrailing);
       UngetToken();
       return eSelectorParsingStatus_Error;
     }
@@ -4196,16 +4211,29 @@ CSSParserImpl::ParseSelector(nsCSSSelect
     else if (mToken.IsSymbol('.')) {    // .class
       parsingStatus = ParseClassSelector(dataMask, *selector);
     }
     else if (mToken.IsSymbol(':')) {    // :pseudo
       parsingStatus = ParsePseudoSelector(dataMask, *selector, false,
                                           getter_AddRefs(pseudoElement),
                                           getter_Transfers(pseudoElementArgs),
                                           &pseudoElementType);
+      if (pseudoElement &&
+          pseudoElementType != nsCSSPseudoElements::ePseudo_AnonBox) {
+        // Pseudo-elements other than anonymous boxes are represented as
+        // direct children ('>' combinator) of the rest of the selector.
+
+        aList->mWeight += selector->CalcWeight();
+
+        selector = aList->AddSelector('>');
+
+        selector->mLowercaseTag.swap(pseudoElement);
+        selector->mClassList = pseudoElementArgs.forget();
+        selector->SetPseudoType(pseudoElementType);
+      }
     }
     else if (mToken.IsSymbol('[')) {    // [attribute
       parsingStatus = ParseAttributeSelector(dataMask, *selector);
       if (eSelectorParsingStatus_Error == parsingStatus) {
         SkipUntil(']');
       }
     }
     else {  // not a selector token, we're done
@@ -4250,26 +4278,16 @@ CSSParserImpl::ParseSelector(nsCSSSelect
     selector->mLowercaseTag.swap(pseudoElement);
     selector->mClassList = pseudoElementArgs.forget();
     selector->SetPseudoType(pseudoElementType);
     return true;
   }
 
   aList->mWeight += selector->CalcWeight();
 
-  // Pseudo-elements other than anonymous boxes are represented as
-  // direct children ('>' combinator) of the rest of the selector.
-  if (pseudoElement) {
-    selector = aList->AddSelector('>');
-
-    selector->mLowercaseTag.swap(pseudoElement);
-    selector->mClassList = pseudoElementArgs.forget();
-    selector->SetPseudoType(pseudoElementType);
-  }
-
   return true;
 }
 
 css::Declaration*
 CSSParserImpl::ParseDeclarationBlock(uint32_t aFlags, nsCSSContextType aContext)
 {
   bool checkForBraces = (aFlags & eParseDeclaration_InBraces) != 0;
 
--- a/layout/style/nsCSSPseudoClasses.cpp
+++ b/layout/style/nsCSSPseudoClasses.cpp
@@ -85,8 +85,17 @@ nsCSSPseudoClasses::GetPseudoType(nsIAto
   for (uint32_t i = 0; i < ArrayLength(CSSPseudoClasses_info); ++i) {
     if (*CSSPseudoClasses_info[i].mAtom == aAtom) {
       return sPseudoClassEnabled[i] ? Type(i) : ePseudoClass_NotPseudoClass;
     }
   }
 
   return nsCSSPseudoClasses::ePseudoClass_NotPseudoClass;
 }
+
+/* static */ bool
+nsCSSPseudoClasses::IsUserActionPseudoClass(Type aType)
+{
+  // See http://dev.w3.org/csswg/selectors4/#useraction-pseudos
+  return aType == ePseudoClass_hover ||
+         aType == ePseudoClass_active ||
+         aType == ePseudoClass_focus;
+}
--- a/layout/style/nsCSSPseudoClasses.h
+++ b/layout/style/nsCSSPseudoClasses.h
@@ -28,14 +28,15 @@ public:
   };
 
   static Type GetPseudoType(nsIAtom* aAtom);
   static bool HasStringArg(Type aType);
   static bool HasNthPairArg(Type aType);
   static bool HasSelectorListArg(Type aType) {
     return aType == ePseudoClass_any;
   }
+  static bool IsUserActionPseudoClass(Type aType);
 
   // Should only be used on types other than Count and NotPseudoClass
   static void PseudoTypeToString(Type aType, nsAString& aString);
 };
 
 #endif /* nsCSSPseudoClasses_h___ */