From 96844bf90f3a5ddfb233941bc0b777e45934aaa2 Mon Sep 17 00:00:00 2001 From: "dbaron%dbaron.org" Date: Mon, 3 Apr 2006 23:52:17 +0000 Subject: [PATCH] Split the nsCSSRuleProcessor half of nsCSSStyleSheet.cpp into nsCSSRuleProcessor.cpp. b=332136 r+sr=bzbarsky git-svn-id: svn://10.0.0.236/trunk@193459 18797224-902f-48f8-a5cc-f745e15eee43 --- mozilla/layout/style/Makefile.in | 1 + mozilla/layout/style/nsCSSRuleProcessor.cpp | 2056 ++++++++++++++++++ mozilla/layout/style/nsCSSRuleProcessor.h | 2 +- mozilla/layout/style/nsCSSStyleSheet.cpp | 2029 +---------------- mozilla/layout/style/nsIStyleRuleProcessor.h | 2 +- 5 files changed, 2061 insertions(+), 2029 deletions(-) create mode 100644 mozilla/layout/style/nsCSSRuleProcessor.cpp diff --git a/mozilla/layout/style/Makefile.in b/mozilla/layout/style/Makefile.in index 6893ba00a95..6ad2e7a40bd 100644 --- a/mozilla/layout/style/Makefile.in +++ b/mozilla/layout/style/Makefile.in @@ -130,6 +130,7 @@ CPPSRCS = \ nsCSSPseudoClasses.cpp \ nsCSSPseudoElements.cpp \ nsCSSRule.cpp \ + nsCSSRuleProcessor.cpp \ nsCSSRules.cpp \ nsCSSScanner.cpp \ nsCSSStruct.cpp \ diff --git a/mozilla/layout/style/nsCSSRuleProcessor.cpp b/mozilla/layout/style/nsCSSRuleProcessor.cpp new file mode 100644 index 00000000000..55134df3e84 --- /dev/null +++ b/mozilla/layout/style/nsCSSRuleProcessor.cpp @@ -0,0 +1,2056 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +// vim:cindent:tabstop=2:expandtab:shiftwidth=2: +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is mozilla.org code. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * L. David Baron + * Daniel Glazman + * + * 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"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/* + * style rule processor for CSS style sheets, responsible for selector + * matching and cascading + */ + +#include "nsCSSRuleProcessor.h" + +#define PL_ARENA_CONST_ALIGN_MASK 7 +#define NS_RULEHASH_ARENA_BLOCK_SIZE (256) +#include "plarena.h" + +#include "nsCRT.h" +#include "nsIAtom.h" +#include "pldhash.h" +#include "nsHashtable.h" +#include "nsICSSPseudoComparator.h" +#include "nsCSSRuleProcessor.h" +#include "nsICSSStyleRule.h" +#include "nsICSSGroupRule.h" +#include "nsIDocument.h" +#include "nsPresContext.h" +#include "nsIEventStateManager.h" +#include "nsHTMLAtoms.h" +#include "nsString.h" +#include "nsUnicharUtils.h" +#include "nsVoidArray.h" +#include "nsDOMError.h" +#include "nsRuleWalker.h" +#include "nsCSSPseudoClasses.h" +#include "nsITextContent.h" +#include "nsCOMPtr.h" +#include "nsHashKeys.h" +#include "nsStyleUtil.h" +#include "nsQuickSort.h" +#include "nsAttrValue.h" +#include "nsAttrName.h" + +struct RuleValue { + /** + * |RuleValue|s are constructed before they become part of the + * |RuleHash|, to act as rule/selector pairs. |Add| is called when + * they are added to the |RuleHash|, and can be considered the second + * half of the constructor. + * + * |RuleValue|s are added to the rule hash from highest weight/order + * to lowest (since this is the fast way to build a singly linked + * list), so the index used to remember the order is backwards. + */ + RuleValue(nsICSSStyleRule* aRule, nsCSSSelector* aSelector) + : mRule(aRule), mSelector(aSelector) {} + + RuleValue* Add(PRInt32 aBackwardIndex, RuleValue *aNext) + { + mBackwardIndex = aBackwardIndex; + mNext = aNext; + return this; + } + + // CAUTION: ~RuleValue will never get called as RuleValues are arena + // allocated and arena cleanup will take care of deleting memory. + // Add code to RuleHash::~RuleHash to get it to call the destructor + // if any more cleanup needs to happen. + ~RuleValue() + { + // Rule values are arena allocated. No need for any deletion. + } + + // Placement new to arena allocate the RuleValues + void *operator new(size_t aSize, PLArenaPool &aArena) CPP_THROW_NEW { + void *mem; + PL_ARENA_ALLOCATE(mem, &aArena, aSize); + return mem; + } + + nsICSSStyleRule* mRule; + nsCSSSelector* mSelector; // which of |mRule|'s selectors + PRInt32 mBackwardIndex; // High index means low weight/order. + RuleValue* mNext; +}; + +// ------------------------------ +// Rule hash table +// + +// Uses any of the sets of ops below. +struct RuleHashTableEntry : public PLDHashEntryHdr { + RuleValue *mRules; // linked list of |RuleValue|, null-terminated +}; + +PR_STATIC_CALLBACK(PLDHashNumber) +RuleHash_CIHashKey(PLDHashTable *table, const void *key) +{ + nsIAtom *atom = NS_CONST_CAST(nsIAtom*, NS_STATIC_CAST(const nsIAtom*, key)); + + nsAutoString str; + atom->ToString(str); + ToUpperCase(str); + return HashString(str); +} + +PR_STATIC_CALLBACK(PRBool) +RuleHash_CIMatchEntry(PLDHashTable *table, const PLDHashEntryHdr *hdr, + const void *key) +{ + nsIAtom *match_atom = NS_CONST_CAST(nsIAtom*, NS_STATIC_CAST(const nsIAtom*, + key)); + // Use the |getKey| callback to avoid code duplication. + // XXX Ugh! Why does |getKey| have different |const|-ness? + nsIAtom *entry_atom = NS_CONST_CAST(nsIAtom*, NS_STATIC_CAST(const nsIAtom*, + table->ops->getKey(table, NS_CONST_CAST(PLDHashEntryHdr*, hdr)))); + + // Check for case-sensitive match first. + if (match_atom == entry_atom) + return PR_TRUE; + + const char *match_str, *entry_str; + match_atom->GetUTF8String(&match_str); + entry_atom->GetUTF8String(&entry_str); + + return (nsCRT::strcasecmp(entry_str, match_str) == 0); +} + +PR_STATIC_CALLBACK(PRBool) +RuleHash_CSMatchEntry(PLDHashTable *table, const PLDHashEntryHdr *hdr, + const void *key) +{ + nsIAtom *match_atom = NS_CONST_CAST(nsIAtom*, NS_STATIC_CAST(const nsIAtom*, + key)); + // Use the |getKey| callback to avoid code duplication. + // XXX Ugh! Why does |getKey| have different |const|-ness? + nsIAtom *entry_atom = NS_CONST_CAST(nsIAtom*, NS_STATIC_CAST(const nsIAtom*, + table->ops->getKey(table, NS_CONST_CAST(PLDHashEntryHdr*, hdr)))); + + return match_atom == entry_atom; +} + +PR_STATIC_CALLBACK(const void*) +RuleHash_TagTable_GetKey(PLDHashTable *table, PLDHashEntryHdr *hdr) +{ + RuleHashTableEntry *entry = NS_STATIC_CAST(RuleHashTableEntry*, hdr); + return entry->mRules->mSelector->mTag; +} + +PR_STATIC_CALLBACK(const void*) +RuleHash_ClassTable_GetKey(PLDHashTable *table, PLDHashEntryHdr *hdr) +{ + RuleHashTableEntry *entry = NS_STATIC_CAST(RuleHashTableEntry*, hdr); + return entry->mRules->mSelector->mClassList->mAtom; +} + +PR_STATIC_CALLBACK(const void*) +RuleHash_IdTable_GetKey(PLDHashTable *table, PLDHashEntryHdr *hdr) +{ + RuleHashTableEntry *entry = NS_STATIC_CAST(RuleHashTableEntry*, hdr); + return entry->mRules->mSelector->mIDList->mAtom; +} + +PR_STATIC_CALLBACK(const void*) +RuleHash_NameSpaceTable_GetKey(PLDHashTable *table, PLDHashEntryHdr *hdr) +{ + RuleHashTableEntry *entry = NS_STATIC_CAST(RuleHashTableEntry*, hdr); + return NS_INT32_TO_PTR(entry->mRules->mSelector->mNameSpace); +} + +PR_STATIC_CALLBACK(PLDHashNumber) +RuleHash_NameSpaceTable_HashKey(PLDHashTable *table, const void *key) +{ + return NS_PTR_TO_INT32(key); +} + +PR_STATIC_CALLBACK(PRBool) +RuleHash_NameSpaceTable_MatchEntry(PLDHashTable *table, + const PLDHashEntryHdr *hdr, + const void *key) +{ + const RuleHashTableEntry *entry = + NS_STATIC_CAST(const RuleHashTableEntry*, hdr); + + return NS_PTR_TO_INT32(key) == + entry->mRules->mSelector->mNameSpace; +} + +static PLDHashTableOps RuleHash_TagTable_Ops = { + PL_DHashAllocTable, + PL_DHashFreeTable, + RuleHash_TagTable_GetKey, + PL_DHashVoidPtrKeyStub, + RuleHash_CSMatchEntry, + PL_DHashMoveEntryStub, + PL_DHashClearEntryStub, + PL_DHashFinalizeStub, + NULL +}; + +// Case-sensitive ops. +static PLDHashTableOps RuleHash_ClassTable_CSOps = { + PL_DHashAllocTable, + PL_DHashFreeTable, + RuleHash_ClassTable_GetKey, + PL_DHashVoidPtrKeyStub, + RuleHash_CSMatchEntry, + PL_DHashMoveEntryStub, + PL_DHashClearEntryStub, + PL_DHashFinalizeStub, + NULL +}; + +// Case-insensitive ops. +static PLDHashTableOps RuleHash_ClassTable_CIOps = { + PL_DHashAllocTable, + PL_DHashFreeTable, + RuleHash_ClassTable_GetKey, + RuleHash_CIHashKey, + RuleHash_CIMatchEntry, + PL_DHashMoveEntryStub, + PL_DHashClearEntryStub, + PL_DHashFinalizeStub, + NULL +}; + +// Case-sensitive ops. +static PLDHashTableOps RuleHash_IdTable_CSOps = { + PL_DHashAllocTable, + PL_DHashFreeTable, + RuleHash_IdTable_GetKey, + PL_DHashVoidPtrKeyStub, + RuleHash_CSMatchEntry, + PL_DHashMoveEntryStub, + PL_DHashClearEntryStub, + PL_DHashFinalizeStub, + NULL +}; + +// Case-insensitive ops. +static PLDHashTableOps RuleHash_IdTable_CIOps = { + PL_DHashAllocTable, + PL_DHashFreeTable, + RuleHash_IdTable_GetKey, + RuleHash_CIHashKey, + RuleHash_CIMatchEntry, + PL_DHashMoveEntryStub, + PL_DHashClearEntryStub, + PL_DHashFinalizeStub, + NULL +}; + +static PLDHashTableOps RuleHash_NameSpaceTable_Ops = { + PL_DHashAllocTable, + PL_DHashFreeTable, + RuleHash_NameSpaceTable_GetKey, + RuleHash_NameSpaceTable_HashKey, + RuleHash_NameSpaceTable_MatchEntry, + PL_DHashMoveEntryStub, + PL_DHashClearEntryStub, + PL_DHashFinalizeStub, + NULL +}; + +#undef RULE_HASH_STATS +#undef PRINT_UNIVERSAL_RULES + +#ifdef DEBUG_dbaron +#define RULE_HASH_STATS +#define PRINT_UNIVERSAL_RULES +#endif + +#ifdef RULE_HASH_STATS +#define RULE_HASH_STAT_INCREMENT(var_) PR_BEGIN_MACRO ++(var_); PR_END_MACRO +#else +#define RULE_HASH_STAT_INCREMENT(var_) PR_BEGIN_MACRO PR_END_MACRO +#endif + +// Enumerator callback function. +typedef void (*RuleEnumFunc)(nsICSSStyleRule* aRule, + nsCSSSelector* aSelector, + void *aData); + +class RuleHash { +public: + RuleHash(PRBool aQuirksMode); + ~RuleHash(); + void PrependRule(RuleValue *aRuleInfo); + void EnumerateAllRules(PRInt32 aNameSpace, nsIAtom* aTag, nsIAtom* aID, + const nsAttrValue* aClassList, + RuleEnumFunc aFunc, void* aData); + void EnumerateTagRules(nsIAtom* aTag, + RuleEnumFunc aFunc, void* aData); + PLArenaPool& Arena() { return mArena; } + +protected: + void PrependRuleToTable(PLDHashTable* aTable, const void* aKey, + RuleValue* aRuleInfo); + void PrependUniversalRule(RuleValue* aRuleInfo); + + // All rule values in these hashtables are arena allocated + PRInt32 mRuleCount; + PLDHashTable mIdTable; + PLDHashTable mClassTable; + PLDHashTable mTagTable; + PLDHashTable mNameSpaceTable; + RuleValue *mUniversalRules; + + RuleValue** mEnumList; + PRInt32 mEnumListSize; + + PLArenaPool mArena; + +#ifdef RULE_HASH_STATS + PRUint32 mUniversalSelectors; + PRUint32 mNameSpaceSelectors; + PRUint32 mTagSelectors; + PRUint32 mClassSelectors; + PRUint32 mIdSelectors; + + PRUint32 mElementsMatched; + PRUint32 mPseudosMatched; + + PRUint32 mElementUniversalCalls; + PRUint32 mElementNameSpaceCalls; + PRUint32 mElementTagCalls; + PRUint32 mElementClassCalls; + PRUint32 mElementIdCalls; + + PRUint32 mPseudoTagCalls; +#endif // RULE_HASH_STATS +}; + +RuleHash::RuleHash(PRBool aQuirksMode) + : mRuleCount(0), + mUniversalRules(nsnull), + mEnumList(nsnull), mEnumListSize(0) +#ifdef RULE_HASH_STATS + , + mUniversalSelectors(0), + mNameSpaceSelectors(0), + mTagSelectors(0), + mClassSelectors(0), + mIdSelectors(0), + mElementsMatched(0), + mPseudosMatched(0), + mElementUniversalCalls(0), + mElementNameSpaceCalls(0), + mElementTagCalls(0), + mElementClassCalls(0), + mElementIdCalls(0), + mPseudoTagCalls(0) +#endif +{ + // Initialize our arena + PL_INIT_ARENA_POOL(&mArena, "RuleHashArena", NS_RULEHASH_ARENA_BLOCK_SIZE); + + PL_DHashTableInit(&mTagTable, &RuleHash_TagTable_Ops, nsnull, + sizeof(RuleHashTableEntry), 64); + PL_DHashTableInit(&mIdTable, + aQuirksMode ? &RuleHash_IdTable_CIOps + : &RuleHash_IdTable_CSOps, + nsnull, sizeof(RuleHashTableEntry), 16); + PL_DHashTableInit(&mClassTable, + aQuirksMode ? &RuleHash_ClassTable_CIOps + : &RuleHash_ClassTable_CSOps, + nsnull, sizeof(RuleHashTableEntry), 16); + PL_DHashTableInit(&mNameSpaceTable, &RuleHash_NameSpaceTable_Ops, nsnull, + sizeof(RuleHashTableEntry), 16); +} + +RuleHash::~RuleHash() +{ +#ifdef RULE_HASH_STATS + printf( +"RuleHash(%p):\n" +" Selectors: Universal (%u) NameSpace(%u) Tag(%u) Class(%u) Id(%u)\n" +" Content Nodes: Elements(%u) Pseudo-Elements(%u)\n" +" Element Calls: Universal(%u) NameSpace(%u) Tag(%u) Class(%u) Id(%u)\n" +" Pseudo-Element Calls: Tag(%u)\n", + NS_STATIC_CAST(void*, this), + mUniversalSelectors, mNameSpaceSelectors, mTagSelectors, + mClassSelectors, mIdSelectors, + mElementsMatched, + mPseudosMatched, + mElementUniversalCalls, mElementNameSpaceCalls, mElementTagCalls, + mElementClassCalls, mElementIdCalls, + mPseudoTagCalls); +#ifdef PRINT_UNIVERSAL_RULES + { + RuleValue* value = mUniversalRules; + if (value) { + printf(" Universal rules:\n"); + do { + nsAutoString selectorText; + PRUint32 lineNumber = value->mRule->GetLineNumber(); + nsCOMPtr sheet; + value->mRule->GetStyleSheet(*getter_AddRefs(sheet)); + nsCOMPtr cssSheet = do_QueryInterface(sheet); + value->mSelector->ToString(selectorText, cssSheet); + + printf(" line %d, %s\n", + lineNumber, NS_ConvertUTF16toUTF8(selectorText).get()); + value = value->mNext; + } while (value); + } + } +#endif // PRINT_UNIVERSAL_RULES +#endif // RULE_HASH_STATS + // Rule Values are arena allocated no need to delete them. Their destructor + // isn't doing any cleanup. So we dont even bother to enumerate through + // the hash tables and call their destructors. + if (nsnull != mEnumList) { + delete [] mEnumList; + } + // delete arena for strings and small objects + PL_DHashTableFinish(&mIdTable); + PL_DHashTableFinish(&mClassTable); + PL_DHashTableFinish(&mTagTable); + PL_DHashTableFinish(&mNameSpaceTable); + PL_FinishArenaPool(&mArena); +} + +void RuleHash::PrependRuleToTable(PLDHashTable* aTable, const void* aKey, + RuleValue* aRuleInfo) +{ + // Get a new or existing entry. + RuleHashTableEntry *entry = NS_STATIC_CAST(RuleHashTableEntry*, + PL_DHashTableOperate(aTable, aKey, PL_DHASH_ADD)); + if (!entry) + return; + entry->mRules = aRuleInfo->Add(mRuleCount++, entry->mRules); +} + +void RuleHash::PrependUniversalRule(RuleValue *aRuleInfo) +{ + mUniversalRules = aRuleInfo->Add(mRuleCount++, mUniversalRules); +} + +void RuleHash::PrependRule(RuleValue *aRuleInfo) +{ + nsCSSSelector *selector = aRuleInfo->mSelector; + if (nsnull != selector->mIDList) { + PrependRuleToTable(&mIdTable, selector->mIDList->mAtom, aRuleInfo); + RULE_HASH_STAT_INCREMENT(mIdSelectors); + } + else if (nsnull != selector->mClassList) { + PrependRuleToTable(&mClassTable, selector->mClassList->mAtom, aRuleInfo); + RULE_HASH_STAT_INCREMENT(mClassSelectors); + } + else if (nsnull != selector->mTag) { + PrependRuleToTable(&mTagTable, selector->mTag, aRuleInfo); + RULE_HASH_STAT_INCREMENT(mTagSelectors); + } + else if (kNameSpaceID_Unknown != selector->mNameSpace) { + PrependRuleToTable(&mNameSpaceTable, + NS_INT32_TO_PTR(selector->mNameSpace), aRuleInfo); + RULE_HASH_STAT_INCREMENT(mNameSpaceSelectors); + } + else { // universal tag selector + PrependUniversalRule(aRuleInfo); + RULE_HASH_STAT_INCREMENT(mUniversalSelectors); + } +} + +// this should cover practically all cases so we don't need to reallocate +#define MIN_ENUM_LIST_SIZE 8 + +#ifdef RULE_HASH_STATS +#define RULE_HASH_STAT_INCREMENT_LIST_COUNT(list_, var_) \ + do { ++(var_); (list_) = (list_)->mNext; } while (list_) +#else +#define RULE_HASH_STAT_INCREMENT_LIST_COUNT(list_, var_) \ + PR_BEGIN_MACRO PR_END_MACRO +#endif + +void RuleHash::EnumerateAllRules(PRInt32 aNameSpace, nsIAtom* aTag, + nsIAtom* aID, const nsAttrValue* aClassList, + RuleEnumFunc aFunc, void* aData) +{ + PRInt32 classCount = aClassList ? aClassList->GetAtomCount() : 0; + + // assume 1 universal, tag, id, and namespace, rather than wasting + // time counting + PRInt32 testCount = classCount + 4; + + if (mEnumListSize < testCount) { + delete [] mEnumList; + mEnumListSize = PR_MAX(testCount, MIN_ENUM_LIST_SIZE); + mEnumList = new RuleValue*[mEnumListSize]; + } + + PRInt32 valueCount = 0; + RULE_HASH_STAT_INCREMENT(mElementsMatched); + + { // universal rules + RuleValue* value = mUniversalRules; + if (nsnull != value) { + mEnumList[valueCount++] = value; + RULE_HASH_STAT_INCREMENT_LIST_COUNT(value, mElementUniversalCalls); + } + } + // universal rules within the namespace + if (kNameSpaceID_Unknown != aNameSpace) { + RuleHashTableEntry *entry = NS_STATIC_CAST(RuleHashTableEntry*, + PL_DHashTableOperate(&mNameSpaceTable, NS_INT32_TO_PTR(aNameSpace), + PL_DHASH_LOOKUP)); + if (PL_DHASH_ENTRY_IS_BUSY(entry)) { + RuleValue *value = entry->mRules; + mEnumList[valueCount++] = value; + RULE_HASH_STAT_INCREMENT_LIST_COUNT(value, mElementNameSpaceCalls); + } + } + if (nsnull != aTag) { + RuleHashTableEntry *entry = NS_STATIC_CAST(RuleHashTableEntry*, + PL_DHashTableOperate(&mTagTable, aTag, PL_DHASH_LOOKUP)); + if (PL_DHASH_ENTRY_IS_BUSY(entry)) { + RuleValue *value = entry->mRules; + mEnumList[valueCount++] = value; + RULE_HASH_STAT_INCREMENT_LIST_COUNT(value, mElementTagCalls); + } + } + if (nsnull != aID) { + RuleHashTableEntry *entry = NS_STATIC_CAST(RuleHashTableEntry*, + PL_DHashTableOperate(&mIdTable, aID, PL_DHASH_LOOKUP)); + if (PL_DHASH_ENTRY_IS_BUSY(entry)) { + RuleValue *value = entry->mRules; + mEnumList[valueCount++] = value; + RULE_HASH_STAT_INCREMENT_LIST_COUNT(value, mElementIdCalls); + } + } + { // extra scope to work around compiler bugs with |for| scoping. + for (PRInt32 index = 0; index < classCount; ++index) { + RuleHashTableEntry *entry = NS_STATIC_CAST(RuleHashTableEntry*, + PL_DHashTableOperate(&mClassTable, aClassList->AtomAt(index), + PL_DHASH_LOOKUP)); + if (PL_DHASH_ENTRY_IS_BUSY(entry)) { + RuleValue *value = entry->mRules; + mEnumList[valueCount++] = value; + RULE_HASH_STAT_INCREMENT_LIST_COUNT(value, mElementClassCalls); + } + } + } + NS_ASSERTION(valueCount <= testCount, "values exceeded list size"); + + if (valueCount > 0) { + // Merge the lists while there are still multiple lists to merge. + while (valueCount > 1) { + PRInt32 valueIndex = 0; + PRInt32 highestRuleIndex = mEnumList[valueIndex]->mBackwardIndex; + for (PRInt32 index = 1; index < valueCount; ++index) { + PRInt32 ruleIndex = mEnumList[index]->mBackwardIndex; + if (ruleIndex > highestRuleIndex) { + valueIndex = index; + highestRuleIndex = ruleIndex; + } + } + RuleValue *cur = mEnumList[valueIndex]; + (*aFunc)(cur->mRule, cur->mSelector, aData); + RuleValue *next = cur->mNext; + mEnumList[valueIndex] = next ? next : mEnumList[--valueCount]; + } + + // Fast loop over single value. + RuleValue* value = mEnumList[0]; + do { + (*aFunc)(value->mRule, value->mSelector, aData); + value = value->mNext; + } while (value); + } +} + +void RuleHash::EnumerateTagRules(nsIAtom* aTag, RuleEnumFunc aFunc, void* aData) +{ + RuleHashTableEntry *entry = NS_STATIC_CAST(RuleHashTableEntry*, + PL_DHashTableOperate(&mTagTable, aTag, PL_DHASH_LOOKUP)); + + RULE_HASH_STAT_INCREMENT(mPseudosMatched); + if (PL_DHASH_ENTRY_IS_BUSY(entry)) { + RuleValue *tagValue = entry->mRules; + do { + RULE_HASH_STAT_INCREMENT(mPseudoTagCalls); + (*aFunc)(tagValue->mRule, tagValue->mSelector, aData); + tagValue = tagValue->mNext; + } while (tagValue); + } +} + +//-------------------------------- + +// Attribute selectors hash table. +struct AttributeSelectorEntry : public PLDHashEntryHdr { + nsIAtom *mAttribute; + nsVoidArray *mSelectors; +}; + +PR_STATIC_CALLBACK(void) +AttributeSelectorClearEntry(PLDHashTable *table, PLDHashEntryHdr *hdr) +{ + AttributeSelectorEntry *entry = NS_STATIC_CAST(AttributeSelectorEntry*, hdr); + delete entry->mSelectors; + memset(entry, 0, table->entrySize); +} + +static PLDHashTableOps AttributeSelectorOps = { + PL_DHashAllocTable, + PL_DHashFreeTable, + PL_DHashGetKeyStub, + PL_DHashVoidPtrKeyStub, + PL_DHashMatchEntryStub, + PL_DHashMoveEntryStub, + AttributeSelectorClearEntry, + PL_DHashFinalizeStub, + NULL +}; + + +//-------------------------------- + +struct RuleCascadeData { + RuleCascadeData(nsIAtom *aMedium, PRBool aQuirksMode) + : mRuleHash(aQuirksMode), + mStateSelectors(), + mMedium(aMedium), + mNext(nsnull) + { + PL_DHashTableInit(&mAttributeSelectors, &AttributeSelectorOps, nsnull, + sizeof(AttributeSelectorEntry), 16); + } + + ~RuleCascadeData() + { + PL_DHashTableFinish(&mAttributeSelectors); + } + RuleHash mRuleHash; + nsVoidArray mStateSelectors; + nsVoidArray mClassSelectors; + nsVoidArray mIDSelectors; + PLDHashTable mAttributeSelectors; // nsIAtom* -> nsVoidArray* + + // Looks up or creates the appropriate list in |mAttributeSelectors|. + // Returns null only on allocation failure. + nsVoidArray* AttributeListFor(nsIAtom* aAttribute); + + nsCOMPtr mMedium; + RuleCascadeData* mNext; // for a different medium +}; + +nsVoidArray* +RuleCascadeData::AttributeListFor(nsIAtom* aAttribute) +{ + AttributeSelectorEntry *entry = NS_STATIC_CAST(AttributeSelectorEntry*, + PL_DHashTableOperate(&mAttributeSelectors, aAttribute, PL_DHASH_ADD)); + if (!entry) + return nsnull; + if (!entry->mSelectors) { + if (!(entry->mSelectors = new nsVoidArray)) { + PL_DHashTableRawRemove(&mAttributeSelectors, entry); + return nsnull; + } + entry->mAttribute = aAttribute; + } + return entry->mSelectors; +} + +// ------------------------------- +// CSS Style rule processor implementation +// + +nsCSSRuleProcessor::nsCSSRuleProcessor(const nsCOMArray& aSheets) + : mSheets(aSheets), + mRuleCascades(nsnull) +{ + for (PRInt32 i = mSheets.Count() - 1; i >= 0; --i) + mSheets[i]->AddRuleProcessor(this); +} + +nsCSSRuleProcessor::~nsCSSRuleProcessor() +{ + for (PRInt32 i = mSheets.Count() - 1; i >= 0; --i) + mSheets[i]->DropRuleProcessor(this); + mSheets.Clear(); + ClearRuleCascades(); +} + +NS_IMPL_ISUPPORTS1(nsCSSRuleProcessor, nsIStyleRuleProcessor) + +MOZ_DECL_CTOR_COUNTER(RuleProcessorData) + +RuleProcessorData::RuleProcessorData(nsPresContext* aPresContext, + nsIContent* aContent, + nsRuleWalker* aRuleWalker, + nsCompatibility* aCompat /*= nsnull*/) +{ + MOZ_COUNT_CTOR(RuleProcessorData); + + NS_PRECONDITION(aPresContext, "null pointer"); + NS_ASSERTION(!aContent || aContent->IsContentOfType(nsIContent::eELEMENT), + "non-element leaked into SelectorMatches"); + + mPresContext = aPresContext; + mContent = aContent; + mParentContent = nsnull; + mRuleWalker = aRuleWalker; + mScopedRoot = nsnull; + + mContentTag = nsnull; + mContentID = nsnull; + mHasAttributes = PR_FALSE; + mIsHTMLContent = PR_FALSE; + mIsHTMLLink = PR_FALSE; + mIsSimpleXLink = PR_FALSE; + mLinkState = eLinkState_Unknown; + mEventState = 0; + mNameSpaceID = kNameSpaceID_Unknown; + mPreviousSiblingData = nsnull; + mParentData = nsnull; + mLanguage = nsnull; + mClasses = nsnull; + + // get the compat. mode (unless it is provided) + if (!aCompat) { + mCompatMode = mPresContext->CompatibilityMode(); + } else { + mCompatMode = *aCompat; + } + + + if (aContent) { + // get the tag and parent + mContentTag = aContent->Tag(); + mParentContent = aContent->GetParent(); + + // get the event state + mPresContext->EventStateManager()->GetContentState(aContent, mEventState); + + // get the ID and classes for the content + mContentID = aContent->GetID(); + mClasses = aContent->GetClasses(); + + // see if there are attributes for the content + mHasAttributes = aContent->GetAttrCount() > 0; + + // check for HTMLContent and Link status + if (aContent->IsContentOfType(nsIContent::eHTML)) { + mIsHTMLContent = PR_TRUE; + // Note that we want to treat non-XML HTML content as XHTML for namespace + // purposes, since html.css has that namespace declared. + mNameSpaceID = kNameSpaceID_XHTML; + } else { + // get the namespace + mNameSpaceID = aContent->GetNameSpaceID(); + } + + + + // if HTML content and it has some attributes, check for an HTML link + // NOTE: optimization: cannot be a link if no attributes (since it needs an href) + if (mIsHTMLContent && mHasAttributes) { + // check if it is an HTML Link + if(nsStyleUtil::IsHTMLLink(aContent, mContentTag, mPresContext, &mLinkState)) { + mIsHTMLLink = PR_TRUE; + } + } + + // if not an HTML link, check for a simple xlink (cannot be both HTML link and xlink) + // NOTE: optimization: cannot be an XLink if no attributes (since it needs an + if(!mIsHTMLLink && + mHasAttributes && + !(mIsHTMLContent || aContent->IsContentOfType(nsIContent::eXUL)) && + nsStyleUtil::IsSimpleXlink(aContent, mPresContext, &mLinkState)) { + mIsSimpleXLink = PR_TRUE; + } + } +} + +RuleProcessorData::~RuleProcessorData() +{ + MOZ_COUNT_DTOR(RuleProcessorData); + + if (mPreviousSiblingData) + mPreviousSiblingData->Destroy(mPresContext); + if (mParentData) + mParentData->Destroy(mPresContext); + + delete mLanguage; +} + +const nsString* RuleProcessorData::GetLang() +{ + if (!mLanguage) { + mLanguage = new nsAutoString(); + if (!mLanguage) + return nsnull; + for (nsIContent* content = mContent; content; + content = content->GetParent()) { + if (content->GetAttrCount() > 0) { + // xml:lang has precedence over lang on HTML elements (see + // XHTML1 section C.7). + nsAutoString value; + PRBool hasAttr = content->GetAttr(kNameSpaceID_XML, nsHTMLAtoms::lang, + value); + if (!hasAttr && content->IsContentOfType(nsIContent::eHTML)) { + hasAttr = content->GetAttr(kNameSpaceID_None, nsHTMLAtoms::lang, + value); + } + if (hasAttr) { + *mLanguage = value; + break; + } + } + } + } + return mLanguage; +} + +static const PRUnichar kNullCh = PRUnichar('\0'); + +static PRBool ValueIncludes(const nsSubstring& aValueList, + const nsSubstring& aValue, + const nsStringComparator& aComparator) +{ + const PRUnichar *p = aValueList.BeginReading(), + *p_end = aValueList.EndReading(); + + while (p < p_end) { + // skip leading space + while (p != p_end && nsCRT::IsAsciiSpace(*p)) + ++p; + + const PRUnichar *val_start = p; + + // look for space or end + while (p != p_end && !nsCRT::IsAsciiSpace(*p)) + ++p; + + const PRUnichar *val_end = p; + + if (val_start < val_end && + aValue.Equals(Substring(val_start, val_end), aComparator)) + return PR_TRUE; + + ++p; // we know the next character is not whitespace + } + return PR_FALSE; +} + +inline PRBool IsLinkPseudo(nsIAtom* aAtom) +{ + return PRBool ((nsCSSPseudoClasses::link == aAtom) || + (nsCSSPseudoClasses::visited == aAtom) || + (nsCSSPseudoClasses::mozAnyLink == aAtom)); +} + +// Return whether we should apply a "global" (i.e., universal-tag) +// selector for event states in quirks mode. Note that +// |data.mIsHTMLLink| is checked separately by the caller, so we return +// false for |nsHTMLAtoms::a|, which here means a named anchor. +inline PRBool IsQuirkEventSensitive(nsIAtom *aContentTag) +{ + return PRBool ((nsHTMLAtoms::button == aContentTag) || + (nsHTMLAtoms::img == aContentTag) || + (nsHTMLAtoms::input == aContentTag) || + (nsHTMLAtoms::label == aContentTag) || + (nsHTMLAtoms::select == aContentTag) || + (nsHTMLAtoms::textarea == aContentTag)); +} + + +static PRBool IsSignificantChild(nsIContent* aChild, PRBool aTextIsSignificant, PRBool aWhitespaceIsSignificant) +{ + NS_ASSERTION(!aWhitespaceIsSignificant || aTextIsSignificant, + "Nonsensical arguments"); + + PRBool isText = aChild->IsContentOfType(nsIContent::eTEXT); + + if (!isText && !aChild->IsContentOfType(nsIContent::eCOMMENT) && + !aChild->IsContentOfType(nsIContent::ePROCESSING_INSTRUCTION)) { + return PR_TRUE; + } + + if (aTextIsSignificant && isText) { + if (!aWhitespaceIsSignificant) { + nsCOMPtr text = do_QueryInterface(aChild); + + if (text && !text->IsOnlyWhitespace()) + return PR_TRUE; + } + else { + return PR_TRUE; + } + } + + return PR_FALSE; +} + +// This function is to be called once we have fetched a value for an attribute +// whose namespace and name match those of aAttrSelector. This function +// performs comparisons on the value only, based on aAttrSelector->mFunction. +static PRBool AttrMatchesValue(const nsAttrSelector* aAttrSelector, + const nsString& aValue) +{ + NS_PRECONDITION(aAttrSelector, "Must have an attribute selector"); + const nsDefaultStringComparator defaultComparator; + const nsCaseInsensitiveStringComparator ciComparator; + const nsStringComparator& comparator = aAttrSelector->mCaseSensitive + ? NS_STATIC_CAST(const nsStringComparator&, defaultComparator) + : NS_STATIC_CAST(const nsStringComparator&, ciComparator); + switch (aAttrSelector->mFunction) { + case NS_ATTR_FUNC_EQUALS: + return aValue.Equals(aAttrSelector->mValue, comparator); + case NS_ATTR_FUNC_INCLUDES: + return ValueIncludes(aValue, aAttrSelector->mValue, comparator); + case NS_ATTR_FUNC_DASHMATCH: + return nsStyleUtil::DashMatchCompare(aValue, aAttrSelector->mValue, comparator); + case NS_ATTR_FUNC_ENDSMATCH: + return StringEndsWith(aValue, aAttrSelector->mValue, comparator); + case NS_ATTR_FUNC_BEGINSMATCH: + return StringBeginsWith(aValue, aAttrSelector->mValue, comparator); + case NS_ATTR_FUNC_CONTAINSMATCH: + return FindInReadable(aAttrSelector->mValue, aValue, comparator); + default: + NS_NOTREACHED("Shouldn't be ending up here"); + return PR_FALSE; + } +} + +// 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 + PRBool* const aDependence = nsnull) + +{ + // 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 + return PR_FALSE; + } + + 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; + nsIContent *parent = data.mParentContent; + if (parent) { + PRBool acceptNonWhitespace = + nsCSSPseudoClasses::firstNode == pseudoClass->mAtom; + PRInt32 index = -1; + do { + firstChild = parent->GetChildAt(++index); + // stop at first non-comment and non-whitespace node (and + // non-text node for firstChild) + } while (firstChild && + !IsSignificantChild(firstChild, acceptNonWhitespace, PR_FALSE)); + } + result = (data.mContent == firstChild); + } + else if ((nsCSSPseudoClasses::lastChild == pseudoClass->mAtom) || + (nsCSSPseudoClasses::lastNode == pseudoClass->mAtom)) { + nsIContent *lastChild = nsnull; + nsIContent *parent = data.mParentContent; + if (parent) { + PRBool acceptNonWhitespace = + nsCSSPseudoClasses::lastNode == pseudoClass->mAtom; + PRUint32 index = parent->GetChildCount(); + do { + lastChild = parent->GetChildAt(--index); + // stop at first non-comment and non-whitespace node (and + // non-text node for lastChild) + } while (lastChild && + !IsSignificantChild(lastChild, acceptNonWhitespace, PR_FALSE)); + } + result = (data.mContent == lastChild); + } + else if (nsCSSPseudoClasses::onlyChild == pseudoClass->mAtom) { + nsIContent *onlyChild = nsnull; + nsIContent *moreChild = nsnull; + nsIContent *parent = data.mParentContent; + if (parent) { + PRInt32 index = -1; + do { + onlyChild = parent->GetChildAt(++index); + // stop at first non-comment, non-whitespace and non-text node + } while (onlyChild && + !IsSignificantChild(onlyChild, PR_FALSE, PR_FALSE)); + if (data.mContent == onlyChild) { + // see if there's any more + do { + moreChild = parent->GetChildAt(++index); + } while (moreChild && !IsSignificantChild(moreChild, PR_FALSE, PR_FALSE)); + } + } + result = (data.mContent == onlyChild && moreChild == nsnull); + } + 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; + + do { + child = element->GetChildAt(++index); + // stop at first non-comment (and non-whitespace for + // :-moz-only-whitespace) node + } while (child && !IsSignificantChild(child, PR_TRUE, isWhitespaceSignificant)); + result = (child == nsnull); + } + else if (nsCSSPseudoClasses::mozEmptyExceptChildrenWithLocalname == pseudoClass->mAtom) { + NS_ASSERTION(pseudoClass->mString, "Must have string!"); + nsIContent *child = nsnull; + nsIContent *element = data.mContent; + PRInt32 index = -1; + + do { + child = element->GetChildAt(++index); + } while (child && + (!IsSignificantChild(child, PR_TRUE, PR_FALSE) || + (child->GetNameSpaceID() == element->GetNameSpaceID() && + child->Tag()->Equals(nsDependentString(pseudoClass->mString))))); + result = (child == nsnull); + } + else if (nsCSSPseudoClasses::mozHasHandlerRef == pseudoClass->mAtom) { + nsIContent *child = nsnull; + nsIContent *element = data.mContent; + PRInt32 index = -1; + + result = PR_FALSE; + if (element) { + do { + child = element->GetChildAt(++index); + if (child && child->IsContentOfType(nsIContent::eHTML) && + child->Tag() == nsHTMLAtoms::param && + child->AttrValueIs(kNameSpaceID_None, nsHTMLAtoms::name, + NS_LITERAL_STRING("pluginurl"), eIgnoreCase)) { + result = PR_TRUE; + break; + } + } while (child); + } + } + else if (nsCSSPseudoClasses::root == pseudoClass->mAtom) { + 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); + } + else if (nsCSSPseudoClasses::lang == pseudoClass->mAtom) { + NS_ASSERTION(nsnull != pseudoClass->mString, "null lang parameter"); + 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 + // from the parent we have to be prepared to look at all parent + // 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 = nsStyleUtil::DashMatchCompare(*lang, + nsDependentString(pseudoClass->mString), + nsCaseInsensitiveStringComparator()); + } + else if (data.mContent) { + nsIDocument* doc = data.mContent->GetDocument(); + if (doc) { + // Try to get the language from the HTTP header or if this + // is missing as well from the preferences. + // The content language can be a comma-separated list of + // language codes. + nsAutoString language; + doc->GetContentLanguage(language); + + nsDependentString langString(pseudoClass->mString); + language.StripWhitespace(); + PRInt32 begin = 0; + PRInt32 len = language.Length(); + while (begin < len) { + PRInt32 end = language.FindChar(PRUnichar(','), begin); + if (end == kNotFound) { + end = len; + } + if (nsStyleUtil::DashMatchCompare(Substring(language, begin, end-begin), + langString, + nsCaseInsensitiveStringComparator())) { + result = PR_TRUE; + break; + } + begin = end + 1; + } + } + } + } + } else if (nsCSSPseudoClasses::active == pseudoClass->mAtom) { + stateToCheck = NS_EVENT_STATE_ACTIVE; + } + 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 = PR_TRUE; + } + 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 = PR_FALSE; // not a link + } + } + else if (nsCSSPseudoClasses::checked == pseudoClass->mAtom) { + // This pseudoclass matches the selected state on the following elements: + //