/* -*- 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 "nsGkAtoms.h" #include "nsString.h" #include "nsUnicharUtils.h" #include "nsVoidArray.h" #include "nsDOMError.h" #include "nsRuleWalker.h" #include "nsCSSPseudoClasses.h" #include "nsIContent.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); } typedef nsIAtom* (* PR_CALLBACK RuleHashGetKey) (PLDHashTable *table, const PLDHashEntryHdr *entry); struct RuleHashTableOps { PLDHashTableOps ops; // Extra callback to avoid duplicating the matchEntry callback for // each table. (There used to be a getKey callback in // PLDHashTableOps.) RuleHashGetKey getKey; }; inline const RuleHashTableOps* ToLocalOps(const PLDHashTableOps *aOps) { return (const RuleHashTableOps*) (((const char*) aOps) - offsetof(RuleHashTableOps, ops)); } 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 our extra |getKey| callback to avoid code duplication. nsIAtom *entry_atom = ToLocalOps(table->ops)->getKey(table, 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 our extra |getKey| callback to avoid code duplication. nsIAtom *entry_atom = ToLocalOps(table->ops)->getKey(table, hdr); return match_atom == entry_atom; } PR_STATIC_CALLBACK(nsIAtom*) RuleHash_TagTable_GetKey(PLDHashTable *table, const PLDHashEntryHdr *hdr) { const RuleHashTableEntry *entry = NS_STATIC_CAST(const RuleHashTableEntry*, hdr); return entry->mRules->mSelector->mTag; } PR_STATIC_CALLBACK(nsIAtom*) RuleHash_ClassTable_GetKey(PLDHashTable *table, const PLDHashEntryHdr *hdr) { const RuleHashTableEntry *entry = NS_STATIC_CAST(const RuleHashTableEntry*, hdr); return entry->mRules->mSelector->mClassList->mAtom; } PR_STATIC_CALLBACK(nsIAtom*) RuleHash_IdTable_GetKey(PLDHashTable *table, const PLDHashEntryHdr *hdr) { const RuleHashTableEntry *entry = NS_STATIC_CAST(const RuleHashTableEntry*, hdr); return entry->mRules->mSelector->mIDList->mAtom; } 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 const RuleHashTableOps RuleHash_TagTable_Ops = { { PL_DHashAllocTable, PL_DHashFreeTable, PL_DHashVoidPtrKeyStub, RuleHash_CSMatchEntry, PL_DHashMoveEntryStub, PL_DHashClearEntryStub, PL_DHashFinalizeStub, NULL }, RuleHash_TagTable_GetKey }; // Case-sensitive ops. static const RuleHashTableOps RuleHash_ClassTable_CSOps = { { PL_DHashAllocTable, PL_DHashFreeTable, PL_DHashVoidPtrKeyStub, RuleHash_CSMatchEntry, PL_DHashMoveEntryStub, PL_DHashClearEntryStub, PL_DHashFinalizeStub, NULL }, RuleHash_ClassTable_GetKey }; // Case-insensitive ops. static const RuleHashTableOps RuleHash_ClassTable_CIOps = { { PL_DHashAllocTable, PL_DHashFreeTable, RuleHash_CIHashKey, RuleHash_CIMatchEntry, PL_DHashMoveEntryStub, PL_DHashClearEntryStub, PL_DHashFinalizeStub, NULL }, RuleHash_ClassTable_GetKey }; // Case-sensitive ops. static const RuleHashTableOps RuleHash_IdTable_CSOps = { { PL_DHashAllocTable, PL_DHashFreeTable, PL_DHashVoidPtrKeyStub, RuleHash_CSMatchEntry, PL_DHashMoveEntryStub, PL_DHashClearEntryStub, PL_DHashFinalizeStub, NULL }, RuleHash_IdTable_GetKey }; // Case-insensitive ops. static const RuleHashTableOps RuleHash_IdTable_CIOps = { { PL_DHashAllocTable, PL_DHashFreeTable, RuleHash_CIHashKey, RuleHash_CIMatchEntry, PL_DHashMoveEntryStub, PL_DHashClearEntryStub, PL_DHashFinalizeStub, NULL }, RuleHash_IdTable_GetKey }; static const PLDHashTableOps RuleHash_NameSpaceTable_Ops = { PL_DHashAllocTable, PL_DHashFreeTable, 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.ops, nsnull, sizeof(RuleHashTableEntry), 64); PL_DHashTableInit(&mIdTable, aQuirksMode ? &RuleHash_IdTable_CIOps.ops : &RuleHash_IdTable_CSOps.ops, nsnull, sizeof(RuleHashTableEntry), 16); PL_DHashTableInit(&mClassTable, aQuirksMode ? &RuleHash_ClassTable_CIOps.ops : &RuleHash_ClassTable_CSOps.ops, 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 const PLDHashTableOps AttributeSelectorOps = { PL_DHashAllocTable, PL_DHashFreeTable, 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) RuleProcessorData::RuleProcessorData(nsPresContext* aPresContext, nsIContent* aContent, nsRuleWalker* aRuleWalker, nsCompatibility* aCompat /*= nsnull*/) { MOZ_COUNT_CTOR(RuleProcessorData); NS_PRECONDITION(aPresContext, "null pointer"); NS_ASSERTION(!aContent || aContent->IsNodeOfType(nsINode::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; mIsLink = 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->IsNodeOfType(nsINode::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)) { mIsLink = 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(!mIsLink && mHasAttributes && !(mIsHTMLContent || aContent->IsNodeOfType(nsINode::eXUL)) && nsStyleUtil::IsLink(aContent, mPresContext, &mLinkState)) { mIsLink = PR_TRUE; } } } RuleProcessorData::~RuleProcessorData() { MOZ_COUNT_DTOR(RuleProcessorData); // Destroy potentially long chains of previous sibling and parent data // without more than one level of recursion. if (mPreviousSiblingData || mParentData) { nsAutoVoidArray destroyQueue; destroyQueue.AppendElement(this); do { RuleProcessorData *d = NS_STATIC_CAST(RuleProcessorData*, destroyQueue.FastElementAt(destroyQueue.Count() - 1)); destroyQueue.RemoveElementAt(destroyQueue.Count() - 1); if (d->mPreviousSiblingData) { destroyQueue.AppendElement(d->mPreviousSiblingData); d->mPreviousSiblingData = nsnull; } if (d->mParentData) { destroyQueue.AppendElement(d->mParentData); d->mParentData = nsnull; } if (d != this) d->Destroy(mPresContext); } while (destroyQueue.Count()); } 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, nsGkAtoms::lang, value); if (!hasAttr && content->IsNodeOfType(nsINode::eHTML)) { hasAttr = content->GetAttr(kNameSpaceID_None, nsGkAtoms::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.mIsLink| is checked separately by the caller, so we return // false for |nsGkAtoms::a|, which here means a named anchor. inline PRBool IsQuirkEventSensitive(nsIAtom *aContentTag) { return PRBool ((nsGkAtoms::button == aContentTag) || (nsGkAtoms::img == aContentTag) || (nsGkAtoms::input == aContentTag) || (nsGkAtoms::label == aContentTag) || (nsGkAtoms::select == aContentTag) || (nsGkAtoms::textarea == aContentTag)); } static PRBool IsSignificantChild(nsIContent* aChild, PRBool aTextIsSignificant, PRBool aWhitespaceIsSignificant) { NS_ASSERTION(!aWhitespaceIsSignificant || aTextIsSignificant, "Nonsensical arguments"); PRBool isText = aChild->IsNodeOfType(nsINode::eTEXT); if (!isText && !aChild->IsNodeOfType(nsINode::eCOMMENT) && !aChild->IsNodeOfType(nsINode::ePROCESSING_INSTRUCTION)) { return PR_TRUE; } return aTextIsSignificant && isText && aChild->TextLength() != 0 && (aWhitespaceIsSignificant || !aChild->TextIsOnlyWhitespace()); } // 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->IsNodeOfType(nsINode::eHTML) && child->Tag() == nsGkAtoms::param && child->AttrValueIs(kNameSpaceID_None, nsGkAtoms::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.mIsLink) { 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 mIsLink"); 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: //