Implement :first-of-type, :last-of-type, and :only-of-type.
b=128585 r+sr=bzbarsky
--- 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>