Bug 525608 part 2. Change the CSS parser to not allow anon box selectors with more than the anon box name in them. Store the pseudo type in pseudo-element selectors. Enforce that all non-anon-box pseudo selectors have an mNext that selects the element they apply to. r=dbaron
authorBoris Zbarsky <bzbarsky@mit.edu>
Fri, 11 Dec 2009 02:37:40 -0500
changeset 35553 909cbe6e89796bc46d7a50f3db7bd7e6cd955169
parent 35552 696a4ad5d011f356235caeea7250e5f827b7083e
child 35554 5c675fda7ba6c8859abdeb64f6f0152b4a06af41
push idunknown
push userunknown
push dateunknown
reviewersdbaron
bugs525608
milestone1.9.3a1pre
Bug 525608 part 2. Change the CSS parser to not allow anon box selectors with more than the anon box name in them. Store the pseudo type in pseudo-element selectors. Enforce that all non-anon-box pseudo selectors have an mNext that selects the element they apply to. r=dbaron
dom/locales/en-US/chrome/layout/css.properties
layout/mathml/mathml.css
layout/mathml/nsMathMLFrame.cpp
layout/style/forms.css
layout/style/nsCSSAnonBoxList.h
layout/style/nsCSSParser.cpp
layout/style/nsCSSPseudoElementList.h
layout/style/nsCSSStyleRule.cpp
layout/style/nsICSSStyleRule.h
--- a/dom/locales/en-US/chrome/layout/css.properties
+++ b/dom/locales/en-US/chrome/layout/css.properties
@@ -132,8 +132,9 @@ PEUnknownFontDesc=Unknown descriptor '%1
 PEMQExpectedExpressionStart=Expected '(' to start media query expression but found '%1$S'.
 PEMQExpressionEOF=contents of media query expression
 PEMQExpectedFeatureName=Expected media feature name but found '%1$S'.
 PEMQExpectedFeatureNameEnd=Expected ':' or ')' after media feature name but found '%1$S'.
 PEMQNoMinMaxWithoutValue=Media features with min- or max- must have a value.
 PEMQExpectedFeatureValue=Found invalid value for media feature.
 PEBadFontBlockStart=Expected '{' to begin @font-face rule but found '%1$S'.
 PEBadFontBlockEnd=Expected '}' to end @font-face rule but found '%1$S'.
+PEAnonBoxNotAlone=Did not expect anonymous box.
\ No newline at end of file
--- a/layout/mathml/mathml.css
+++ b/layout/mathml/mathml.css
@@ -60,19 +60,16 @@ math {
 }
 math[mode="display"], math[display="block"] {
   display: block;
   text-align: -moz-center;
 }
 math[display="inline"] {
   display: inline;
 }
-::-moz-math-inline {
-  display: inline;
-}
 
 /**************************************************************************/
 /* Style switching during frame construction depending on the context of <mi>:
    These rules are not used when mathvariant or fontstyle is specified
    explicitly. 
 /**************************************************************************/
 
 /* If the textual content of an <mi> consists of a single character
--- a/layout/mathml/nsMathMLFrame.cpp
+++ b/layout/mathml/nsMathMLFrame.cpp
@@ -160,18 +160,18 @@ nsMathMLFrame::UpdatePresentationData(PR
 /* static */ void
 nsMathMLFrame::ResolveMathMLCharStyle(nsPresContext*  aPresContext,
                                       nsIContent*      aContent,
                                       nsStyleContext*  aParentStyleContext,
                                       nsMathMLChar*    aMathMLChar,
                                       PRBool           aIsMutableChar)
 {
   nsIAtom* pseudoStyle = (aIsMutableChar) ?
-    nsCSSAnonBoxes::mozMathStretchy :
-    nsCSSAnonBoxes::mozMathAnonymous; // savings
+    nsCSSPseudoElements::mozMathStretchy :
+    nsCSSPseudoElements::mozMathAnonymous; // savings
   nsRefPtr<nsStyleContext> newStyleContext;
   newStyleContext = aPresContext->StyleSet()->
     ResolvePseudoStyleFor(aContent, pseudoStyle, aParentStyleContext);
 
   if (newStyleContext)
     aMathMLChar->SetStyleContext(newStyleContext);
 }
 
--- a/layout/style/forms.css
+++ b/layout/style/forms.css
@@ -254,20 +254,16 @@ select:empty {
   white-space: nowrap;
   text-align: inherit;
   -moz-user-select: none;
   /* Make sure to size correctly if the combobox has a non-auto height. */
   height: 100% ! important;
   -moz-box-sizing: border-box ! important;
 }
 
-select::-moz-scrolled-content {
-  display: block !important;
-}
-
 option {
   display: block;
   float: none !important;
   position: static !important;
   min-height: 1em;
   line-height: normal !important;
   -moz-user-select: none;
   text-indent: 0;
--- a/layout/style/nsCSSAnonBoxList.h
+++ b/layout/style/nsCSSAnonBoxList.h
@@ -104,17 +104,11 @@ CSS_ANON_BOX(moztreeline, ":-moz-tree-li
 CSS_ANON_BOX(moztreetwisty, ":-moz-tree-twisty")
 CSS_ANON_BOX(moztreeimage, ":-moz-tree-image")
 CSS_ANON_BOX(moztreecelltext, ":-moz-tree-cell-text")
 CSS_ANON_BOX(moztreecheckbox, ":-moz-tree-checkbox")
 CSS_ANON_BOX(moztreeprogressmeter, ":-moz-tree-progressmeter")
 CSS_ANON_BOX(moztreedropfeedback, ":-moz-tree-drop-feedback")
 #endif
 
-#ifdef MOZ_MATHML
-CSS_ANON_BOX(mozMathStretchy, ":-moz-math-stretchy")
-CSS_ANON_BOX(mozMathAnonymous, ":-moz-math-anonymous")
-CSS_ANON_BOX(mozMathInline, ":-moz-math-inline")
-#endif
-
 #ifdef MOZ_SVG
 CSS_ANON_BOX(mozSVGForeignContent, ":-moz-svg-foreign-content")
 #endif
--- a/layout/style/nsCSSParser.cpp
+++ b/layout/style/nsCSSParser.cpp
@@ -349,17 +349,18 @@ protected:
   // aPseudoElement and aPseudoElementArgs are the location where
   // pseudo-elements (as opposed to pseudo-classes) are stored;
   // pseudo-classes are stored on aSelector.  aPseudoElement and
   // aPseudoElementArgs must be non-null iff !aIsNegated.
   nsSelectorParsingStatus ParsePseudoSelector(PRInt32&       aDataMask,
                                               nsCSSSelector& aSelector,
                                               PRBool         aIsNegated,
                                               nsIAtom**      aPseudoElement,
-                                              nsPseudoClassList** aPseudoElementArgs);
+                                              nsPseudoClassList** aPseudoElementArgs,
+                                              nsCSSPseudoElements::Type* aPseudoElementType);
 
   nsSelectorParsingStatus ParseAttributeSelector(PRInt32&       aDataMask,
                                                  nsCSSSelector& aSelector);
 
   nsSelectorParsingStatus ParseTypeOrUniversalSelector(PRInt32&       aDataMask,
                                                        nsCSSSelector& aSelector,
                                                        PRBool         aIsNegated);
 
@@ -369,17 +370,18 @@ protected:
   nsSelectorParsingStatus ParsePseudoClassWithNthPairArg(nsCSSSelector& aSelector,
                                                          nsIAtom*       aPseudo);
 
   nsSelectorParsingStatus ParseNegatedSimpleSelector(PRInt32&       aDataMask,
                                                      nsCSSSelector& aSelector);
 
   nsSelectorParsingStatus ParseSelector(nsCSSSelector& aSelectorResult,
                                         nsIAtom** aPseudoElement,
-                                        nsPseudoClassList** aPseudoElementArgs);
+                                        nsPseudoClassList** aPseudoElementArgs,
+                                        nsCSSPseudoElements::Type* aPseudoElementType);
 
   // If aTerminateAtBrace is true, the selector list is done when we
   // hit a '{'.  Otherwise, it's done when we hit EOF.
   PRBool ParseSelectorList(nsCSSSelectorList*& aListHead,
                            PRBool aTerminateAtBrace);
   PRBool ParseSelectorGroup(nsCSSSelectorList*& aListHead);
   nsCSSDeclaration* ParseDeclarationBlock(PRBool aCheckForBraces);
   PRBool ParseDeclaration(nsCSSDeclaration* aDeclaration,
@@ -2492,46 +2494,55 @@ CSSParserImpl::ParseSelectorGroup(nsCSSS
   while (!done) {
     nsAutoPtr<nsCSSSelector> newSelector(new nsCSSSelector());
     if (!newSelector) {
       mScanner.SetLowLevelError(NS_ERROR_OUT_OF_MEMORY);
       return PR_FALSE;
     }
     nsCOMPtr<nsIAtom> pseudoElement;
     nsAutoPtr<nsPseudoClassList> pseudoElementArgs;
+    nsCSSPseudoElements::Type pseudoElementType;
     nsSelectorParsingStatus parsingStatus =
       ParseSelector(*newSelector, getter_AddRefs(pseudoElement),
-                    getter_Transfers(pseudoElementArgs));
+                    getter_Transfers(pseudoElementArgs),
+                    &pseudoElementType);
     if (parsingStatus == eSelectorParsingStatus_Empty) {
       if (!list) {
         REPORT_UNEXPECTED(PESelectorGroupNoSelector);
       }
       break;
     }
     if (parsingStatus == eSelectorParsingStatus_Error) {
       list = nsnull;
       break;
     }
+    if (pseudoElement &&
+        pseudoElementType == nsCSSPseudoElements::ePseudo_AnonBox &&
+        (list || !IsUniversalSelector(*newSelector))) {
+      REPORT_UNEXPECTED(PEAnonBoxNotAlone);
+      list = nsnull;
+      break;
+    }
     if (nsnull == list) {
       list = new nsCSSSelectorList();
       if (nsnull == list) {
         mScanner.SetLowLevelError(NS_ERROR_OUT_OF_MEMORY);
         return PR_FALSE;
       }
     }
+
     list->AddSelector(newSelector);
     nsCSSSelector* listSel = list->mSelectors;
 
     // We got a pseudo-element (or anonymous box).  We actually
     // represent pseudo-elements as a child of the rest of the selector.
     if (pseudoElement) {
-      if (listSel->mNext || !IsUniversalSelector(*listSel)) {
+      if (pseudoElementType != nsCSSPseudoElements::ePseudo_AnonBox) {
         // We need to put the pseudo-element on a new selector that's a
-        // child of the current one.  (If it's the only thing in the
-        // entire selector group, we can just put it on this one.)
+        // child of the current one.
         listSel->mOperator = PRUnichar('>');
         nsAutoPtr<nsCSSSelector> empty(new nsCSSSelector());
         if (!empty) {
           mScanner.SetLowLevelError(NS_ERROR_OUT_OF_MEMORY);
           return PR_FALSE;
         }
         list->AddSelector(empty);
         // Save the weight of the non-pseudo-element part of this selector now
@@ -2539,16 +2550,17 @@ CSSParserImpl::ParseSelectorGroup(nsCSSS
         listSel = list->mSelectors; // use the new one for the pseudo
       }
       NS_ASSERTION(!listSel->mLowercaseTag &&
                    !listSel->mCasedTag &&
                    !listSel->mPseudoClassList,
                    "already initialized");
       listSel->mLowercaseTag.swap(pseudoElement);
       listSel->mPseudoClassList = pseudoElementArgs.forget();
+      listSel->SetPseudoType(pseudoElementType);
       havePseudoElement = PR_TRUE;
     }
 
     combinator = PRUnichar(0);
     if (!GetToken(PR_FALSE)) {
       break;
     }
 
@@ -2986,17 +2998,18 @@ CSSParserImpl::ParseAttributeSelector(PR
 //
 // Parse pseudo-classes and pseudo-elements
 //
 CSSParserImpl::nsSelectorParsingStatus
 CSSParserImpl::ParsePseudoSelector(PRInt32&       aDataMask,
                                    nsCSSSelector& aSelector,
                                    PRBool         aIsNegated,
                                    nsIAtom**      aPseudoElement,
-                                   nsPseudoClassList** aPseudoElementArgs)
+                                   nsPseudoClassList** aPseudoElementArgs,
+                                   nsCSSPseudoElements::Type* aPseudoElementType)
 {
   NS_ASSERTION(aIsNegated || (aPseudoElement && aPseudoElementArgs),
                "expected location to store pseudo element");
   NS_ASSERTION(!aIsNegated || (!aPseudoElement && !aPseudoElementArgs),
                "negated selectors shouldn't have a place to store "
                "pseudo elements");
   if (! GetToken(PR_FALSE)) { // premature eof
     REPORT_UNEXPECTED_EOF(PEPseudoSelEOF);
@@ -3030,34 +3043,44 @@ CSSParserImpl::ParsePseudoSelector(PRInt
   nsCOMPtr<nsIAtom> pseudo = do_GetAtom(buffer);
   if (!pseudo) {
     mScanner.SetLowLevelError(NS_ERROR_OUT_OF_MEMORY);
     return eSelectorParsingStatus_Error;
   }
 
   // stash away some info about this pseudo so we only have to get it once.
   PRBool isTreePseudo = PR_FALSE;
+  nsCSSPseudoElements::Type pseudoElementType =
+    nsCSSPseudoElements::GetPseudoType(pseudo);
 #ifdef MOZ_XUL
-  isTreePseudo = nsCSSAnonBoxes::IsTreePseudoElement(pseudo);
+  isTreePseudo = (pseudoElementType == nsCSSPseudoElements::ePseudo_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.
   PRBool isTree = (eCSSToken_Function == mToken.mType) && isTreePseudo;
 #endif
-  PRBool isPseudoElement = nsCSSPseudoElements::IsPseudoElement(pseudo);
+  PRBool isPseudoElement =
+    (pseudoElementType < nsCSSPseudoElements::ePseudo_PseudoElementCount);
   // anonymous boxes are only allowed if they're the tree boxes or we have
   // enabled unsafe rules
-  PRBool isAnonBox = nsCSSAnonBoxes::IsAnonBox(pseudo) &&
-    (mUnsafeRulesEnabled || isTreePseudo);
+  PRBool isAnonBox = isTreePseudo ||
+    (pseudoElementType == nsCSSPseudoElements::ePseudo_AnonBox &&
+     mUnsafeRulesEnabled);
   PRBool isPseudoClass = nsCSSPseudoClasses::IsPseudoClass(pseudo);
 
+  NS_ASSERTION(!isPseudoClass ||
+               pseudoElementType == nsCSSPseudoElements::ePseudo_NotPseudoElement,
+               "Why is this atom both a pseudo-class and a pseudo-element?");
+  NS_ASSERTION(isPseudoClass + isPseudoElement + isAnonBox <= 1,
+               "Shouldn't be more than one of these");
+
   if (!isPseudoClass && !isPseudoElement && !isAnonBox) {
     // Not a pseudo-class, not a pseudo-element.... forget it
     REPORT_UNEXPECTED_TOKEN(PEPseudoSelUnknown);
     UngetToken();
     return eSelectorParsingStatus_Error;
   }
 
   // If it's a function token, it better be on our "ok" list, and if the name
@@ -3139,16 +3162,17 @@ CSSParserImpl::ParsePseudoSelector(PRInt
       REPORT_UNEXPECTED_TOKEN(PEPseudoSelNewStyleOnly);
       UngetToken();
       return eSelectorParsingStatus_Error;
     }
 
     if (0 == (aDataMask & SEL_MASK_PELEM)) {
       aDataMask |= SEL_MASK_PELEM;
       NS_ADDREF(*aPseudoElement = pseudo);
+      *aPseudoElementType = pseudoElementType;
 
 #ifdef 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)) {
@@ -3230,17 +3254,17 @@ CSSParserImpl::ParseNegatedSimpleSelecto
   if (eCSSToken_ID == mToken.mType) { // #id
     parsingStatus = ParseIDSelector(aDataMask, *newSel);
   }
   else if (mToken.IsSymbol('.')) {    // .class
     parsingStatus = ParseClassSelector(aDataMask, *newSel);
   }
   else if (mToken.IsSymbol(':')) {    // :pseudo
     parsingStatus = ParsePseudoSelector(aDataMask, *newSel, PR_TRUE,
-                                        nsnull, nsnull);
+                                        nsnull, nsnull, nsnull);
   }
   else if (mToken.IsSymbol('[')) {    // [attribute
     parsingStatus = ParseAttributeSelector(aDataMask, *newSel);
   }
   else {
     // then it should be a type element or universal selector
     parsingStatus = ParseTypeOrUniversalSelector(aDataMask, *newSel, PR_TRUE);
   }
@@ -3440,17 +3464,18 @@ CSSParserImpl::ParsePseudoClassWithNthPa
 
 /**
  * This is the format for selectors:
  * operator? [[namespace |]? element_name]? [ ID | class | attrib | pseudo ]*
  */
 CSSParserImpl::nsSelectorParsingStatus
 CSSParserImpl::ParseSelector(nsCSSSelector& aSelector,
                              nsIAtom** aPseudoElement,
-                             nsPseudoClassList** aPseudoElementArgs)
+                             nsPseudoClassList** aPseudoElementArgs,
+                             nsCSSPseudoElements::Type* aPseudoElementType)
 {
   if (! GetToken(PR_TRUE)) {
     REPORT_UNEXPECTED_EOF(PESelectorEOF);
     return eSelectorParsingStatus_Error;
   }
 
   PRInt32 dataMask = 0;
   nsSelectorParsingStatus parsingStatus =
@@ -3463,17 +3488,18 @@ CSSParserImpl::ParseSelector(nsCSSSelect
     if (eCSSToken_ID == mToken.mType) { // #id
       parsingStatus = ParseIDSelector(dataMask, aSelector);
     }
     else if (mToken.IsSymbol('.')) {    // .class
       parsingStatus = ParseClassSelector(dataMask, aSelector);
     }
     else if (mToken.IsSymbol(':')) {    // :pseudo
       parsingStatus = ParsePseudoSelector(dataMask, aSelector, PR_FALSE,
-                                          aPseudoElement, aPseudoElementArgs);
+                                          aPseudoElement, aPseudoElementArgs,
+                                          aPseudoElementType);
     }
     else if (mToken.IsSymbol('[')) {    // [attribute
       parsingStatus = ParseAttributeSelector(dataMask, aSelector);
     }
     else {  // not a selector token, we're done
       parsingStatus = eSelectorParsingStatus_Done;
       break;
     }
--- a/layout/style/nsCSSPseudoElementList.h
+++ b/layout/style/nsCSSPseudoElementList.h
@@ -76,8 +76,13 @@ CSS_PSEUDO_ELEMENT(mozSelection, ":-moz-
 CSS_PSEUDO_ELEMENT(mozFocusInner, ":-moz-focus-inner", 0)
 CSS_PSEUDO_ELEMENT(mozFocusOuter, ":-moz-focus-outer", 0)
 
 // XXXbz should we really allow random content to style these?  Maybe
 // use our flags to prevent that?
 CSS_PSEUDO_ELEMENT(mozListBullet, ":-moz-list-bullet", 0)
 CSS_PSEUDO_ELEMENT(mozListNumber, ":-moz-list-number", 0)
 
+#ifdef MOZ_MATHML
+CSS_PSEUDO_ELEMENT(mozMathStretchy, ":-moz-math-stretchy", 0)
+CSS_PSEUDO_ELEMENT(mozMathAnonymous, ":-moz-math-anonymous", 0)
+#endif
+
--- a/layout/style/nsCSSStyleRule.cpp
+++ b/layout/style/nsCSSStyleRule.cpp
@@ -77,16 +77,18 @@
 #include "nsCSSPseudoClasses.h"
 #include "nsCSSAnonBoxes.h"
 #include "nsTArray.h"
 
 #include "nsContentUtils.h"
 #include "nsContentErrors.h"
 #include "mozAutoDocUpdate.h"
 
+#include "prlog.h"
+
 #define NS_IF_CLONE(member_)                                                  \
   PR_BEGIN_MACRO                                                              \
     if (member_) {                                                            \
       result->member_ = member_->Clone();                                     \
       if (!result->member_) {                                                 \
         delete result;                                                        \
         return nsnull;                                                        \
       }                                                                       \
@@ -275,32 +277,36 @@ nsCSSSelector::nsCSSSelector(void)
     mCasedTag(nsnull),
     mIDList(nsnull),
     mClassList(nsnull),
     mPseudoClassList(nsnull),
     mAttrList(nsnull),
     mNegations(nsnull),
     mNext(nsnull),
     mNameSpace(kNameSpaceID_Unknown),
-    mOperator(0)
+    mOperator(0),
+    mPseudoType(nsCSSPseudoElements::ePseudo_NotPseudoElement)
 {
   MOZ_COUNT_CTOR(nsCSSSelector);
+  // Make sure mPseudoType can hold all nsCSSPseudoElements::Type values
+  PR_STATIC_ASSERT(nsCSSPseudoElements::ePseudo_MAX < PR_INT16_MAX);
 }
 
 nsCSSSelector*
 nsCSSSelector::Clone(PRBool aDeepNext, PRBool aDeepNegations) const
 {
   nsCSSSelector *result = new nsCSSSelector();
   if (!result)
     return nsnull;
 
   result->mNameSpace = mNameSpace;
   result->mLowercaseTag = mLowercaseTag;
   result->mCasedTag = mCasedTag;
   result->mOperator = mOperator;
+  result->mPseudoType = mPseudoType;
   
   NS_IF_CLONE(mIDList);
   NS_IF_CLONE(mClassList);
   NS_IF_CLONE(mPseudoClassList);
   NS_IF_CLONE(mAttrList);
 
   // No need to worry about multiple levels of recursion since an
   // mNegations can't have an mNext.
--- a/layout/style/nsICSSStyleRule.h
+++ b/layout/style/nsICSSStyleRule.h
@@ -47,16 +47,17 @@
 
 //#include <stdio.h>
 #include "nsICSSRule.h"
 #include "nsString.h"
 #include "nsCOMPtr.h"
 #include "nsCSSProps.h"
 #include "nsCSSValue.h"
 #include "nsIAtom.h"
+#include "nsCSSPseudoElements.h"
 
 class nsIAtom;
 class nsCSSDeclaration;
 class nsICSSStyleSheet;
 
 struct nsAtomList {
 public:
   nsAtomList(nsIAtom* aAtom);
@@ -194,32 +195,43 @@ private:
   // happens if and only if the default namespace would apply to this
   // selector).
   PRBool CanBeNamespaced(PRBool aIsNegated) const;
   // Calculate the specificity of this selector (not including its mNext
   // or its mNegations).
   PRInt32 CalcWeightWithoutNegations() const;
 
 public:
+  // Get and set the selector's pseudo type
+  nsCSSPseudoElements::Type PseudoType() const {
+    return static_cast<nsCSSPseudoElements::Type>(mPseudoType);
+  }
+  void SetPseudoType(nsCSSPseudoElements::Type aType) {
+    NS_ASSERTION(aType > PR_INT16_MIN && aType < PR_INT16_MAX, "Out of bounds");
+    mPseudoType = static_cast<PRInt16>(aType);
+  }
+
   // For case-sensitive documents, mLowercaseTag is the same as mCasedTag,
   // but in case-insensitive documents (HTML) mLowercaseTag is lowercase.
   // Also, for pseudo-elements mCasedTag will be null but mLowercaseTag
   // contains their name.
   nsCOMPtr<nsIAtom> mLowercaseTag;
   nsCOMPtr<nsIAtom> mCasedTag;
   nsAtomList*     mIDList;
   nsAtomList*     mClassList;
   nsPseudoClassList* mPseudoClassList; // atom for the pseudo, string for
                                        // the argument to functional pseudos
   nsAttrSelector* mAttrList;
   nsCSSSelector*  mNegations;
   nsCSSSelector*  mNext;
   PRInt32         mNameSpace;
   PRUnichar       mOperator;
-private: 
+private:
+  // PRInt16 to make sure it packs well with mOperator
+  PRInt16        mPseudoType;
   // These are not supported and are not implemented! 
   nsCSSSelector(const nsCSSSelector& aCopy);
   nsCSSSelector& operator=(const nsCSSSelector& aCopy); 
 };
 
 /**
  * A selector list is the unit of selectors that each style rule has.
  * For example, "P B, H1 B { ... }" would be a selector list with two