// // Parse pseudo-classes and pseudo-elements // internal nsSelectorParsingStatus ParsePseudoSelector(ref int32_t aDataMask, nsCSSSelector aSelector, bool aIsNegated, ref string aPseudoElement, object aPseudoElementArgs, ref nsCSSPseudoElement aPseudoElementType) { Debug.Assert(!aIsNegated || (aPseudoElement == null && aPseudoElementArgs == null), "negated selectors shouldn't have a place to store pseudo elements"); if (! GetToken(false)) { // premature eof { if (!mSuppressErrors) mReporter.ReportUnexpected("PEPseudoSelEOF"); }; return nsSelectorParsingStatus.Error; } // First, find out whether we are parsing a CSS3 pseudo-element bool parsingPseudoElement = false; if (mToken.IsSymbol(':')) { parsingPseudoElement = true; if (! GetToken(false)) { // premature eof { if (!mSuppressErrors) mReporter.ReportUnexpected("PEPseudoSelEOF"); }; return nsSelectorParsingStatus.Error; } } // Do some sanity-checking on the token if (nsCSSTokenType.Ident != mToken.mType && nsCSSTokenType.Function != mToken.mType) { // malformed selector { if (!mSuppressErrors) mReporter.ReportUnexpected("PEPseudoSelBadName", mToken); }; UngetToken(); return nsSelectorParsingStatus.Error; } // OK, now we know we have an mIdent. Atomize it. All the atoms, for // pseudo-classes as well as pseudo-elements, start with a single ':' string pseudo = String.Intern(":" + mToken.mIdentStr); // stash away some info about this pseudo so we only have to get it once. bool isTreePseudo = false; nsCSSPseudoElement pseudoElementType = nsCSSPseudoElements.GetPseudoType(pseudo); nsCSSPseudoClass pseudoClassType = nsCSSPseudoClasses.GetPseudoType(pseudo); // We currently allow :-moz-placeholder and .-moz-placeholder. We have to // be a bit stricter regarding the pseudo-element parsing rules. if (pseudoElementType == nsCSSPseudoElement.MozPlaceholder && pseudoClassType == nsCSSPseudoClass.MozPlaceholder) { if (parsingPseudoElement) { pseudoClassType = nsCSSPseudoClass.NotPseudoClass; } else { pseudoElementType = nsCSSPseudoElement.NotPseudoElement; } } #if MOZ_XUL isTreePseudo = (pseudoElementType == nsCSSPseudoElement.Xultree); // If a tree pseudo-element is using the function syntax, it will // get isTree set here and will pass the check below that only // allows functions if they are in our list of things allowed to be // functions. If it is _not_ using the function syntax, isTree will // be false, and it will still pass that check. So the tree // pseudo-elements are allowed to be either functions or not, as // desired. bool isTree = (nsCSSTokenType.Function == mToken.mType) && isTreePseudo; #endif bool isPseudoElement = (pseudoElementType < nsCSSPseudoElement.PseudoElementCount); // anonymous boxes are only allowed if they're the tree boxes or we have // enabled unsafe rules bool isAnonBox = isTreePseudo || (pseudoElementType == nsCSSPseudoElement.AnonBox && mUnsafeRulesEnabled); bool isPseudoClass = (pseudoClassType != nsCSSPseudoClass.NotPseudoClass); Debug.Assert(!isPseudoClass || pseudoElementType == nsCSSPseudoElement.NotPseudoElement, "Why is this atom both a pseudo-class and a pseudo-element?"); Debug.Assert((isPseudoClass?1:0) + (isPseudoElement?1:0) + (isAnonBox?1:0) <= 1, "Shouldn't be more than one of these"); if (!isPseudoClass && !isPseudoElement && !isAnonBox) { // Not a pseudo-class, not a pseudo-element.... forget it { if (!mSuppressErrors) mReporter.ReportUnexpected("PEPseudoSelUnknown", mToken); }; UngetToken(); return nsSelectorParsingStatus.Error; } // If it's a function token, it better be on our "ok" list, and if the name // is that of a function pseudo it better be a function token if ((nsCSSTokenType.Function == mToken.mType) != ( #if MOZ_XUL isTree || #endif nsCSSPseudoClass.NotPseudo == pseudoClassType || nsCSSPseudoClasses.HasStringArg(pseudoClassType) || nsCSSPseudoClasses.HasNthPairArg(pseudoClassType) || nsCSSPseudoClasses.HasSelectorListArg(pseudoClassType))) { // There are no other function pseudos { if (!mSuppressErrors) mReporter.ReportUnexpected("PEPseudoSelNonFunc", mToken); }; UngetToken(); return nsSelectorParsingStatus.Error; } // If it starts with ".", it better be a pseudo-element if (parsingPseudoElement && !isPseudoElement && !isAnonBox) { { if (!mSuppressErrors) mReporter.ReportUnexpected("PEPseudoSelNotPE", mToken); }; UngetToken(); return nsSelectorParsingStatus.Error; } if (!parsingPseudoElement && nsCSSPseudoClass.NotPseudo == pseudoClassType) { if (aIsNegated) { // :not() can't be itself negated { if (!mSuppressErrors) mReporter.ReportUnexpected("PEPseudoSelDoubleNot", mToken); }; UngetToken(); return nsSelectorParsingStatus.Error; } // CSS 3 Negation pseudo-class takes one simple selector as argument nsSelectorParsingStatus parsingStatus = ParseNegatedSimpleSelector(ref aDataMask, aSelector); if (nsSelectorParsingStatus.Continue != parsingStatus) { return parsingStatus; } } else if (!parsingPseudoElement && isPseudoClass) { aDataMask |= SEL_MASK_PCLASS; if (nsCSSTokenType.Function == mToken.mType) { nsSelectorParsingStatus parsingStatus; if (nsCSSPseudoClasses.HasStringArg(pseudoClassType)) { parsingStatus = ParsePseudoClassWithIdentArg(aSelector, pseudoClassType); } else if (nsCSSPseudoClasses.HasNthPairArg(pseudoClassType)) { parsingStatus = ParsePseudoClassWithNthPairArg(aSelector, pseudoClassType); } else { Debug.Assert(nsCSSPseudoClasses.HasSelectorListArg(pseudoClassType), "unexpected pseudo with function token"); parsingStatus = ParsePseudoClassWithSelectorListArg(aSelector, pseudoClassType); } if (nsSelectorParsingStatus.Continue != parsingStatus) { if (nsSelectorParsingStatus.Error == parsingStatus) { SkipUntil(')'); } return parsingStatus; } } else { aSelector.AddPseudoClass(pseudoClassType); } } else if (isPseudoElement || isAnonBox) { // Pseudo-element. Make some more sanity checks. if (aIsNegated) { // pseudo-elements can't be negated { if (!mSuppressErrors) mReporter.ReportUnexpected("PEPseudoSelPEInNot", mToken); }; UngetToken(); return nsSelectorParsingStatus.Error; } // CSS2 pseudo-elements and -moz-tree-* pseudo-elements are allowed // to have a single ':' on them. Others (CSS3+ pseudo-elements and // various -moz-* pseudo-elements) must have |parsingPseudoElement| // set. if (!parsingPseudoElement && !nsCSSPseudoElements.IsCSS2PseudoElement(pseudo) #if MOZ_XUL && !isTreePseudo #endif ) { { if (!mSuppressErrors) mReporter.ReportUnexpected("PEPseudoSelNewStyleOnly", mToken); }; UngetToken(); return nsSelectorParsingStatus.Error; } if (0 == (aDataMask & SEL_MASK_PELEM)) { aDataMask |= SEL_MASK_PELEM; aPseudoElement = pseudo; aPseudoElementType = pseudoElementType; #if MOZ_XUL if (isTree) { // We have encountered a pseudoelement of the form // -moz-tree-xxxx(a,b,c). We parse (a,b,c) and add each // 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 nsSelectorParsingStatus.Error; } } #endif // the next *non*whitespace token must be '{' or ',' or EOF if (!GetToken(true)) { // premature eof is ok (here!) return nsSelectorParsingStatus.Done; } if ((mToken.IsSymbol('{') || mToken.IsSymbol(','))) { UngetToken(); return nsSelectorParsingStatus.Done; } { if (!mSuppressErrors) mReporter.ReportUnexpected("PEPseudoSelTrailing", mToken); }; UngetToken(); return nsSelectorParsingStatus.Error; } else { // multiple pseudo elements, not legal { if (!mSuppressErrors) mReporter.ReportUnexpected("PEPseudoSelMultiplePE", mToken); }; UngetToken(); return nsSelectorParsingStatus.Error; } } #if DEBUG else { // We should never end up here. Indeed, if we ended up here, we know (from // the current if/else cascade) that !isPseudoElement and !isAnonBox. But // then due to our earlier check we know that isPseudoClass. Since we // didn't fall into the isPseudoClass case in this cascade, we must have // parsingPseudoElement. But we've already checked the // parsingPseudoElement && !isPseudoClass && !isAnonBox case and bailed if // it's happened. Debug.Fail("How did this happen?"); } #endif return nsSelectorParsingStatus.Continue; }
internal nsSelectorParsingStatus ParsePseudoClassWithNthPairArg(nsCSSSelector aSelector, nsCSSPseudoClass aType) { int32_t[] numbers = { 0, 0 }; bool lookForB = true; // Follow the whitespace rules as proposed in // http://lists.w3.org/Archives/Public/www-style/2008Mar/0121.html if (! GetToken(true)) { { if (!mSuppressErrors) mReporter.ReportUnexpected("PEPseudoClassArgEOF"); }; return nsSelectorParsingStatus.Error; } if (nsCSSTokenType.Ident == mToken.mType || nsCSSTokenType.Dimension == mToken.mType) { // The CSS tokenization doesn't handle :nth-child() containing - well: // 2n-1 is a dimension // n-1 is an identifier // The easiest way to deal with that is to push everything from the // minus on back onto the scanner's pushback buffer. uint32_t truncAt = 0; if (StringBeginsWith(mToken.mIdentStr, "n-")) { truncAt = 1; } else if (StringBeginsWith(mToken.mIdentStr, "-n-")) { truncAt = 2; } if (truncAt != 0) { mScanner.Backup(mToken.mIdentStr.Length() - truncAt); mToken.mIdentStr = mToken.mIdentStr.Substring(0, truncAt); } } if (nsCSSTokenType.Ident == mToken.mType) { if (mToken.mIdentStr.LowerCaseEqualsLiteral("odd")) { numbers[0] = 2; numbers[1] = 1; lookForB = false; } else if (mToken.mIdentStr.LowerCaseEqualsLiteral("even")) { numbers[0] = 2; numbers[1] = 0; lookForB = false; } else if (mToken.mIdentStr.LowerCaseEqualsLiteral("n")) { numbers[0] = 1; } else if (mToken.mIdentStr.LowerCaseEqualsLiteral("-n")) { numbers[0] = -1; } else { { if (!mSuppressErrors) mReporter.ReportUnexpected("PEPseudoClassArgNotNth", mToken); }; return nsSelectorParsingStatus.Error; // our caller calls SkipUntil(')') } } else if (nsCSSTokenType.Number == mToken.mType) { if (!mToken.mIntegerValid) { { if (!mSuppressErrors) mReporter.ReportUnexpected("PEPseudoClassArgNotNth", mToken); }; return nsSelectorParsingStatus.Error; // our caller calls SkipUntil(')') } numbers[1] = mToken.mInteger; lookForB = false; } else if (nsCSSTokenType.Dimension == mToken.mType) { if (!mToken.mIntegerValid || !mToken.mIdentStr.LowerCaseEqualsLiteral("n")) { { if (!mSuppressErrors) mReporter.ReportUnexpected("PEPseudoClassArgNotNth", mToken); }; return nsSelectorParsingStatus.Error; // our caller calls SkipUntil(')') } numbers[0] = mToken.mInteger; } // XXX If it's a ')', is that valid? (as 0n+0) else { { if (!mSuppressErrors) mReporter.ReportUnexpected("PEPseudoClassArgNotNth", mToken); }; UngetToken(); return nsSelectorParsingStatus.Error; // our caller calls SkipUntil(')') } if (! GetToken(true)) { { if (!mSuppressErrors) mReporter.ReportUnexpected("PEPseudoClassArgEOF"); }; return nsSelectorParsingStatus.Error; } if (lookForB && !mToken.IsSymbol(')')) { // The '+' or '-' sign can optionally be separated by whitespace. // If it is separated by whitespace from what follows it, it appears // as a separate token rather than part of the number token. bool haveSign = false; int32_t sign = 1; if (mToken.IsSymbol('+') || mToken.IsSymbol('-')) { haveSign = true; if (mToken.IsSymbol('-')) { sign = -1; } if (! GetToken(true)) { { if (!mSuppressErrors) mReporter.ReportUnexpected("PEPseudoClassArgEOF"); }; return nsSelectorParsingStatus.Error; } } if (nsCSSTokenType.Number != mToken.mType || !mToken.mIntegerValid || mToken.mHasSign == haveSign) { { if (!mSuppressErrors) mReporter.ReportUnexpected("PEPseudoClassArgNotNth", mToken); }; return nsSelectorParsingStatus.Error; // our caller calls SkipUntil(')') } numbers[1] = mToken.mInteger * sign; if (! GetToken(true)) { { if (!mSuppressErrors) mReporter.ReportUnexpected("PEPseudoClassArgEOF"); }; return nsSelectorParsingStatus.Error; } } if (!mToken.IsSymbol(')')) { { if (!mSuppressErrors) mReporter.ReportUnexpected("PEPseudoClassNoClose", mToken); }; return nsSelectorParsingStatus.Error; // our caller calls SkipUntil(')') } aSelector.AddPseudoClass(aType, numbers); return nsSelectorParsingStatus.Continue; }
// // Parse the argument of a pseudo-class that has a selector list argument. // Such selector lists cannot contain combinators, but can contain // anything that goes between a pair of combinators. // internal nsSelectorParsingStatus ParsePseudoClassWithSelectorListArg(nsCSSSelector aSelector, nsCSSPseudoClass aType) { var slist = new nsCSSSelectorList(); if (! ParseSelectorList(ref slist, ')')) { return nsSelectorParsingStatus.Error; // our caller calls SkipUntil(')') } // Check that none of the selectors in the list have combinators or // pseudo-elements. for (nsCSSSelectorList l = slist; l != null; l = l.mNext) { nsCSSSelector s = l.mSelectors; if (s.mNext != null || s.IsPseudoElement()) { return nsSelectorParsingStatus.Error; // our caller calls SkipUntil(')') } } // Add the pseudo with the selector list parameter aSelector.AddPseudoClass(aType, slist); // close the parenthesis if (!ExpectSymbol(')', true)) { { if (!mSuppressErrors) mReporter.ReportUnexpected("PEPseudoClassNoClose", mToken); }; return nsSelectorParsingStatus.Error; // our caller calls SkipUntil(')') } return nsSelectorParsingStatus.Continue; }
// // Parse the argument of a pseudo-class that has an ident arg // internal nsSelectorParsingStatus ParsePseudoClassWithIdentArg(nsCSSSelector aSelector, nsCSSPseudoClass aType) { if (! GetToken(true)) { // premature eof { if (!mSuppressErrors) mReporter.ReportUnexpected("PEPseudoClassArgEOF"); }; return nsSelectorParsingStatus.Error; } // We expect an identifier with a language abbreviation if (nsCSSTokenType.Ident != mToken.mType) { { if (!mSuppressErrors) mReporter.ReportUnexpected("PEPseudoClassArgNotIdent", mToken); }; UngetToken(); return nsSelectorParsingStatus.Error; // our caller calls SkipUntil(')') } // -moz-locale-dir and -moz-dir can only have values of 'ltr' or 'rtl'. if (aType == nsCSSPseudoClass.MozLocaleDir || aType == nsCSSPseudoClass.Dir) { mToken.mIdentStr = mToken.mIdentStr.ToLower(); // case insensitive if (!mToken.mIdentStr.EqualsLiteral("ltr") && !mToken.mIdentStr.EqualsLiteral("rtl")) { { if (!mSuppressErrors) mReporter.ReportUnexpected("PEBadDirValue", mToken); }; return nsSelectorParsingStatus.Error; // our caller calls SkipUntil(')') } } // Add the pseudo with the language parameter aSelector.AddPseudoClass(aType, mToken.mIdentStr); // close the parenthesis if (!ExpectSymbol(')', true)) { { if (!mSuppressErrors) mReporter.ReportUnexpected("PEPseudoClassNoClose", mToken); }; return nsSelectorParsingStatus.Error; // our caller calls SkipUntil(')') } return nsSelectorParsingStatus.Continue; }