Bug 499655. Selectors should have dual atoms: HTML and other. r=dbaron

This commit is contained in:
David Zbarsky
2009-07-30 13:28:42 -04:00
parent 23db8f4fa3
commit c69e697fad
8 changed files with 190 additions and 87 deletions

View File

@@ -2452,7 +2452,7 @@ CSSParserImpl::ParseSelectorList(nsCSSSelectorList*& aListHead,
static PRBool IsSinglePseudoClass(const nsCSSSelector& aSelector) static PRBool IsSinglePseudoClass(const nsCSSSelector& aSelector)
{ {
return PRBool((aSelector.mNameSpace == kNameSpaceID_Unknown) && return PRBool((aSelector.mNameSpace == kNameSpaceID_Unknown) &&
(aSelector.mTag == nsnull) && (aSelector.mLowercaseTag == nsnull) &&
(aSelector.mIDList == nsnull) && (aSelector.mIDList == nsnull) &&
(aSelector.mClassList == nsnull) && (aSelector.mClassList == nsnull) &&
(aSelector.mAttrList == nsnull) && (aSelector.mAttrList == nsnull) &&
@@ -2527,7 +2527,7 @@ CSSParserImpl::ParseSelectorGroup(nsCSSSelectorList*& aList)
list->AddSelector(empty); // leave a blank (universal) selector in the middle list->AddSelector(empty); // leave a blank (universal) selector in the middle
listSel = list->mSelectors; // use the new one for the pseudo listSel = list->mSelectors; // use the new one for the pseudo
} }
listSel->mTag = pseudoElement; listSel->mLowercaseTag = pseudoElement;
} }
else { // append new pseudo element selector else { // append new pseudo element selector
nsAutoPtr<nsCSSSelector> pseudoTagSelector(new nsCSSSelector()); nsAutoPtr<nsCSSSelector> pseudoTagSelector(new nsCSSSelector());
@@ -2535,9 +2535,9 @@ CSSParserImpl::ParseSelectorGroup(nsCSSSelectorList*& aList)
mScanner.SetLowLevelError(NS_ERROR_OUT_OF_MEMORY); mScanner.SetLowLevelError(NS_ERROR_OUT_OF_MEMORY);
return PR_FALSE; return PR_FALSE;
} }
pseudoTagSelector->mTag = pseudoClassList->mAtom; // steal ref count pseudoTagSelector->mLowercaseTag = pseudoClassList->mAtom; // steal ref count
#ifdef MOZ_XUL #ifdef MOZ_XUL
if (IsTreePseudoElement(pseudoTagSelector->mTag)) { if (IsTreePseudoElement(pseudoTagSelector->mLowercaseTag)) {
// Take the remaining "pseudoclasses" that we parsed // Take the remaining "pseudoclasses" that we parsed
// inside the tree pseudoelement's ()-list, and // inside the tree pseudoelement's ()-list, and
// make our new selector have these pseudoclasses // make our new selector have these pseudoclasses
@@ -2684,13 +2684,8 @@ CSSParserImpl::ParseTypeOrUniversalSelector(PRInt32& aDataMask,
} }
if (eCSSToken_Ident == mToken.mType) { // element name if (eCSSToken_Ident == mToken.mType) { // element name
aDataMask |= SEL_MASK_ELEM; aDataMask |= SEL_MASK_ELEM;
if (mCaseSensitive) {
aSelector.SetTag(mToken.mIdent); aSelector.SetTag(mToken.mIdent, mCaseSensitive);
}
else {
ToLowerCase(mToken.mIdent, buffer);
aSelector.SetTag(buffer);
}
} }
else if (mToken.IsSymbol('*')) { // universal selector else if (mToken.IsSymbol('*')) { // universal selector
aDataMask |= SEL_MASK_ELEM; aDataMask |= SEL_MASK_ELEM;
@@ -2728,13 +2723,8 @@ CSSParserImpl::ParseTypeOrUniversalSelector(PRInt32& aDataMask,
} }
if (eCSSToken_Ident == mToken.mType) { // element name if (eCSSToken_Ident == mToken.mType) { // element name
aDataMask |= SEL_MASK_ELEM; aDataMask |= SEL_MASK_ELEM;
if (mCaseSensitive) {
aSelector.SetTag(mToken.mIdent); aSelector.SetTag(mToken.mIdent, mCaseSensitive);
}
else {
ToLowerCase(mToken.mIdent, buffer);
aSelector.SetTag(buffer);
}
} }
else if (mToken.IsSymbol('*')) { // universal selector else if (mToken.IsSymbol('*')) { // universal selector
aDataMask |= SEL_MASK_ELEM; aDataMask |= SEL_MASK_ELEM;
@@ -2748,13 +2738,8 @@ CSSParserImpl::ParseTypeOrUniversalSelector(PRInt32& aDataMask,
} }
else { // was element name else { // was element name
SetDefaultNamespaceOnSelector(aSelector); SetDefaultNamespaceOnSelector(aSelector);
if (mCaseSensitive) { aSelector.SetTag(buffer, mCaseSensitive);
aSelector.SetTag(buffer);
}
else {
ToLowerCase(buffer);
aSelector.SetTag(buffer);
}
aDataMask |= SEL_MASK_ELEM; aDataMask |= SEL_MASK_ELEM;
} }
if (! GetToken(PR_FALSE)) { // premature eof is ok (here!) if (! GetToken(PR_FALSE)) { // premature eof is ok (here!)
@@ -2772,13 +2757,7 @@ CSSParserImpl::ParseTypeOrUniversalSelector(PRInt32& aDataMask,
} }
if (eCSSToken_Ident == mToken.mType) { // element name if (eCSSToken_Ident == mToken.mType) { // element name
aDataMask |= SEL_MASK_ELEM; aDataMask |= SEL_MASK_ELEM;
if (mCaseSensitive) { aSelector.SetTag(mToken.mIdent, mCaseSensitive);
aSelector.SetTag(mToken.mIdent);
}
else {
ToLowerCase(mToken.mIdent, buffer);
aSelector.SetTag(buffer);
}
} }
else if (mToken.IsSymbol('*')) { // universal selector else if (mToken.IsSymbol('*')) { // universal selector
aDataMask |= SEL_MASK_ELEM; aDataMask |= SEL_MASK_ELEM;

View File

@@ -210,7 +210,7 @@ RuleHash_TagTable_GetKey(PLDHashTable *table, const PLDHashEntryHdr *hdr)
{ {
const RuleHashTableEntry *entry = const RuleHashTableEntry *entry =
static_cast<const RuleHashTableEntry*>(hdr); static_cast<const RuleHashTableEntry*>(hdr);
return entry->mRules->mSelector->mTag; return entry->mRules->mSelector->mLowercaseTag;
} }
static nsIAtom* static nsIAtom*
@@ -513,8 +513,8 @@ void RuleHash::PrependRule(RuleValue *aRuleInfo)
PrependRuleToTable(&mClassTable, selector->mClassList->mAtom, aRuleInfo); PrependRuleToTable(&mClassTable, selector->mClassList->mAtom, aRuleInfo);
RULE_HASH_STAT_INCREMENT(mClassSelectors); RULE_HASH_STAT_INCREMENT(mClassSelectors);
} }
else if (nsnull != selector->mTag) { else if (nsnull != selector->mLowercaseTag) {
PrependRuleToTable(&mTagTable, selector->mTag, aRuleInfo); PrependRuleToTable(&mTagTable, selector->mLowercaseTag, aRuleInfo);
RULE_HASH_STAT_INCREMENT(mTagSelectors); RULE_HASH_STAT_INCREMENT(mTagSelectors);
} }
else if (kNameSpaceID_Unknown != selector->mNameSpace) { else if (kNameSpaceID_Unknown != selector->mNameSpace) {
@@ -1191,11 +1191,23 @@ static PRBool SelectorMatches(RuleProcessorData &data,
{ {
// namespace/tag match // namespace/tag match
// optimization : bail out early if we can
if ((kNameSpaceID_Unknown != aSelector->mNameSpace && if ((kNameSpaceID_Unknown != aSelector->mNameSpace &&
data.mNameSpaceID != aSelector->mNameSpace) || data.mNameSpaceID != aSelector->mNameSpace))
(aSelector->mTag && aSelector->mTag != data.mContentTag)) {
// optimization : bail out early if we can
return PR_FALSE; return PR_FALSE;
if (aSelector->mLowercaseTag) {
//If we tested that this is an HTML node in a text/html document and
//had some tweaks in RuleHash, we could remove case-sensitivity from
//style sheets.
if (data.mIsHTMLContent) {
if (data.mContentTag != aSelector->mLowercaseTag)
return PR_FALSE;
}
else {
if (data.mContentTag != aSelector->mCasedTag)
return PR_FALSE;
}
} }
PRBool result = PR_TRUE; PRBool result = PR_TRUE;
@@ -1593,7 +1605,8 @@ static PRBool SelectorMatches(RuleProcessorData &data,
if ((stateToCheck & (NS_EVENT_STATE_HOVER | NS_EVENT_STATE_ACTIVE)) && if ((stateToCheck & (NS_EVENT_STATE_HOVER | NS_EVENT_STATE_ACTIVE)) &&
data.mCompatMode == eCompatibility_NavQuirks && data.mCompatMode == eCompatibility_NavQuirks &&
// global selector (but don't check .class): // global selector (but don't check .class):
!aSelector->mTag && !aSelector->mIDList && !aSelector->mAttrList && !aSelector->HasTagSelector() && !aSelector->mIDList &&
!aSelector->mAttrList &&
// This (or the other way around) both make :not() asymmetric // This (or the other way around) both make :not() asymmetric
// in quirks mode (and it's hard to work around since we're // in quirks mode (and it's hard to work around since we're
// testing the current mNegations, not the first // testing the current mNegations, not the first
@@ -1929,7 +1942,10 @@ static void PseudoEnumFunc(nsICSSStyleRule* aRule, nsCSSSelector* aSelector,
{ {
PseudoRuleProcessorData* data = (PseudoRuleProcessorData*)aData; PseudoRuleProcessorData* data = (PseudoRuleProcessorData*)aData;
NS_ASSERTION(aSelector->mTag == data->mPseudoTag, "RuleHash failure"); if (!aSelector->IsPseudoElement())
return;
NS_ASSERTION(aSelector->mLowercaseTag == data->mPseudoTag, "RuleHash failure");
PRBool matches = PR_TRUE; PRBool matches = PR_TRUE;
if (data->mComparator) if (data->mComparator)
data->mComparator->PseudoMatches(data->mPseudoTag, aSelector, &matches); data->mComparator->PseudoMatches(data->mPseudoTag, aSelector, &matches);

View File

@@ -258,7 +258,8 @@ nsAttrSelector::~nsAttrSelector(void)
// -- nsCSSSelector ------------------------------- // -- nsCSSSelector -------------------------------
nsCSSSelector::nsCSSSelector(void) nsCSSSelector::nsCSSSelector(void)
: mTag(nsnull), : mLowercaseTag(nsnull),
mCasedTag(nsnull),
mIDList(nsnull), mIDList(nsnull),
mClassList(nsnull), mClassList(nsnull),
mPseudoClassList(nsnull), mPseudoClassList(nsnull),
@@ -279,7 +280,8 @@ nsCSSSelector::Clone(PRBool aDeepNext, PRBool aDeepNegations) const
return nsnull; return nsnull;
result->mNameSpace = mNameSpace; result->mNameSpace = mNameSpace;
result->mTag = mTag; result->mLowercaseTag = mLowercaseTag;
result->mCasedTag = mCasedTag;
result->mOperator = mOperator; result->mOperator = mOperator;
NS_IF_CLONE(mIDList); NS_IF_CLONE(mIDList);
@@ -316,7 +318,8 @@ nsCSSSelector::~nsCSSSelector(void)
void nsCSSSelector::Reset(void) void nsCSSSelector::Reset(void)
{ {
mNameSpace = kNameSpaceID_Unknown; mNameSpace = kNameSpaceID_Unknown;
mTag = nsnull; mLowercaseTag = nsnull;
mCasedTag = nsnull;
NS_IF_DELETE(mIDList); NS_IF_DELETE(mIDList);
NS_IF_DELETE(mClassList); NS_IF_DELETE(mClassList);
NS_IF_DELETE(mPseudoClassList); NS_IF_DELETE(mPseudoClassList);
@@ -334,12 +337,23 @@ void nsCSSSelector::SetNameSpace(PRInt32 aNameSpace)
mNameSpace = aNameSpace; mNameSpace = aNameSpace;
} }
void nsCSSSelector::SetTag(const nsString& aTag) void nsCSSSelector::SetTag(const nsString& aTag, PRBool aCaseMatters)
{ {
if (aTag.IsEmpty()) if (aTag.IsEmpty()) {
mTag = nsnull; mLowercaseTag = mCasedTag = nsnull;
else return;
mTag = do_GetAtom(aTag); }
mCasedTag = do_GetAtom(aTag);
if (aCaseMatters) {
mLowercaseTag = mCasedTag;
}
else {
nsAutoString lowercase(aTag);
ToLowerCase(lowercase);
mLowercaseTag = do_GetAtom(lowercase);
}
} }
void nsCSSSelector::AddID(const nsString& aID) void nsCSSSelector::AddID(const nsString& aID)
@@ -422,7 +436,7 @@ PRInt32 nsCSSSelector::CalcWeightWithoutNegations() const
{ {
PRInt32 weight = 0; PRInt32 weight = 0;
if (nsnull != mTag) { if (nsnull != mLowercaseTag) {
weight += 0x000001; weight += 0x000001;
} }
nsAtomList* list = mIDList; nsAtomList* list = mIDList;
@@ -458,19 +472,6 @@ PRInt32 nsCSSSelector::CalcWeight() const
return weight; return weight;
} }
// pseudo-elements are stored in the selectors' chain using fictional elements;
// these fictional elements have mTag starting with a colon
static PRBool IsPseudoElement(nsIAtom* aAtom)
{
if (aAtom) {
const char* str;
aAtom->GetUTF8String(&str);
return str && (*str == ':');
}
return PR_FALSE;
}
// //
// Builds the textual representation of a selector. Called by DOM 2 CSS // Builds the textual representation of a selector. Called by DOM 2 CSS
// StyleRule:selectorText // StyleRule:selectorText
@@ -499,7 +500,7 @@ nsCSSSelector::ToString(nsAString& aString, nsICSSStyleSheet* aSheet,
// Append the combinator, if needed. // Append the combinator, if needed.
if (!stack.IsEmpty()) { if (!stack.IsEmpty()) {
const nsCSSSelector *next = stack.ElementAt(index - 1); const nsCSSSelector *next = stack.ElementAt(index - 1);
if (!IsPseudoElement(next->mTag)) { if (!next->IsPseudoElement()) {
aString.Append(PRUnichar(' ')); aString.Append(PRUnichar(' '));
PRUnichar oper = s->mOperator; PRUnichar oper = s->mOperator;
if (oper != PRUnichar(0)) { if (oper != PRUnichar(0)) {
@@ -532,7 +533,7 @@ nsCSSSelector::AppendToStringWithoutCombinatorsOrNegations
PRBool aIsNegated) const PRBool aIsNegated) const
{ {
nsAutoString temp; nsAutoString temp;
PRBool isPseudoElement = IsPseudoElement(mTag); PRBool isPseudoElement = IsPseudoElement();
// For non-pseudo-element selectors or for lone pseudo-elements, deal with // For non-pseudo-element selectors or for lone pseudo-elements, deal with
// namespace prefixes. // namespace prefixes.
@@ -588,7 +589,7 @@ nsCSSSelector::AppendToStringWithoutCombinatorsOrNegations
} }
} }
if (!mTag) { if (!mLowercaseTag) {
// Universal selector: avoid writing the universal selector when we // Universal selector: avoid writing the universal selector when we
// can avoid it, especially since we're required to avoid it for the // can avoid it, especially since we're required to avoid it for the
// inside of :not() // inside of :not()
@@ -605,12 +606,12 @@ nsCSSSelector::AppendToStringWithoutCombinatorsOrNegations
// XXXldb Why? // XXXldb Why?
aString.Append(PRUnichar('*')); aString.Append(PRUnichar('*'));
} }
if (!nsCSSPseudoElements::IsCSS2PseudoElement(mTag)) { if (!nsCSSPseudoElements::IsCSS2PseudoElement(mLowercaseTag)) {
aString.Append(PRUnichar(':')); aString.Append(PRUnichar(':'));
} }
} }
nsAutoString prefix; nsAutoString prefix;
mTag->ToString(prefix); mLowercaseTag->ToString(prefix);
aString.Append(prefix); aString.Append(prefix);
} }

View File

@@ -152,7 +152,7 @@ public:
void Reset(void); void Reset(void);
void SetNameSpace(PRInt32 aNameSpace); void SetNameSpace(PRInt32 aNameSpace);
void SetTag(const nsString& aTag); void SetTag(const nsString& aTag, PRBool aCaseSensitive);
void AddID(const nsString& aID); void AddID(const nsString& aID);
void AddClass(const nsString& aClass); void AddClass(const nsString& aClass);
void AddPseudoClass(nsIAtom* aPseudoClass); void AddPseudoClass(nsIAtom* aPseudoClass);
@@ -163,6 +163,14 @@ public:
const nsString& aValue, PRBool aCaseSensitive); const nsString& aValue, PRBool aCaseSensitive);
void SetOperator(PRUnichar aOperator); void SetOperator(PRUnichar aOperator);
inline PRBool HasTagSelector() const {
return !!mCasedTag;
}
inline PRBool IsPseudoElement() const {
return mLowercaseTag && !mCasedTag;
}
// Calculate the specificity of this selector (not including its mNext!). // Calculate the specificity of this selector (not including its mNext!).
PRInt32 CalcWeight() const; PRInt32 CalcWeight() const;
@@ -188,7 +196,12 @@ private:
PRInt32 CalcWeightWithoutNegations() const; PRInt32 CalcWeightWithoutNegations() const;
public: public:
nsCOMPtr<nsIAtom> mTag; // 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* mIDList;
nsAtomList* mClassList; nsAtomList* mClassList;
nsPseudoClassList* mPseudoClassList; // atom for the pseudo, string for nsPseudoClassList* mPseudoClassList; // atom for the pseudo, string for

View File

@@ -101,6 +101,8 @@ _TEST_FILES = test_acid3_test46.html \
test_bug450191.html \ test_bug450191.html \
test_bug453896_deck.html \ test_bug453896_deck.html \
test_bug470769.html \ test_bug470769.html \
test_bug499655.html \
test_bug499655.xhtml \
test_cascade.html \ test_cascade.html \
test_compute_data_with_start_struct.html \ test_compute_data_with_start_struct.html \
test_css_eof_handling.html \ test_css_eof_handling.html \

View File

@@ -0,0 +1,46 @@
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=499655
-->
<head>
<title>Test for Bug 499655</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=499655">Mozilla Bug 499655</a>
<p id="display"></p>
<div id="content">
</div>
<pre id="test">
<script class="testbody" type="text/javascript">
/** Test for Bug 499655 **/
test1 = document.createElementNS("http://www.w3.org/1999/xhtml","test");
test2 = document.createElementNS("http://www.w3.org/1999/xhtml","TEst");
test3 = document.createElementNS("test","test");
test4 = document.createElementNS("test","TEst");
content = document.getElementById("content");
content.appendChild(test1);
content.appendChild(test2);
content.appendChild(test3);
content.appendChild(test4);
list = document.querySelectorAll('test');
is(list.length, 2, "Number of elements found");
is(list[0], test1, "First element didn't match");
is(list[1], test3, "Third element didn't match");
list = document.querySelectorAll('TEst');
is(list.length, 2, "Wrong number of elements found");
is(list[0], test1, "First element didn't match");
is(list[1], test4, "Fourth element didn't match");
</script>
</pre>
</body>
</html>

View File

@@ -0,0 +1,49 @@
<html xmlns="http://www.w3.org/1999/xhtml">
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=499655
-->
<head>
<title>Test for Bug 499655</title>
<script type="application/javascript" src="/MochiKit/packed.js"></script>
<script type="application/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=499655">Mozilla Bug 499655</a>
<p id="display"></p>
<div id="content" style="display: none">
</div>
<pre id="test">
<script type="application/javascript">
<![CDATA[
test1 = document.createElementNS("http://www.w3.org/1999/xhtml","test");
test2 = document.createElementNS("http://www.w3.org/1999/xhtml","TEst");
test3 = document.createElementNS("test","test");
test4 = document.createElementNS("test","TEst");
content = document.getElementById("content");
content.appendChild(test1);
content.appendChild(test2);
content.appendChild(test3);
content.appendChild(test4);
list = document.querySelectorAll('test');
is(list.length, 2, "Number of elements found");
is(list[0], test1, "First element didn't match");
is(list[1] , test3, "Third element didn't match");
list = document.querySelectorAll('TEst');
is(list.length, 2, "Number of elements found");
is(list[0], test2, "Second element didn't match");
is(list[1], test4, "Fourth element didn't match");
]]>
</script>
</pre>
</body>
</html>

View File

@@ -4267,26 +4267,23 @@ nsTreeBodyFrame::GetPseudoStyleContext(nsIAtom* aPseudoElement)
NS_IMETHODIMP NS_IMETHODIMP
nsTreeBodyFrame::PseudoMatches(nsIAtom* aTag, nsCSSSelector* aSelector, PRBool* aResult) nsTreeBodyFrame::PseudoMatches(nsIAtom* aTag, nsCSSSelector* aSelector, PRBool* aResult)
{ {
if (aSelector->mTag == aTag) { NS_ABORT_IF_FALSE(aSelector->mLowercaseTag == aTag,
// Iterate the pseudoclass list. For each item in the list, see if "should not have been called");
// it is contained in our scratch array. If we have a miss, then // Iterate the pseudoclass list. For each item in the list, see if
// we aren't a match. If all items in the pseudoclass list are // it is contained in our scratch array. If we have a miss, then
// present in the scratch array, then we have a match. // we aren't a match. If all items in the pseudoclass list are
nsPseudoClassList* curr = aSelector->mPseudoClassList; // present in the scratch array, then we have a match.
while (curr) { nsPseudoClassList* curr = aSelector->mPseudoClassList;
PRInt32 index; while (curr) {
mScratchArray->GetIndexOf(curr->mAtom, &index); PRInt32 index;
if (index == -1) { mScratchArray->GetIndexOf(curr->mAtom, &index);
*aResult = PR_FALSE; if (index == -1) {
return NS_OK; *aResult = PR_FALSE;
} return NS_OK;
curr = curr->mNext;
} }
*aResult = PR_TRUE; curr = curr->mNext;
} }
else *aResult = PR_TRUE;
*aResult = PR_FALSE;
return NS_OK; return NS_OK;
} }