Implement :first-of-type, :last-of-type, and :only-of-type. b=128585 r+sr=bzbarsky
authorL. David Baron <dbaron@dbaron.org>
Mon, 02 Jun 2008 20:17:35 -0700
changeset 15214 84d1f9d39ac3b4c71d7c292b463e1a8f5b741c22
parent 15213 dc3c9abdd272ff2ce9acf13a783572244540d7ae
child 15215 af9466a5c157511c4bd35b99f8eee007dd01ed6f
push idunknown
push userunknown
push dateunknown
bugs128585
milestone1.9.1a1pre
Implement :first-of-type, :last-of-type, and :only-of-type. b=128585 r+sr=bzbarsky
layout/style/nsCSSPseudoClassList.h
layout/style/nsCSSRuleProcessor.cpp
layout/style/nsIStyleRuleProcessor.h
layout/style/test/test_bug73586.html
layout/style/test/test_of_type_selectors.xhtml
--- a/layout/style/nsCSSPseudoClassList.h
+++ b/layout/style/nsCSSPseudoClassList.h
@@ -73,16 +73,19 @@ CSS_PSEUDO_CLASS(hover, ":hover")
 CSS_PSEUDO_CLASS(mozDragOver, ":-moz-drag-over")
 CSS_PSEUDO_CLASS(target, ":target")
 
 CSS_PSEUDO_CLASS(firstChild, ":first-child")
 CSS_PSEUDO_CLASS(firstNode, ":-moz-first-node")
 CSS_PSEUDO_CLASS(lastChild, ":last-child")
 CSS_PSEUDO_CLASS(lastNode, ":-moz-last-node")
 CSS_PSEUDO_CLASS(onlyChild, ":only-child")
+CSS_PSEUDO_CLASS(firstOfType, ":first-of-type")
+CSS_PSEUDO_CLASS(lastOfType, ":last-of-type")
+CSS_PSEUDO_CLASS(onlyOfType, ":only-of-type")
 CSS_PSEUDO_CLASS(nthChild, ":nth-child")
 CSS_PSEUDO_CLASS(nthLastChild, ":nth-last-child")
 CSS_PSEUDO_CLASS(nthOfType, ":nth-of-type")
 CSS_PSEUDO_CLASS(nthLastOfType, ":nth-last-of-type")
 
 // Image, object, etc state pseudo-classes
 CSS_PSEUDO_CLASS(mozBroken, ":-moz-broken")
 CSS_PSEUDO_CLASS(mozUserDisabled, ":-moz-user-disabled")
--- a/layout/style/nsCSSRuleProcessor.cpp
+++ b/layout/style/nsCSSRuleProcessor.cpp
@@ -952,22 +952,23 @@ static inline PRInt32
 CSSNameSpaceID(nsIContent *aContent)
 {
   return aContent->IsNodeOfType(nsINode::eHTML)
            ? kNameSpaceID_XHTML
            : aContent->GetNameSpaceID();
 }
 
 PRInt32
-RuleProcessorData::GetNthIndex(PRBool aIsOfType, PRBool aIsFromEnd)
+RuleProcessorData::GetNthIndex(PRBool aIsOfType, PRBool aIsFromEnd,
+                               PRBool aCheckEdgeOnly)
 {
   NS_ASSERTION(mParentContent, "caller should check mParentContent");
 
   PRInt32 &slot = mNthIndices[aIsOfType][aIsFromEnd];
-  if (slot != -2)
+  if (slot != -2 && (slot != -1 || aCheckEdgeOnly))
     return slot;
 
   PRInt32 result = 1;
   nsIContent* parent = mParentContent;
 
   PRUint32 cur;
   PRInt32 increment;
   if (aIsFromEnd) {
@@ -986,18 +987,25 @@ RuleProcessorData::GetNthIndex(PRBool aI
       break;
     }
     cur += increment;
     if (child == mContent)
       break;
     if (child->IsNodeOfType(nsINode::eELEMENT) &&
         (!aIsOfType ||
          (child->Tag() == mContentTag &&
-          CSSNameSpaceID(child) == mNameSpaceID)))
+          CSSNameSpaceID(child) == mNameSpaceID))) {
+      if (aCheckEdgeOnly) {
+        // The caller only cares whether or not the result is 1, and we
+        // now know it's not.
+        result = -1;
+        break;
+      }
       ++result;
+    }
   }
 
   slot = result;
   return result;
 }
 
 static const PRUnichar kNullCh = PRUnichar('\0');
 
@@ -1211,17 +1219,17 @@ static PRBool SelectorMatches(RuleProces
           nsCSSPseudoClasses::nthLastOfType == pseudoClass->mAtom;
         if (setNodeFlags) {
           if (isFromEnd)
             parent->SetFlags(NODE_HAS_SLOW_SELECTOR);
           else
             parent->SetFlags(NODE_HAS_SLOW_SELECTOR_NOAPPEND);
         }
 
-        const PRInt32 index = data.GetNthIndex(isOfType, isFromEnd);
+        const PRInt32 index = data.GetNthIndex(isOfType, isFromEnd, PR_FALSE);
         if (index <= 0) {
           // Node is anonymous content (not really a child of its parent).
           result = PR_FALSE;
         } else {
           const PRInt32 a = pseudoClass->u.mNumbers[0];
           const PRInt32 b = pseudoClass->u.mNumbers[1];
           // result should be true if there exists n >= 0 such that
           // a * n + b == index.
@@ -1234,16 +1242,40 @@ static PRBool SelectorMatches(RuleProces
             const PRInt32 n = (index - b) / a;
             result = n >= 0 && (a * n == index - b);
           }
         }
       } else {
         result = PR_FALSE;
       }
     }
+    else if (nsCSSPseudoClasses::firstOfType == pseudoClass->mAtom ||
+             nsCSSPseudoClasses::lastOfType == pseudoClass->mAtom ||
+             nsCSSPseudoClasses::onlyOfType == pseudoClass->mAtom) {
+      nsIContent *parent = data.mParentContent;
+      if (parent) {
+        const PRBool checkFirst =
+          pseudoClass->mAtom != nsCSSPseudoClasses::lastOfType;
+        const PRBool checkLast =
+          pseudoClass->mAtom != nsCSSPseudoClasses::firstOfType;
+        if (setNodeFlags) {
+          if (checkLast)
+            parent->SetFlags(NODE_HAS_SLOW_SELECTOR);
+          else
+            parent->SetFlags(NODE_HAS_SLOW_SELECTOR_NOAPPEND);
+        }
+
+        result = (!checkFirst ||
+                  data.GetNthIndex(PR_TRUE, PR_FALSE, PR_TRUE) == 1) &&
+                 (!checkLast ||
+                  data.GetNthIndex(PR_TRUE, PR_TRUE, PR_TRUE) == 1);
+      } else {
+        result = PR_FALSE;
+      }
+    }
     else if (nsCSSPseudoClasses::empty == pseudoClass->mAtom ||
              nsCSSPseudoClasses::mozOnlyWhitespace == pseudoClass->mAtom) {
       nsIContent *child = nsnull;
       nsIContent *element = data.mContent;
       const PRBool isWhitespaceSignificant =
         nsCSSPseudoClasses::empty == pseudoClass->mAtom;
       PRInt32 index = -1;
 
--- a/layout/style/nsIStyleRuleProcessor.h
+++ b/layout/style/nsIStyleRuleProcessor.h
@@ -81,17 +81,21 @@ struct RuleProcessorData {
     aContext->FreeToShell(sizeof(RuleProcessorData), this);
   }
 
   const nsString* GetLang();
 
   // Returns a 1-based index of the child in its parent.  If the child
   // is not in its parent's child list (i.e., it is anonymous content),
   // returns 0.
-  PRInt32 GetNthIndex(PRBool aIsOfType, PRBool aIsFromEnd);
+  // If aCheckEdgeOnly is true, the function will return 1 if the result
+  // is 1, and something other than 1 (maybe or maybe not a valid
+  // result) otherwise.
+  PRInt32 GetNthIndex(PRBool aIsOfType, PRBool aIsFromEnd,
+                      PRBool aCheckEdgeOnly);
 
   nsPresContext*    mPresContext;
   nsIContent*       mContent;       // weak ref
   nsIContent*       mParentContent; // if content, content->GetParent(); weak ref
   nsRuleWalker*     mRuleWalker; // Used to add rules to our results.
   nsIContent*       mScopedRoot;    // Root of scoped stylesheet (set and unset by the supplier of the scoped stylesheet
   
   nsIAtom*          mContentTag;    // if content, then content->GetTag()
@@ -110,16 +114,17 @@ struct RuleProcessorData {
   RuleProcessorData* mPreviousSiblingData;
   RuleProcessorData* mParentData;
 
 protected:
   nsAutoString *mLanguage; // NULL means we haven't found out the language yet
 
   // This node's index for :nth-child(), :nth-last-child(),
   // :nth-of-type(), :nth-last-of-type().  If -2, needs to be computed.
+  // If -1, needs to be computed but known not to be 1.
   // If 0, the node is not at any index in its parent.
   // The first subscript is 0 for -child and 1 for -of-type, the second
   // subscript is 0 for nth- and 1 for nth-last-.
   PRInt32 mNthIndices[2][2];
 };
 
 struct ElementRuleProcessorData : public RuleProcessorData {
   ElementRuleProcessorData(nsPresContext* aPresContext,
--- a/layout/style/test/test_bug73586.html
+++ b/layout/style/test/test_bug73586.html
@@ -157,13 +157,37 @@ styleText.data = "span:nth-last-of-type(
 run_series(function(child, elt, elts, node, nodes) {
         var nlidx = elts - elt;
         var matches = nlidx % 2 == 1 && nlidx <= 5;
         is(cs(child).color, matches ? GREEN : BLACK,
            "child " + node + " should " + (matches ? "" : "NOT ") +
            " match " + styleText.data);
     });
 
+styleText.data = "span:first-of-type { color: green; }";
+run_series(function(child, elt, elts, node, nodes) {
+        var matches = (elt == 0);
+        is(cs(child).color, matches ? GREEN : BLACK,
+           "child " + node + " should " + (matches ? "" : "NOT ") +
+           " match " + styleText.data);
+    });
+
+styleText.data = "span:last-of-type { color: green; }";
+run_series(function(child, elt, elts, node, nodes) {
+        var matches = (elt == elts - 1);
+        is(cs(child).color, matches ? GREEN : BLACK,
+           "child " + node + " should " + (matches ? "" : "NOT ") +
+           " match " + styleText.data);
+    });
+
+styleText.data = "span:only-of-type { color: green; }";
+run_series(function(child, elt, elts, node, nodes) {
+        var matches = elts == 1;
+        is(cs(child).color, matches ? GREEN : BLACK,
+           "child " + node + " should " + (matches ? "" : "NOT ") +
+           " match " + styleText.data);
+    });
+
 </script>
 </pre>
 </body>
 </html>
 
--- a/layout/style/test/test_of_type_selectors.xhtml
+++ b/layout/style/test/test_of_type_selectors.xhtml
@@ -79,16 +79,19 @@ function run() {
     // 6 - :address
     // 7 - :p
     test_selector(":nth-of-type(1)", [0, 3, 4, 5, 7], [1, 2, 6]);
     test_selector(":nth-last-of-type(1)", [2, 3, 4, 6, 7], [0, 1, 5]);
     test_selector(":nth-last-of-type(-n+1)", [2, 3, 4, 6, 7], [0, 1, 5]);
     test_selector(":nth-of-type(even)", [1, 6], [0, 2, 3, 4, 5, 7]);
     test_selector(":nth-last-of-type(odd)", [0, 2, 3, 4, 6, 7], [1, 5]);
     test_selector(":nth-last-of-type(n+2)", [0, 1, 5], [2, 3, 4, 6, 7]);
+    test_selector(":first-of-type", [0, 3, 4, 5, 7], [1, 2, 6]);
+    test_selector(":last-of-type", [2, 3, 4, 6, 7], [0, 1, 5]);
+    test_selector(":only-of-type", [3, 4, 7], [0, 1, 2, 5, 6]);
 }
 
 run();
 
 ]]>
 </script>
 </pre>
 </body>