зеркало из https://github.com/mozilla/gecko-dev.git
Implement :nth-child(), :nth-last-child(), :nth-of-type(), :nth-last-of-type(). b=75375 r+sr=bzbarsky
This commit is contained in:
Родитель
8b2c14530a
Коммит
0474b185bc
|
@ -269,6 +269,10 @@ protected:
|
|||
nsIAtom* aPseudo,
|
||||
nsresult& aErrorCode);
|
||||
|
||||
nsSelectorParsingStatus ParsePseudoClassWithNthPairArg(nsCSSSelector& aSelector,
|
||||
nsIAtom* aPseudo,
|
||||
nsresult& aErrorCode);
|
||||
|
||||
nsSelectorParsingStatus ParseNegatedSimpleSelector(PRInt32& aDataMask,
|
||||
nsCSSSelector& aSelector,
|
||||
nsresult& aErrorCode);
|
||||
|
@ -2560,7 +2564,8 @@ CSSParserImpl::ParsePseudoSelector(PRInt32& aDataMask,
|
|||
isTree ||
|
||||
#endif
|
||||
nsCSSPseudoClasses::notPseudo == pseudo ||
|
||||
nsCSSPseudoClasses::HasStringArg(pseudo))) {
|
||||
nsCSSPseudoClasses::HasStringArg(pseudo) ||
|
||||
nsCSSPseudoClasses::HasNthPairArg(pseudo))) {
|
||||
// There are no other function pseudos
|
||||
REPORT_UNEXPECTED_TOKEN(PEPseudoSelNonFunc);
|
||||
UngetToken();
|
||||
|
@ -2598,7 +2603,13 @@ CSSParserImpl::ParsePseudoSelector(PRInt32& aDataMask,
|
|||
return parsingStatus;
|
||||
}
|
||||
}
|
||||
// XXX are there more pseudo classes which accept arguments ?
|
||||
else if (nsCSSPseudoClasses::HasNthPairArg(pseudo)) {
|
||||
nsSelectorParsingStatus parsingStatus =
|
||||
ParsePseudoClassWithNthPairArg(aSelector, pseudo, aErrorCode);
|
||||
if (eSelectorParsingStatus_Continue != parsingStatus) {
|
||||
return parsingStatus;
|
||||
}
|
||||
}
|
||||
else {
|
||||
aSelector.AddPseudoClass(pseudo);
|
||||
}
|
||||
|
@ -2763,6 +2774,7 @@ CSSParserImpl::ParsePseudoClassWithIdentArg(nsCSSSelector& aSelector,
|
|||
if (eCSSToken_Ident != mToken.mType) {
|
||||
REPORT_UNEXPECTED_TOKEN(PEPseudoClassArgNotIdent);
|
||||
UngetToken();
|
||||
// XXX Call SkipUntil to the next ")"?
|
||||
return eSelectorParsingStatus_Error;
|
||||
}
|
||||
|
||||
|
@ -2772,12 +2784,144 @@ CSSParserImpl::ParsePseudoClassWithIdentArg(nsCSSSelector& aSelector,
|
|||
// close the parenthesis
|
||||
if (!ExpectSymbol(aErrorCode, ')', PR_TRUE)) {
|
||||
REPORT_UNEXPECTED_TOKEN(PEPseudoClassNoClose);
|
||||
// XXX Call SkipUntil to the next ")"?
|
||||
return eSelectorParsingStatus_Error;
|
||||
}
|
||||
|
||||
return eSelectorParsingStatus_Continue;
|
||||
}
|
||||
|
||||
CSSParserImpl::nsSelectorParsingStatus
|
||||
CSSParserImpl::ParsePseudoClassWithNthPairArg(nsCSSSelector& aSelector,
|
||||
nsIAtom* aPseudo,
|
||||
nsresult& aErrorCode)
|
||||
{
|
||||
PRInt32 numbers[2] = { 0, 0 };
|
||||
PRBool lookForB = PR_TRUE;
|
||||
|
||||
// Check whether we have the first parenthesis
|
||||
if (!ExpectSymbol(aErrorCode, '(', PR_FALSE)) {
|
||||
REPORT_UNEXPECTED_TOKEN(PEPseudoClassNoArg);
|
||||
return eSelectorParsingStatus_Error;
|
||||
}
|
||||
|
||||
// Follow the whitespace rules as proposed in
|
||||
// http://lists.w3.org/Archives/Public/www-style/2008Mar/0121.html
|
||||
|
||||
if (! GetToken(aErrorCode, PR_TRUE)) {
|
||||
REPORT_UNEXPECTED_EOF(PEPseudoClassArgEOF);
|
||||
return eSelectorParsingStatus_Error;
|
||||
}
|
||||
|
||||
if (eCSSToken_Ident == mToken.mType || eCSSToken_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.
|
||||
PRUint32 truncAt = 0;
|
||||
if (StringBeginsWith(mToken.mIdent, NS_LITERAL_STRING("n-"))) {
|
||||
truncAt = 1;
|
||||
} else if (StringBeginsWith(mToken.mIdent, NS_LITERAL_STRING("-n-"))) {
|
||||
truncAt = 2;
|
||||
}
|
||||
if (truncAt != 0) {
|
||||
for (PRUint32 i = mToken.mIdent.Length() - 1; i >= truncAt; --i) {
|
||||
mScanner.Pushback(mToken.mIdent[i]);
|
||||
}
|
||||
mToken.mIdent.Truncate(truncAt);
|
||||
}
|
||||
}
|
||||
|
||||
if (eCSSToken_Ident == mToken.mType) {
|
||||
if (mToken.mIdent.EqualsIgnoreCase("odd")) {
|
||||
numbers[0] = 2;
|
||||
numbers[1] = 1;
|
||||
lookForB = PR_FALSE;
|
||||
}
|
||||
else if (mToken.mIdent.EqualsIgnoreCase("even")) {
|
||||
numbers[0] = 2;
|
||||
numbers[1] = 0;
|
||||
lookForB = PR_FALSE;
|
||||
}
|
||||
else if (mToken.mIdent.EqualsIgnoreCase("n")) {
|
||||
numbers[0] = 1;
|
||||
}
|
||||
else if (mToken.mIdent.EqualsIgnoreCase("-n")) {
|
||||
numbers[0] = -1;
|
||||
}
|
||||
else {
|
||||
REPORT_UNEXPECTED_TOKEN(PEPseudoClassArgNotNth);
|
||||
// XXX Call SkipUntil to the next ")"?
|
||||
return eSelectorParsingStatus_Error;
|
||||
}
|
||||
}
|
||||
else if (eCSSToken_Number == mToken.mType) {
|
||||
if (!mToken.mIntegerValid) {
|
||||
REPORT_UNEXPECTED_TOKEN(PEPseudoClassArgNotNth);
|
||||
// XXX Call SkipUntil to the next ")"?
|
||||
return eSelectorParsingStatus_Error;
|
||||
}
|
||||
numbers[1] = mToken.mInteger;
|
||||
lookForB = PR_FALSE;
|
||||
}
|
||||
else if (eCSSToken_Dimension == mToken.mType) {
|
||||
if (!mToken.mIntegerValid || !mToken.mIdent.EqualsIgnoreCase("n")) {
|
||||
REPORT_UNEXPECTED_TOKEN(PEPseudoClassArgNotNth);
|
||||
// XXX Call SkipUntil to the next ")"?
|
||||
return eSelectorParsingStatus_Error;
|
||||
}
|
||||
numbers[0] = mToken.mInteger;
|
||||
}
|
||||
// XXX If it's a ')', is that valid? (as 0n+0)
|
||||
else {
|
||||
REPORT_UNEXPECTED_TOKEN(PEPseudoClassArgNotNth);
|
||||
// XXX Call SkipUntil to the next ")" (unless this is one already)?
|
||||
return eSelectorParsingStatus_Error;
|
||||
}
|
||||
|
||||
if (! GetToken(aErrorCode, PR_TRUE)) {
|
||||
REPORT_UNEXPECTED_EOF(PEPseudoClassArgEOF);
|
||||
return eSelectorParsingStatus_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.
|
||||
PRBool haveSign = PR_FALSE;
|
||||
PRInt32 sign = 1;
|
||||
if (mToken.IsSymbol('+') || mToken.IsSymbol('-')) {
|
||||
haveSign = PR_TRUE;
|
||||
if (mToken.IsSymbol('-')) {
|
||||
sign = -1;
|
||||
}
|
||||
if (! GetToken(aErrorCode, PR_TRUE)) {
|
||||
REPORT_UNEXPECTED_EOF(PEPseudoClassArgEOF);
|
||||
return eSelectorParsingStatus_Error;
|
||||
}
|
||||
}
|
||||
if (eCSSToken_Number != mToken.mType ||
|
||||
!mToken.mIntegerValid || mToken.mHasSign == haveSign) {
|
||||
REPORT_UNEXPECTED_TOKEN(PEPseudoClassArgNotNth);
|
||||
// XXX Call SkipUntil to the next ")"?
|
||||
return eSelectorParsingStatus_Error;
|
||||
}
|
||||
numbers[1] = mToken.mInteger * sign;
|
||||
if (! GetToken(aErrorCode, PR_TRUE)) {
|
||||
REPORT_UNEXPECTED_EOF(PEPseudoClassArgEOF);
|
||||
return eSelectorParsingStatus_Error;
|
||||
}
|
||||
}
|
||||
if (!mToken.IsSymbol(')')) {
|
||||
REPORT_UNEXPECTED_TOKEN(PEPseudoClassNoClose);
|
||||
// XXX Call SkipUntil to the next ")"?
|
||||
return eSelectorParsingStatus_Error;
|
||||
}
|
||||
aSelector.AddPseudoClass(aPseudo, numbers);
|
||||
return eSelectorParsingStatus_Continue;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This is the format for selectors:
|
||||
* operator? [[namespace |]? element_name]? [ ID | class | attrib | pseudo ]*
|
||||
|
|
|
@ -78,6 +78,10 @@ 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(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")
|
||||
|
|
|
@ -79,5 +79,8 @@ nsCSSPseudoClasses::HasStringArg(nsIAtom* aAtom)
|
|||
PRBool
|
||||
nsCSSPseudoClasses::HasNthPairArg(nsIAtom* aAtom)
|
||||
{
|
||||
return PR_FALSE;
|
||||
return aAtom == nsCSSPseudoClasses::nthChild ||
|
||||
aAtom == nsCSSPseudoClasses::nthLastChild ||
|
||||
aAtom == nsCSSPseudoClasses::nthOfType ||
|
||||
aAtom == nsCSSPseudoClasses::nthLastOfType;
|
||||
}
|
||||
|
|
|
@ -827,6 +827,10 @@ RuleProcessorData::RuleProcessorData(nsPresContext* aPresContext,
|
|||
mParentData = nsnull;
|
||||
mLanguage = nsnull;
|
||||
mClasses = nsnull;
|
||||
mNthIndices[0][0] = -2;
|
||||
mNthIndices[0][1] = -2;
|
||||
mNthIndices[1][0] = -2;
|
||||
mNthIndices[1][1] = -2;
|
||||
|
||||
// get the compat. mode (unless it is provided)
|
||||
if (!aCompat) {
|
||||
|
@ -944,6 +948,57 @@ const nsString* RuleProcessorData::GetLang()
|
|||
return mLanguage;
|
||||
}
|
||||
|
||||
static inline PRInt32
|
||||
CSSNameSpaceID(nsIContent *aContent)
|
||||
{
|
||||
return aContent->IsNodeOfType(nsINode::eHTML)
|
||||
? kNameSpaceID_XHTML
|
||||
: aContent->GetNameSpaceID();
|
||||
}
|
||||
|
||||
PRInt32
|
||||
RuleProcessorData::GetNthIndex(PRBool aIsOfType, PRBool aIsFromEnd)
|
||||
{
|
||||
NS_ASSERTION(mParentContent, "caller should check mParentContent");
|
||||
|
||||
PRInt32 &slot = mNthIndices[aIsOfType][aIsFromEnd];
|
||||
if (slot != -2)
|
||||
return slot;
|
||||
|
||||
PRInt32 result = 1;
|
||||
nsIContent* parent = mParentContent;
|
||||
|
||||
PRUint32 cur;
|
||||
PRInt32 increment;
|
||||
if (aIsFromEnd) {
|
||||
cur = parent->GetChildCount() - 1;
|
||||
increment = -1;
|
||||
} else {
|
||||
cur = 0;
|
||||
increment = 1;
|
||||
}
|
||||
|
||||
for (;;) {
|
||||
nsIContent* child = parent->GetChildAt(cur);
|
||||
if (!child) {
|
||||
// mContent is the root of an anonymous content subtree.
|
||||
result = 0; // special value to indicate that it is not at any index
|
||||
break;
|
||||
}
|
||||
cur += increment;
|
||||
if (child == mContent)
|
||||
break;
|
||||
if (child->IsNodeOfType(nsINode::eELEMENT) &&
|
||||
(!aIsOfType ||
|
||||
(child->Tag() == mContentTag &&
|
||||
CSSNameSpaceID(child) == mNameSpaceID)))
|
||||
++result;
|
||||
}
|
||||
|
||||
slot = result;
|
||||
return result;
|
||||
}
|
||||
|
||||
static const PRUnichar kNullCh = PRUnichar('\0');
|
||||
|
||||
static PRBool ValueIncludes(const nsSubstring& aValueList,
|
||||
|
@ -1142,6 +1197,48 @@ static PRBool SelectorMatches(RuleProcessorData &data,
|
|||
}
|
||||
result = (data.mContent == onlyChild && moreChild == nsnull);
|
||||
}
|
||||
else if (nsCSSPseudoClasses::nthChild == pseudoClass->mAtom ||
|
||||
nsCSSPseudoClasses::nthLastChild == pseudoClass->mAtom ||
|
||||
nsCSSPseudoClasses::nthOfType == pseudoClass->mAtom ||
|
||||
nsCSSPseudoClasses::nthLastOfType == pseudoClass->mAtom) {
|
||||
nsIContent *parent = data.mParentContent;
|
||||
if (parent) {
|
||||
PRBool isOfType =
|
||||
nsCSSPseudoClasses::nthOfType == pseudoClass->mAtom ||
|
||||
nsCSSPseudoClasses::nthLastOfType == pseudoClass->mAtom;
|
||||
PRBool isFromEnd =
|
||||
nsCSSPseudoClasses::nthLastChild == pseudoClass->mAtom ||
|
||||
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);
|
||||
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.
|
||||
if (a == 0) {
|
||||
result = b == index;
|
||||
} else {
|
||||
// Integer division in C does truncation (towards 0). So
|
||||
// check that the result is nonnegative, and that there was no
|
||||
// truncation.
|
||||
const PRInt32 n = (index - b) / a;
|
||||
result = n >= 0 && (a * n == index - b);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
result = PR_FALSE;
|
||||
}
|
||||
}
|
||||
else if (nsCSSPseudoClasses::empty == pseudoClass->mAtom ||
|
||||
nsCSSPseudoClasses::mozOnlyWhitespace == pseudoClass->mAtom) {
|
||||
nsIContent *child = nsnull;
|
||||
|
|
|
@ -1000,7 +1000,8 @@ PRBool nsCSSScanner::ParseNumber(nsresult& aErrorCode, PRInt32 c,
|
|||
{
|
||||
nsString& ident = aToken.mIdent;
|
||||
ident.SetLength(0);
|
||||
PRBool gotDot = (c == '.') ? PR_TRUE : PR_FALSE;
|
||||
PRBool gotDot = (c == '.');
|
||||
aToken.mHasSign = (c == '+' || c == '-');
|
||||
if (c != '+') {
|
||||
ident.Append(PRUnichar(c));
|
||||
}
|
||||
|
@ -1023,11 +1024,18 @@ PRBool nsCSSScanner::ParseNumber(nsresult& aErrorCode, PRInt32 c,
|
|||
PRInt32 ec;
|
||||
float value = ident.ToFloat(&ec);
|
||||
|
||||
// Look at character that terminated the number
|
||||
// Set mIntegerValid for all cases (except %, below) because we need
|
||||
// it for the "2n" in :nth-child(2n).
|
||||
aToken.mIntegerValid = PR_FALSE;
|
||||
if (!gotDot) {
|
||||
aToken.mInteger = ident.ToInteger(&ec);
|
||||
aToken.mIntegerValid = PR_TRUE;
|
||||
}
|
||||
ident.SetLength(0);
|
||||
|
||||
// Look at character that terminated the number
|
||||
if (c >= 0) {
|
||||
if (StartsIdent(c, Peek(aErrorCode))) {
|
||||
ident.SetLength(0);
|
||||
if (!GatherIdent(aErrorCode, c, ident)) {
|
||||
return PR_FALSE;
|
||||
}
|
||||
|
@ -1035,24 +1043,12 @@ PRBool nsCSSScanner::ParseNumber(nsresult& aErrorCode, PRInt32 c,
|
|||
} else if ('%' == c) {
|
||||
type = eCSSToken_Percentage;
|
||||
value = value / 100.0f;
|
||||
ident.SetLength(0);
|
||||
aToken.mIntegerValid = PR_FALSE;
|
||||
} else {
|
||||
// Put back character that stopped numeric scan
|
||||
Pushback(c);
|
||||
if (!gotDot) {
|
||||
aToken.mInteger = ident.ToInteger(&ec);
|
||||
aToken.mIntegerValid = PR_TRUE;
|
||||
}
|
||||
ident.SetLength(0);
|
||||
}
|
||||
}
|
||||
else { // stream ended
|
||||
if (!gotDot) {
|
||||
aToken.mInteger = ident.ToInteger(&ec);
|
||||
aToken.mIntegerValid = PR_TRUE;
|
||||
}
|
||||
ident.SetLength(0);
|
||||
}
|
||||
aToken.mNumber = value;
|
||||
aToken.mType = type;
|
||||
return PR_TRUE;
|
||||
|
|
|
@ -105,7 +105,8 @@ enum nsCSSTokenType {
|
|||
|
||||
struct nsCSSToken {
|
||||
nsCSSTokenType mType;
|
||||
PRPackedBool mIntegerValid;
|
||||
PRPackedBool mIntegerValid; // for number and dimension
|
||||
PRPackedBool mHasSign; // for number, percentage, and dimension
|
||||
nsAutoString mIdent;
|
||||
float mNumber;
|
||||
PRInt32 mInteger;
|
||||
|
@ -178,6 +179,12 @@ class nsCSSScanner {
|
|||
// Get the next token that may be a string or unquoted URL or whitespace
|
||||
PRBool NextURL(nsresult& aErrorCode, nsCSSToken& aTokenResult);
|
||||
|
||||
// It's really ugly that we have to expose this, but it's the easiest
|
||||
// way to do :nth-child() parsing sanely. (In particular, in
|
||||
// :nth-child(2n-1), "2n-1" is a dimension, and we need to push the
|
||||
// "-1" back so we can read it again as a number.)
|
||||
void Pushback(PRUnichar aChar);
|
||||
|
||||
static inline PRBool
|
||||
IsIdentStart(PRInt32 aChar)
|
||||
{
|
||||
|
@ -212,7 +219,6 @@ protected:
|
|||
PRBool EnsureData(nsresult& aErrorCode);
|
||||
PRInt32 Read(nsresult& aErrorCode);
|
||||
PRInt32 Peek(nsresult& aErrorCode);
|
||||
void Pushback(PRUnichar aChar);
|
||||
PRBool LookAhead(nsresult& aErrorCode, PRUnichar aChar);
|
||||
PRBool EatWhiteSpace(nsresult& aErrorCode);
|
||||
PRBool EatNewline(nsresult& aErrorCode);
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
* the Initial Developer. All Rights Reserved.
|
||||
*
|
||||
* Contributor(s):
|
||||
* L. David Baron <dbaron@dbaron.org>, Mozilla Corporation
|
||||
*
|
||||
* Alternatively, the contents of this file may be used under the terms of
|
||||
* either of the GNU General Public License Version 2 or later (the "GPL"),
|
||||
|
@ -82,7 +83,12 @@ struct RuleProcessorData {
|
|||
|
||||
const nsString* GetLang();
|
||||
|
||||
nsPresContext* mPresContext;
|
||||
// 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);
|
||||
|
||||
nsPresContext* mPresContext;
|
||||
nsIContent* mContent; // weak ref
|
||||
nsIContent* mParentContent; // if content, content->GetParent(); weak ref
|
||||
nsRuleWalker* mRuleWalker; // Used to add rules to our results.
|
||||
|
@ -106,6 +112,13 @@ struct RuleProcessorData {
|
|||
|
||||
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 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 {
|
||||
|
|
|
@ -99,10 +99,12 @@ _TEST_FILES = test_bug73586.html \
|
|||
test_inherit_computation.html \
|
||||
test_initial_storage.html \
|
||||
test_initial_computation.html \
|
||||
test_of_type_selectors.xhtml \
|
||||
test_parse_rule.html \
|
||||
test_property_database.html \
|
||||
test_property_syntax_errors.html \
|
||||
test_selectors.html \
|
||||
test_selectors_on_anonymous_content.html \
|
||||
test_style_struct_copy_constructors.html \
|
||||
test_value_storage.html \
|
||||
test_value_computation.html \
|
||||
|
@ -121,6 +123,7 @@ _TEST_FILES = test_bug73586.html \
|
|||
redirect-3.css \
|
||||
redirect-3.css^headers^ \
|
||||
post-redirect-3.css \
|
||||
xbl_bindings.xml \
|
||||
$(NULL)
|
||||
|
||||
|
||||
|
|
|
@ -128,6 +128,40 @@ run_series(function(child, elt, elts, node, nodes) {
|
|||
" match :-moz-last-node");
|
||||
});
|
||||
|
||||
styleText.data = "span:nth-child(1) { background: lime; }";
|
||||
run_series(function(child, elt, elts, node, nodes) {
|
||||
var matches = elt == 0;
|
||||
is(cs(child).backgroundColor, matches ? LIME : WHITE,
|
||||
"child " + node + " should " + (matches ? "" : "NOT ") +
|
||||
" match " + styleText.data);
|
||||
});
|
||||
|
||||
styleText.data = "span:nth-last-child(0n+2) { color: green; }";
|
||||
run_series(function(child, elt, elts, node, nodes) {
|
||||
var matches = (elt == elts - 2);
|
||||
is(cs(child).color, matches ? GREEN : BLACK,
|
||||
"child " + node + " should " + (matches ? "" : "NOT ") +
|
||||
" match " + styleText.data);
|
||||
});
|
||||
|
||||
styleText.data = "span:nth-of-type(2n+3) { color: green; }";
|
||||
run_series(function(child, elt, elts, node, nodes) {
|
||||
var nidx = elt + 1;
|
||||
var matches = nidx % 2 == 1 && nidx >= 3;
|
||||
is(cs(child).color, matches ? GREEN : BLACK,
|
||||
"child " + node + " should " + (matches ? "" : "NOT ") +
|
||||
" match " + styleText.data);
|
||||
});
|
||||
|
||||
styleText.data = "span:nth-last-of-type(-2n+5) { color: green; }";
|
||||
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);
|
||||
});
|
||||
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
|
|
|
@ -0,0 +1,96 @@
|
|||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<!--
|
||||
https://bugzilla.mozilla.org/show_bug.cgi?id=75375
|
||||
-->
|
||||
<head>
|
||||
<title>Test for *-of-type selectors in Bug 75375</title>
|
||||
<script type="text/javascript" src="/MochiKit/packed.js"></script>
|
||||
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
|
||||
</head>
|
||||
<body>
|
||||
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=75375">Mozilla Bug 75375</a>
|
||||
<div id="content" style="display: none"
|
||||
xmlns:html="http://www.w3.org/1999/xhtml">
|
||||
|
||||
<p>This is a <code>p</code> element in the HTML namespace.</p>
|
||||
<p>This is a second <code>p</code> element in the HTML namespace.</p>
|
||||
<html:p>This is an <code>html:p</code> element in the HTML namespace.</html:p>
|
||||
<p xmlns="http://www.example.com/ns">This is a <code>p</code> element in the <code>http://www.example.com/ns</code> namespace.</p>
|
||||
<html:address>This is an <code>html:address</code> element in the HTML namespace.</html:address>
|
||||
<address xmlns="">This is a <code>address</code> element in no namespace.</address>
|
||||
<address xmlns="">This is a <code>address</code> element in no namespace.</address>
|
||||
<p xmlns="">This is a <code>p</code> element in no namespace.</p>
|
||||
|
||||
</div>
|
||||
<pre id="test">
|
||||
<script class="testbody" type="text/javascript">
|
||||
<![CDATA[
|
||||
|
||||
/** Test for *-of-type selectors in Bug 75375 **/
|
||||
|
||||
var HTML_NS = "http://www.w3.org/1999/xhtml";
|
||||
|
||||
function setup_style_text() {
|
||||
var result = document.createCDATASection("");
|
||||
var style = document.createElementNS(HTML_NS, "style");
|
||||
style.appendChild(result);
|
||||
document.getElementsByTagName("head")[0].appendChild(style);
|
||||
return result;
|
||||
}
|
||||
|
||||
function run() {
|
||||
var styleText = setup_style_text();
|
||||
|
||||
var elements = [];
|
||||
|
||||
var div = document.getElementById("content");
|
||||
for (var i = 0; i < div.childNodes.length; ++i) {
|
||||
var child = div.childNodes[i];
|
||||
if (child.nodeType == Node.ELEMENT_NODE)
|
||||
elements.push(child);
|
||||
}
|
||||
|
||||
var counter = 0;
|
||||
|
||||
function test_selector(selector, match_indices, notmatch_indices)
|
||||
{
|
||||
var zi = ++counter;
|
||||
styleText.data = selector + " { z-index: " + zi + " }";
|
||||
var i;
|
||||
for (i in match_indices) {
|
||||
var e = elements[match_indices[i]];
|
||||
is(getComputedStyle(e, "").zIndex, zi,
|
||||
"element " + match_indices[i] + " matched " + selector);
|
||||
}
|
||||
for (i in notmatch_indices) {
|
||||
var e = elements[notmatch_indices[i]];
|
||||
is(getComputedStyle(e, "").zIndex, "auto",
|
||||
"element " + notmatch_indices[i] + " did not match " + selector);
|
||||
}
|
||||
}
|
||||
|
||||
// 0 - html:p
|
||||
// 1 - html:p
|
||||
// 2 - html:p
|
||||
// 3 - example:p
|
||||
// 4 - html:address
|
||||
// 5 - :address
|
||||
// 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]);
|
||||
}
|
||||
|
||||
run();
|
||||
|
||||
]]>
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -43,7 +43,13 @@ function run() {
|
|||
function test_selector_in_html(selector, body_contents, match_fn, notmatch_fn)
|
||||
{
|
||||
var zi = ++gCounter;
|
||||
ifdoc.body.innerHTML = body_contents;
|
||||
if (typeof(body_contents) == "string") {
|
||||
ifdoc.body.innerHTML = body_contents;
|
||||
} else {
|
||||
// It's a function.
|
||||
ifdoc.body.innerHTML = "";
|
||||
body_contents(ifdoc.body);
|
||||
}
|
||||
style_text.data = selector + "{ z-index: " + zi + " }";
|
||||
var should_match = match_fn(ifdoc);
|
||||
var should_not_match = notmatch_fn(ifdoc);
|
||||
|
@ -92,6 +98,35 @@ function run() {
|
|||
style_text.data = "";
|
||||
}
|
||||
|
||||
function test_parseable(selector)
|
||||
{
|
||||
var zi = ++gCounter;
|
||||
ifdoc.body.innerHTML = "<p></p>";
|
||||
style_text.data = "p, " + selector + "{ z-index: " + zi + " }";
|
||||
var should_match = ifdoc.getElementsByTagName("p")[0];
|
||||
is(ifwin.getComputedStyle(should_match, "").zIndex, zi,
|
||||
"selector " + selector + " was parsed");
|
||||
ifdoc.body.innerHTML = "";
|
||||
style_text.data = "";
|
||||
}
|
||||
|
||||
function test_balanced_unparseable(selector)
|
||||
{
|
||||
var zi1 = ++gCounter;
|
||||
var zi2 = ++gCounter;
|
||||
ifdoc.body.innerHTML = "<p></p><div></div>";
|
||||
style_text.data = "p, " + selector + "{ z-index: " + zi1 + " }" +
|
||||
"div { z-index: " + zi2 + " }";
|
||||
var should_not_match = ifdoc.getElementsByTagName("p")[0];
|
||||
var should_match = ifdoc.getElementsByTagName("div")[0];
|
||||
is(ifwin.getComputedStyle(should_not_match, "").zIndex, "auto",
|
||||
"selector " + selector + " was a parser error");
|
||||
is(ifwin.getComputedStyle(should_match, "").zIndex, zi2,
|
||||
"selector " + selector + " error was recovered from");
|
||||
ifdoc.body.innerHTML = "";
|
||||
style_text.data = "";
|
||||
}
|
||||
|
||||
// Bug 420814
|
||||
test_selector_in_html(
|
||||
"div ~ div p",
|
||||
|
@ -100,6 +135,160 @@ function run() {
|
|||
function(doc) { return []; }
|
||||
);
|
||||
|
||||
// :nth-child(), etc.
|
||||
// Follow the whitespace rules as proposed in
|
||||
// http://lists.w3.org/Archives/Public/www-style/2008Mar/0121.html
|
||||
test_balanced_unparseable(":nth-child()");
|
||||
test_balanced_unparseable(":nth-of-type( )");
|
||||
test_parseable(":nth-last-child( odd)");
|
||||
test_parseable(":nth-last-of-type(even )");
|
||||
test_parseable(":nth-child(n )");
|
||||
test_parseable(":nth-of-type( 2n)");
|
||||
test_parseable(":nth-last-child( -n)");
|
||||
test_parseable(":nth-last-of-type(-2n )");
|
||||
test_balanced_unparseable(":nth-child(- n)");
|
||||
test_balanced_unparseable(":nth-of-type(-2 n)");
|
||||
test_balanced_unparseable(":nth-last-of-type(2n1)");
|
||||
test_balanced_unparseable(":nth-child(2n++1)");
|
||||
test_balanced_unparseable(":nth-of-type(2n-+1)");
|
||||
test_balanced_unparseable(":nth-last-child(2n+-1)");
|
||||
test_balanced_unparseable(":nth-last-of-type(2n--1)");
|
||||
test_parseable(":nth-child( 3n + 1 )");
|
||||
test_parseable(":nth-child( +3n - 2 )");
|
||||
test_parseable(":nth-child( -n+ 6)");
|
||||
test_parseable(":nth-child( +6 )");
|
||||
test_balanced_unparseable(":nth-child(3 n)");
|
||||
test_balanced_unparseable(":nth-child(+ 2n)");
|
||||
test_balanced_unparseable(":nth-child(+ 2)");
|
||||
test_parseable(":nth-child(3)");
|
||||
test_parseable(":nth-of-type(-3)");
|
||||
test_parseable(":nth-last-child(+3)");
|
||||
test_parseable(":nth-last-of-type(0)");
|
||||
test_parseable(":nth-child(-0)");
|
||||
test_parseable(":nth-of-type(3n)");
|
||||
test_parseable(":nth-last-child(-3n)");
|
||||
test_parseable(":nth-last-of-type(+3n)");
|
||||
test_parseable(":nth-last-of-type(0n)");
|
||||
test_parseable(":nth-child(-0n)");
|
||||
test_parseable(":nth-of-type(n)");
|
||||
test_parseable(":nth-last-child(-n)");
|
||||
test_parseable(":nth-last-of-type(2n+1)");
|
||||
test_parseable(":nth-child(2n-1)");
|
||||
test_parseable(":nth-of-type(2n+0)");
|
||||
test_parseable(":nth-last-child(2n-0)");
|
||||
test_parseable(":nth-child(-0n+0)");
|
||||
test_parseable(":nth-of-type(n+1)");
|
||||
test_parseable(":nth-last-child(n-1)");
|
||||
test_parseable(":nth-last-of-type(-n+1)");
|
||||
test_parseable(":nth-child(-n-1)");
|
||||
test_balanced_unparseable(":nth-child(2-n)");
|
||||
test_balanced_unparseable(":nth-child(2-n-1)");
|
||||
test_balanced_unparseable(":nth-child(n-2-1)");
|
||||
|
||||
// exercise the an+b matching logic particularly hard for
|
||||
// :nth-child() (since we know we use the same code for all 4)
|
||||
var seven_ps = "<p></p><p></p><p></p><p></p><p></p><p></p><p></p>";
|
||||
function pset(indices) { // takes an array of 1-based indices
|
||||
return function pset_filter(doc) {
|
||||
var a = doc.getElementsByTagName("p");
|
||||
var result = [];
|
||||
for (var i in indices)
|
||||
result.push(a[indices[i] - 1]);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
test_selector_in_html(":nth-child(0)", seven_ps,
|
||||
pset([]), pset([1, 2, 3, 4, 5, 6, 7]));
|
||||
test_selector_in_html(":nth-child(-3)", seven_ps,
|
||||
pset([]), pset([1, 2, 3, 4, 5, 6, 7]));
|
||||
test_selector_in_html(":nth-child(3)", seven_ps,
|
||||
pset([3]), pset([1, 2, 4, 5, 6, 7]));
|
||||
test_selector_in_html(":nth-child(0n+3)", seven_ps,
|
||||
pset([3]), pset([1, 2, 4, 5, 6, 7]));
|
||||
test_selector_in_html(":nth-child(-0n+3)", seven_ps,
|
||||
pset([3]), pset([1, 2, 4, 5, 6, 7]));
|
||||
test_selector_in_html(":nth-child(8)", seven_ps,
|
||||
pset([]), pset([1, 2, 3, 4, 5, 6, 7]));
|
||||
test_selector_in_html(":nth-child(odd)", seven_ps,
|
||||
pset([1, 3, 5, 7]), pset([2, 4, 6]));
|
||||
test_selector_in_html(":nth-child(even)", seven_ps,
|
||||
pset([2, 4, 6]), pset([1, 3, 5, 7]));
|
||||
test_selector_in_html(":nth-child(2n-1)", seven_ps,
|
||||
pset([1, 3, 5, 7]), pset([2, 4, 6]));
|
||||
test_selector_in_html(":nth-child( 2n - 1 )", seven_ps,
|
||||
pset([1, 3, 5, 7]), pset([2, 4, 6]));
|
||||
test_selector_in_html(":nth-child(2n+1)", seven_ps,
|
||||
pset([1, 3, 5, 7]), pset([2, 4, 6]));
|
||||
test_selector_in_html(":nth-child( 2n + 1 )", seven_ps,
|
||||
pset([1, 3, 5, 7]), pset([2, 4, 6]));
|
||||
test_selector_in_html(":nth-child(2n+0)", seven_ps,
|
||||
pset([2, 4, 6]), pset([1, 3, 5, 7]));
|
||||
test_selector_in_html(":nth-child(2n-0)", seven_ps,
|
||||
pset([2, 4, 6]), pset([1, 3, 5, 7]));
|
||||
test_selector_in_html(":nth-child(-n+3)", seven_ps,
|
||||
pset([1, 2, 3]), pset([4, 5, 6, 7]));
|
||||
test_selector_in_html(":nth-child(-n-3)", seven_ps,
|
||||
pset([]), pset([1, 2, 3, 4, 5, 6, 7]));
|
||||
test_selector_in_html(":nth-child(n)", seven_ps,
|
||||
pset([1, 2, 3, 4, 5, 6, 7]), pset([]));
|
||||
test_selector_in_html(":nth-child(n-3)", seven_ps,
|
||||
pset([1, 2, 3, 4, 5, 6, 7]), pset([]));
|
||||
test_selector_in_html(":nth-child(n+3)", seven_ps,
|
||||
pset([3, 4, 5, 6, 7]), pset([1, 2]));
|
||||
test_selector_in_html(":nth-child(2n+3)", seven_ps,
|
||||
pset([3, 5, 7]), pset([1, 2, 4, 6]));
|
||||
test_selector_in_html(":nth-child(2n)", seven_ps,
|
||||
pset([2, 4, 6]), pset([1, 3, 5, 7]));
|
||||
test_selector_in_html(":nth-child(2n-3)", seven_ps,
|
||||
pset([1, 3, 5, 7]), pset([2, 4, 6]));
|
||||
test_selector_in_html(":nth-child(-1n+3)", seven_ps,
|
||||
pset([1, 2, 3]), pset([4, 5, 6, 7]));
|
||||
test_selector_in_html(":nth-child(-2n+3)", seven_ps,
|
||||
pset([1, 3]), pset([2, 4, 5, 6, 7]));
|
||||
// And a few spot-checks for the other :nth-* selectors
|
||||
test_selector_in_html(":nth-child(4n+1)", seven_ps,
|
||||
pset([1, 5]), pset([2, 3, 4, 6, 7]));
|
||||
test_selector_in_html(":nth-last-child(4n+1)", seven_ps,
|
||||
pset([3, 7]), pset([1, 2, 4, 5, 6]));
|
||||
test_selector_in_html(":nth-of-type(4n+1)", seven_ps,
|
||||
pset([1, 5]), pset([2, 3, 4, 6, 7]));
|
||||
test_selector_in_html(":nth-last-of-type(4n+1)", seven_ps,
|
||||
pset([3, 7]), pset([1, 2, 4, 5, 6]));
|
||||
test_selector_in_html(":nth-child(6)", seven_ps,
|
||||
pset([6]), pset([1, 2, 3, 4, 5, 7]));
|
||||
test_selector_in_html(":nth-last-child(6)", seven_ps,
|
||||
pset([2]), pset([1, 3, 4, 5, 6, 7]));
|
||||
test_selector_in_html(":nth-of-type(6)", seven_ps,
|
||||
pset([6]), pset([1, 2, 3, 4, 5, 7]));
|
||||
test_selector_in_html(":nth-last-of-type(6)", seven_ps,
|
||||
pset([2]), pset([1, 3, 4, 5, 6, 7]));
|
||||
|
||||
// And a bunch of tests for the of-type aspect of :nth-of-type() and
|
||||
// :nth-last-of-type(). Note that the last div here contains two
|
||||
// children.
|
||||
var mixed_elements="<p></p><p></p><div></div><p></p><div><p></p><address></address></div><address></address>";
|
||||
function pdaset(ps, divs, addresses) { // takes an array of 1-based indices
|
||||
var l = { p: ps, div: divs, address: addresses };
|
||||
return function pdaset_filter(doc) {
|
||||
var result = [];
|
||||
for (var tag in l) {
|
||||
var a = doc.getElementsByTagName(tag);
|
||||
var indices = l[tag];
|
||||
for (var i in indices)
|
||||
result.push(a[indices[i] - 1]);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
test_selector_in_html(":nth-of-type(odd)", mixed_elements,
|
||||
pdaset([1, 3, 4], [1], [1, 2]),
|
||||
pdaset([2], [2], []));
|
||||
test_selector_in_html(":nth-of-type(2n-0)", mixed_elements,
|
||||
pdaset([2], [2], []),
|
||||
pdaset([1, 3, 4], [1], [1, 2]));
|
||||
test_selector_in_html(":nth-last-of-type(even)", mixed_elements,
|
||||
pdaset([2], [1], []),
|
||||
pdaset([1, 3, 4], [2], [1, 2]));
|
||||
|
||||
SimpleTest.finish();
|
||||
}
|
||||
|
|
|
@ -0,0 +1,79 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<title>Test for CSS Selectors</title>
|
||||
<!--
|
||||
Separate from test_selectors.html so we don't need to deal with
|
||||
waiting for the binding document to load.
|
||||
-->
|
||||
<script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
|
||||
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
|
||||
<style type="text/css">
|
||||
|
||||
#display { -moz-binding: url(xbl_bindings.xml#onedivchild); }
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body onload="run()">
|
||||
<div id="display"></div>
|
||||
<pre id="test">
|
||||
<script class="testbody" type="text/javascript">
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
|
||||
function run() {
|
||||
|
||||
function setup_style() {
|
||||
var style_elem = document.createElement("style");
|
||||
style_elem.setAttribute("type", "text/css");
|
||||
document.getElementsByTagName("head")[0].appendChild(style_elem);
|
||||
var style_text = document.createTextNode("");
|
||||
style_elem.appendChild(style_text);
|
||||
return style_text;
|
||||
}
|
||||
|
||||
var style_text = setup_style();
|
||||
|
||||
var gCounter = 0;
|
||||
|
||||
function test_selector(selector, matches_docdiv, matches_anondiv)
|
||||
{
|
||||
var zi = ++gCounter;
|
||||
style_text.data = selector + "{ z-index: " + zi + " }";
|
||||
|
||||
var doc_div = document.getElementById("display");
|
||||
var anon_div = document.getAnonymousNodes(doc_div)[0];
|
||||
var should_match = [];
|
||||
var should_not_match = [];
|
||||
(matches_docdiv ? should_match : should_not_match).push(doc_div);
|
||||
(matches_anondiv ? should_match : should_not_match).push(anon_div);
|
||||
|
||||
for (var i = 0; i < should_match.length; ++i) {
|
||||
var e = should_match[i];
|
||||
is(getComputedStyle(e, "").zIndex, zi,
|
||||
"element matched " + selector);
|
||||
}
|
||||
for (var i = 0; i < should_not_match.length; ++i) {
|
||||
var e = should_not_match[i];
|
||||
is(getComputedStyle(e, "").zIndex, "auto",
|
||||
"element did not match " + selector);
|
||||
}
|
||||
|
||||
style_text.data = "";
|
||||
}
|
||||
|
||||
// Test that the root of an XBL1 anonymous content subtree doesn't
|
||||
// match :nth-child().
|
||||
test_selector("div.anondiv", false, true);
|
||||
test_selector("div:nth-child(odd)", true, false);
|
||||
test_selector("div:nth-child(even)", false, false);
|
||||
|
||||
SimpleTest.finish();
|
||||
}
|
||||
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
<?xml version="1.0"?>
|
||||
<bindings xmlns="http://www.mozilla.org/xbl"
|
||||
xmlns:html="http://www.w3.org/1999/xhtml">
|
||||
|
||||
<binding id="onedivchild">
|
||||
<content><html:div class="anondiv" /></content>
|
||||
</binding>
|
||||
|
||||
</bindings>
|
Загрузка…
Ссылка в новой задаче