From eac6a24015cb890b4945fb27de01a4082dcdd584 Mon Sep 17 00:00:00 2001 From: "dbaron%dbaron.org" Date: Wed, 9 Nov 2005 04:17:26 +0000 Subject: [PATCH] Change storage of :not() selectors so that there is one item in mNegations per :not(), and simplify various code accordingly. Simplify SelectorMatches a little more. b=315567 r+sr=bzbarsky --- layout/style/nsCSSParser.cpp | 45 ++-- layout/style/nsCSSStyleRule.cpp | 45 +--- layout/style/nsCSSStyleSheet.cpp | 379 +++++++++++++++---------------- layout/style/nsICSSStyleRule.h | 2 +- 4 files changed, 223 insertions(+), 248 deletions(-) diff --git a/layout/style/nsCSSParser.cpp b/layout/style/nsCSSParser.cpp index efeb573d8ec3..92bee3ef28ba 100644 --- a/layout/style/nsCSSParser.cpp +++ b/layout/style/nsCSSParser.cpp @@ -2581,42 +2581,39 @@ CSSParserImpl::ParseNegatedSimpleSelector(PRInt32& aDataMask, REPORT_UNEXPECTED_EOF(PENegationEOF); return eSelectorParsingStatus_Error; } - if (!aSelector.mNegations) { - aSelector.mNegations = new nsCSSSelector(); - if (!aSelector.mNegations) { - aErrorCode = NS_ERROR_OUT_OF_MEMORY; - return eSelectorParsingStatus_Error; - } + + // Create a new nsCSSSelector and add it to the end of + // aSelector.mNegations. + // Given the current parsing rules, every selector in mNegations + // contains only one simple selector (css3 definition) within it. + // This could easily change in future versions of CSS, and the only + // thing we need to change to support that is this parsing code. + nsCSSSelector *newSel = new nsCSSSelector(); + if (!newSel) { + aErrorCode = NS_ERROR_OUT_OF_MEMORY; + return eSelectorParsingStatus_Error; } - // ID, class and attribute selectors and pseudo-classes are stored in - // the first mNegations attached to a selector + nsCSSSelector* negations = &aSelector; + while (negations->mNegations) { + negations = negations->mNegations; + } + negations->mNegations = newSel; + nsSelectorParsingStatus parsingStatus; if (eCSSToken_ID == mToken.mType) { // #id - parsingStatus = ParseIDSelector(aDataMask, *aSelector.mNegations, aErrorCode); + parsingStatus = ParseIDSelector(aDataMask, *newSel, aErrorCode); } else if (mToken.IsSymbol('.')) { // .class - parsingStatus = ParseClassSelector(aDataMask, *aSelector.mNegations, aErrorCode); + parsingStatus = ParseClassSelector(aDataMask, *newSel, aErrorCode); } else if (mToken.IsSymbol(':')) { // :pseudo - parsingStatus = ParsePseudoSelector(aDataMask, *aSelector.mNegations, aErrorCode, PR_TRUE); + parsingStatus = ParsePseudoSelector(aDataMask, *newSel, aErrorCode, PR_TRUE); } else if (mToken.IsSymbol('[')) { // [attribute - parsingStatus = ParseAttributeSelector(aDataMask, *aSelector.mNegations, aErrorCode); + parsingStatus = ParseAttributeSelector(aDataMask, *newSel, aErrorCode); } else { // then it should be a type element or universal selector - nsCSSSelector *newSel = new nsCSSSelector(); - if (!newSel) { - aErrorCode = NS_ERROR_OUT_OF_MEMORY; - return eSelectorParsingStatus_Error; - } - nsCSSSelector* negations = aSelector.mNegations; - while (nsnull != negations->mNegations) { - negations = negations->mNegations; - } - // negated type element selectors and universal selectors are stored after the first - // mNegations containing only negated IDs, classes, attributes and pseudo-classes - negations->mNegations = newSel; parsingStatus = ParseTypeOrUniversalSelector(aDataMask, *newSel, aErrorCode, PR_TRUE); } if (eSelectorParsingStatus_Error == parsingStatus) { diff --git a/layout/style/nsCSSStyleRule.cpp b/layout/style/nsCSSStyleRule.cpp index 2ed94dbf34d4..38c91b83444e 100644 --- a/layout/style/nsCSSStyleRule.cpp +++ b/layout/style/nsCSSStyleRule.cpp @@ -80,12 +80,6 @@ #define NS_IF_DELETE(ptr) \ if (ptr) { delete ptr; ptr = nsnull; } -#define NS_IF_NEGATED_START(bool,str) \ - if (bool) { str.AppendLiteral(":not("); } - -#define NS_IF_NEGATED_END(bool,str) \ - if (bool) { str.Append(PRUnichar(')')); } - MOZ_DECL_CTOR_COUNTER(nsAtomList) nsAtomList::nsAtomList(nsIAtom* aAtom) @@ -542,16 +536,15 @@ nsCSSSelector::ToString(nsAString& aString, nsICSSStyleSheet* aSheet, if (!aAppend) aString.Truncate(); - ToStringInternal(aString, aSheet, IsPseudoElement(mTag), 0); + ToStringInternal(aString, aSheet, IsPseudoElement(mTag), PR_FALSE); } void nsCSSSelector::ToStringInternal(nsAString& aString, nsICSSStyleSheet* aSheet, PRBool aIsPseudoElem, - PRIntn aNegatedIndex) const + PRBool aIsNegated) const { nsAutoString temp; - PRBool aIsNegated = PRBool(0 < aNegatedIndex); PRBool isPseudoElement = IsPseudoElement(mTag); // selectors are linked from right-to-left, so the next selector in the linked list @@ -564,11 +557,6 @@ void nsCSSSelector::ToStringInternal(nsAString& aString, aString.Append(PRUnichar(' ')); } } - if (1 < aNegatedIndex) { - // the first mNegations does not contain a negated type element selector - // or a negated universal selector - NS_IF_NEGATED_START(aIsNegated, aString) - } // For non-pseudo-element selectors or for lone pseudo-elements, deal with // namespace prefixes. @@ -611,13 +599,9 @@ void nsCSSSelector::ToStringInternal(nsAString& aString, } // smells like a universal selector - if (!mTag && !mIDList && !mClassList) { - if (1 != aNegatedIndex) { - aString.Append(PRUnichar('*')); - } - if (1 < aNegatedIndex) { - NS_IF_NEGATED_END(aIsNegated, aString) - } + if (!mTag && !mIDList && !mClassList && !mPseudoClassList && !mAttrList) { + // XXXldb Or if we've written a namespace. + aString.Append(PRUnichar('*')); } else { // Append the tag name, if there is one if (mTag) { @@ -633,17 +617,14 @@ void nsCSSSelector::ToStringInternal(nsAString& aString, nsAutoString prefix; mTag->ToString(prefix); aString.Append(prefix); - NS_IF_NEGATED_END(aIsNegated, aString) } // Append the id, if there is one if (mIDList) { nsAtomList* list = mIDList; while (list != nsnull) { list->mAtom->ToString(temp); - NS_IF_NEGATED_START(aIsNegated, aString) aString.Append(PRUnichar('#')); aString.Append(temp); - NS_IF_NEGATED_END(aIsNegated, aString) list = list->mNext; } } @@ -652,10 +633,8 @@ void nsCSSSelector::ToStringInternal(nsAString& aString, nsAtomList* list = mClassList; while (list != nsnull) { list->mAtom->ToString(temp); - NS_IF_NEGATED_START(aIsNegated, aString) aString.Append(PRUnichar('.')); aString.Append(temp); - NS_IF_NEGATED_END(aIsNegated, aString) list = list->mNext; } } @@ -665,7 +644,6 @@ void nsCSSSelector::ToStringInternal(nsAString& aString, if (mAttrList) { nsAttrSelector* list = mAttrList; while (list != nsnull) { - NS_IF_NEGATED_START(aIsNegated, aString) aString.Append(PRUnichar('[')); // Append the namespace prefix if (list->mNameSpace > 0) { @@ -709,7 +687,6 @@ void nsCSSSelector::ToStringInternal(nsAString& aString, aString.Append(PRUnichar(']')); - NS_IF_NEGATED_END(aIsNegated, aString) list = list->mNext; } } @@ -719,21 +696,23 @@ void nsCSSSelector::ToStringInternal(nsAString& aString, nsAtomStringList* list = mPseudoClassList; while (list != nsnull) { list->mAtom->ToString(temp); - NS_IF_NEGATED_START(aIsNegated, aString) aString.Append(temp); if (nsnull != list->mString) { aString.Append(PRUnichar('(')); aString.Append(list->mString); aString.Append(PRUnichar(')')); } - NS_IF_NEGATED_END(aIsNegated, aString) list = list->mNext; } } - if (mNegations) { - // chain all the negated selectors - mNegations->ToStringInternal(aString, aSheet, PR_FALSE, aNegatedIndex + 1); + if (!aIsNegated) { + for (nsCSSSelector* negation = mNegations; negation; + negation = negation->mNegations) { + aString.AppendLiteral(":not("); + negation->ToStringInternal(aString, aSheet, PR_FALSE, PR_TRUE); + aString.Append(PRUnichar(')')); + } } // Append the operator only if the selector is not negated and is not diff --git a/layout/style/nsCSSStyleSheet.cpp b/layout/style/nsCSSStyleSheet.cpp index b952581fe825..35973110f4dd 100644 --- a/layout/style/nsCSSStyleSheet.cpp +++ b/layout/style/nsCSSStyleSheet.cpp @@ -2613,17 +2613,14 @@ RuleProcessorData::RuleProcessorData(nsPresContext* aPresContext, mClasses = nsnull; // get the compat. mode (unless it is provided) - if(!aCompat) { + if (!aCompat) { mCompatMode = mPresContext->CompatibilityMode(); } else { mCompatMode = *aCompat; } - if(aContent){ - // we hold no ref to the content... - mContent = aContent; - + if (aContent) { // get the tag and parent mContentTag = aContent->Tag(); mParentContent = aContent->GetParent(); @@ -2742,16 +2739,6 @@ static PRBool ValueIncludes(const nsSubstring& aValueList, return PR_FALSE; } -inline PRBool IsEventPseudo(nsIAtom* aAtom) -{ - return nsCSSPseudoClasses::active == aAtom || - nsCSSPseudoClasses::mozDragOver == aAtom || - nsCSSPseudoClasses::focus == aAtom || - nsCSSPseudoClasses::hover == aAtom || - nsCSSPseudoClasses::target == aAtom; - // XXX selected, enabled, disabled, selection? -} - inline PRBool IsLinkPseudo(nsIAtom* aAtom) { return PRBool ((nsCSSPseudoClasses::link == aAtom) || @@ -2832,54 +2819,43 @@ static PRBool AttrMatchesValue(const nsAttrSelector* aAttrSelector, } } -#define STATE_CHECK(_state) \ - ((aStateMask & (_state)) || \ - (localTrue == (0 != (data.mEventState & (_state))))) - // NOTE: The |aStateMask| code isn't going to work correctly anymore if // we start batching style changes, because if multiple states change in // separate notifications then we might determine the style is not // state-dependent when it really is (e.g., determining that a // :hover:active rule no longer matches when both states are unset). +// XXXldb This is a real problem for things like [checked]:checked where +// both states are determined exactly by an attribute. + +// |aDependence| has two functions: +// * when non-null, it indicates that we're processing a negation, +// which is done only when SelectorMatches calls itself recursively +// * what it points to should be set to true whenever a test is skipped +// because of aStateMask or aAttribute static PRBool SelectorMatches(RuleProcessorData &data, nsCSSSelector* aSelector, PRInt32 aStateMask, // states NOT to test nsIAtom* aAttribute, // attribute NOT to test - PRInt8 aNegationIndex) + PRBool* const aDependence = nsnull) { - // if we are dealing with negations, reverse the values of PR_TRUE and PR_FALSE - PRBool localFalse = 0 < aNegationIndex; - PRBool localTrue = 0 == aNegationIndex; - PRBool result = localTrue; - - // Do not perform the test if aNegationIndex==1 - // because it then contains only negated IDs, classes, attributes and pseudo- - // classes - if (1 != aNegationIndex) { - if (kNameSpaceID_Unknown != aSelector->mNameSpace) { - if (data.mNameSpaceID != aSelector->mNameSpace) { - result = localFalse; - } - } - if (localTrue == result) { - if ((nsnull != aSelector->mTag) && (aSelector->mTag != data.mContentTag)) { - result = localFalse; - } - } + // namespace/tag match + if ((kNameSpaceID_Unknown != aSelector->mNameSpace && + data.mNameSpaceID != aSelector->mNameSpace) || + (aSelector->mTag && aSelector->mTag != data.mContentTag)) { // optimization : bail out early if we can - if (!result) { - return PR_FALSE; - } + return PR_FALSE; } - result = PR_TRUE; + PRBool result = PR_TRUE; + const PRBool isNegated = (aDependence != nsnull); // test for pseudo class match // first-child, root, lang, active, focus, hover, link, visited... // XXX disabled, enabled, selected, selection for (nsAtomStringList* pseudoClass = aSelector->mPseudoClassList; pseudoClass && result; pseudoClass = pseudoClass->mNext) { + PRInt32 stateToCheck = 0; if ((nsCSSPseudoClasses::firstChild == pseudoClass->mAtom) || (nsCSSPseudoClasses::firstNode == pseudoClass->mAtom) ) { nsIContent *firstChild = nsnull; @@ -2895,7 +2871,7 @@ static PRBool SelectorMatches(RuleProcessorData &data, } while (firstChild && !IsSignificantChild(firstChild, acceptNonWhitespace, PR_FALSE)); } - result = localTrue == (data.mContent == firstChild); + result = (data.mContent == firstChild); } else if ((nsCSSPseudoClasses::lastChild == pseudoClass->mAtom) || (nsCSSPseudoClasses::lastNode == pseudoClass->mAtom)) { @@ -2912,7 +2888,7 @@ static PRBool SelectorMatches(RuleProcessorData &data, } while (lastChild && !IsSignificantChild(lastChild, acceptNonWhitespace, PR_FALSE)); } - result = localTrue == (data.mContent == lastChild); + result = (data.mContent == lastChild); } else if (nsCSSPseudoClasses::onlyChild == pseudoClass->mAtom) { nsIContent *onlyChild = nsnull; @@ -2932,14 +2908,13 @@ static PRBool SelectorMatches(RuleProcessorData &data, } while (moreChild && !IsSignificantChild(moreChild, PR_FALSE, PR_FALSE)); } } - result = localTrue == (data.mContent == onlyChild && - moreChild == nsnull); + result = (data.mContent == onlyChild && moreChild == nsnull); } else if (nsCSSPseudoClasses::empty == pseudoClass->mAtom || nsCSSPseudoClasses::mozOnlyWhitespace == pseudoClass->mAtom) { nsIContent *child = nsnull; nsIContent *element = data.mContent; - PRBool isWhitespaceSignificant = + const PRBool isWhitespaceSignificant = nsCSSPseudoClasses::empty == pseudoClass->mAtom; PRInt32 index = -1; @@ -2948,7 +2923,7 @@ static PRBool SelectorMatches(RuleProcessorData &data, // stop at first non-comment (and non-whitespace for // :-moz-only-whitespace) node } while (child && !IsSignificantChild(child, PR_TRUE, isWhitespaceSignificant)); - result = localTrue == (child == nsnull); + result = (child == nsnull); } else if (nsCSSPseudoClasses::mozEmptyExceptChildrenWithLocalname == pseudoClass->mAtom) { NS_ASSERTION(pseudoClass->mString, "Must have string!"); @@ -2962,26 +2937,20 @@ static PRBool SelectorMatches(RuleProcessorData &data, (!IsSignificantChild(child, PR_TRUE, PR_FALSE) || (child->GetNameSpaceID() == element->GetNameSpaceID() && child->Tag()->Equals(nsDependentString(pseudoClass->mString))))); - result = localTrue == (child == nsnull); + result = (child == nsnull); } else if (nsCSSPseudoClasses::root == pseudoClass->mAtom) { - if (data.mParentContent) { - result = localFalse; - } - else { - result = localTrue; - } + result = (data.mParentContent == nsnull); } else if (nsCSSPseudoClasses::mozBoundElement == pseudoClass->mAtom) { // XXXldb How do we know where the selector came from? And what // if there are multiple bindings, and we should be matching the // outer one? - result = (data.mScopedRoot && data.mScopedRoot == data.mContent) - ? localTrue : localFalse; + result = (data.mScopedRoot && data.mScopedRoot == data.mContent); } else if (nsCSSPseudoClasses::lang == pseudoClass->mAtom) { NS_ASSERTION(nsnull != pseudoClass->mString, "null lang parameter"); - result = localFalse; + result = PR_FALSE; if (pseudoClass->mString && *pseudoClass->mString) { // We have to determine the language of the current element. Since // this is currently no property and since the language is inherited @@ -2989,7 +2958,7 @@ static PRBool SelectorMatches(RuleProcessorData &data, // nodes. The language itself is encoded in the LANG attribute. const nsString* lang = data.GetLang(); if (lang && !lang->IsEmpty()) { // null check for out-of-memory - result = localTrue == nsStyleUtil::DashMatchCompare(*lang, + result = nsStyleUtil::DashMatchCompare(*lang, nsDependentString(pseudoClass->mString), nsCaseInsensitiveStringComparator()); } @@ -3015,7 +2984,7 @@ static PRBool SelectorMatches(RuleProcessorData &data, if (nsStyleUtil::DashMatchCompare(Substring(language, begin, end-begin), langString, nsCaseInsensitiveStringComparator())) { - result = localTrue; + result = PR_TRUE; break; } begin = end + 1; @@ -3023,60 +2992,46 @@ static PRBool SelectorMatches(RuleProcessorData &data, } } } + } else if (nsCSSPseudoClasses::active == pseudoClass->mAtom) { + stateToCheck = NS_EVENT_STATE_ACTIVE; } - else if (IsEventPseudo(pseudoClass->mAtom)) { - // check if the element is event-sensitive - if (data.mCompatMode == eCompatibility_NavQuirks && - // global selector (but don't check .class): - !aSelector->mTag && !aSelector->mIDList && !aSelector->mAttrList && - // This (or the other way around) both make :not() asymmetric - // in quirks mode (and it's hard to work around since we're - // testing the current mNegations, not the first - // (unnegated)). This at least makes it closer to the spec. - aNegationIndex == 0 && - // :hover or :active - (nsCSSPseudoClasses::active == pseudoClass->mAtom || - nsCSSPseudoClasses::hover == pseudoClass->mAtom) && - // important for |IsQuirkEventSensitive|: - data.mIsHTMLContent && !data.mIsHTMLLink && - !IsQuirkEventSensitive(data.mContentTag)) { - // In quirks mode, only make certain elements sensitive to - // selectors ":hover" and ":active". - result = localFalse; - } else { - if (nsCSSPseudoClasses::active == pseudoClass->mAtom) { - result = STATE_CHECK(NS_EVENT_STATE_ACTIVE); - } - else if (nsCSSPseudoClasses::focus == pseudoClass->mAtom) { - result = STATE_CHECK(NS_EVENT_STATE_FOCUS); - } - else if (nsCSSPseudoClasses::hover == pseudoClass->mAtom) { - result = STATE_CHECK(NS_EVENT_STATE_HOVER); - } - else if (nsCSSPseudoClasses::mozDragOver == pseudoClass->mAtom) { - result = STATE_CHECK(NS_EVENT_STATE_DRAGOVER); - } - else if (nsCSSPseudoClasses::target == pseudoClass->mAtom) { - result = STATE_CHECK(NS_EVENT_STATE_URLTARGET); - } - } + else if (nsCSSPseudoClasses::focus == pseudoClass->mAtom) { + stateToCheck = NS_EVENT_STATE_FOCUS; + } + else if (nsCSSPseudoClasses::hover == pseudoClass->mAtom) { + stateToCheck = NS_EVENT_STATE_HOVER; + } + else if (nsCSSPseudoClasses::mozDragOver == pseudoClass->mAtom) { + stateToCheck = NS_EVENT_STATE_DRAGOVER; + } + else if (nsCSSPseudoClasses::target == pseudoClass->mAtom) { + stateToCheck = NS_EVENT_STATE_URLTARGET; } else if (IsLinkPseudo(pseudoClass->mAtom)) { if (data.mIsHTMLLink || data.mIsSimpleXLink) { if (nsCSSPseudoClasses::mozAnyLink == pseudoClass->mAtom) { - result = localTrue; + result = PR_TRUE; } - else if (nsCSSPseudoClasses::link == pseudoClass->mAtom) { - result = (aStateMask & NS_EVENT_STATE_VISITED) || - localTrue == (eLinkState_Unvisited == data.mLinkState); - } - else if (nsCSSPseudoClasses::visited == pseudoClass->mAtom) { - result = (aStateMask & NS_EVENT_STATE_VISITED) || - localTrue == (eLinkState_Visited == data.mLinkState); + else { + NS_ASSERTION(nsCSSPseudoClasses::link == pseudoClass->mAtom || + nsCSSPseudoClasses::visited == pseudoClass->mAtom, + "somebody changed IsLinkPseudo"); + NS_ASSERTION(data.mLinkState == eLinkState_Unvisited || + data.mLinkState == eLinkState_Visited, + "unexpected link state for " + "mIsHTMLLink || mIsSimpleXLink"); + if (aStateMask & NS_EVENT_STATE_VISITED) { + result = PR_TRUE; + if (aDependence) + *aDependence = PR_TRUE; + } else { + result = ((eLinkState_Unvisited == data.mLinkState) == + (nsCSSPseudoClasses::link == pseudoClass->mAtom)); + } } } else { - result = localFalse; // not a link + result = PR_FALSE; // not a link } } else if (nsCSSPseudoClasses::checked == pseudoClass->mAtom) { @@ -3084,74 +3039,101 @@ static PRBool SelectorMatches(RuleProcessorData &data, //