diff -r 000000000000 -r 6474c204b198 layout/style/nsCSSRuleProcessor.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/layout/style/nsCSSRuleProcessor.cpp Wed Dec 31 06:09:35 2014 +0100 @@ -0,0 +1,3624 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +// vim:cindent:tabstop=2:expandtab:shiftwidth=2: +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + * style rule processor for CSS style sheets, responsible for selector + * matching and cascading + */ + +#define PL_ARENA_CONST_ALIGN_MASK 7 +// We want page-sized arenas so there's no fragmentation involved. +// Including plarena.h must come first to avoid it being included by some +// header file thereby making PL_ARENA_CONST_ALIGN_MASK ineffective. +#define NS_CASCADEENUMDATA_ARENA_BLOCK_SIZE (4096) +#include "plarena.h" + +#include "nsCSSRuleProcessor.h" +#include "nsRuleProcessorData.h" +#include +#include "nsIAtom.h" +#include "pldhash.h" +#include "nsICSSPseudoComparator.h" +#include "mozilla/MemoryReporting.h" +#include "mozilla/css/StyleRule.h" +#include "mozilla/css/GroupRule.h" +#include "nsIDocument.h" +#include "nsPresContext.h" +#include "nsGkAtoms.h" +#include "nsUnicharUtils.h" +#include "nsError.h" +#include "nsRuleWalker.h" +#include "nsCSSPseudoClasses.h" +#include "nsCSSPseudoElements.h" +#include "nsIContent.h" +#include "nsCOMPtr.h" +#include "nsHashKeys.h" +#include "nsStyleUtil.h" +#include "nsQuickSort.h" +#include "nsAttrValue.h" +#include "nsAttrValueInlines.h" +#include "nsAttrName.h" +#include "nsTArray.h" +#include "nsContentUtils.h" +#include "nsIMediaList.h" +#include "nsCSSRules.h" +#include "nsStyleSet.h" +#include "mozilla/dom/Element.h" +#include "nsNthIndexCache.h" +#include "mozilla/ArrayUtils.h" +#include "mozilla/EventStates.h" +#include "mozilla/Preferences.h" +#include "mozilla/LookAndFeel.h" +#include "mozilla/Likely.h" + +using namespace mozilla; +using namespace mozilla::dom; + +#define VISITED_PSEUDO_PREF "layout.css.visited_links_enabled" + +static bool gSupportVisitedPseudo = true; + +static nsTArray< nsCOMPtr >* sSystemMetrics = 0; + +#ifdef XP_WIN +uint8_t nsCSSRuleProcessor::sWinThemeId = LookAndFeel::eWindowsTheme_Generic; +#endif + +/** + * A struct representing a given CSS rule and a particular selector + * from that rule's selector list. + */ +struct RuleSelectorPair { + RuleSelectorPair(css::StyleRule* aRule, nsCSSSelector* aSelector) + : mRule(aRule), mSelector(aSelector) {} + // If this class ever grows a destructor, deal with + // PerWeightDataListItem appropriately. + + css::StyleRule* mRule; + nsCSSSelector* mSelector; // which of |mRule|'s selectors +}; + +#define NS_IS_ANCESTOR_OPERATOR(ch) \ + ((ch) == char16_t(' ') || (ch) == char16_t('>')) + +/** + * A struct representing a particular rule in an ordered list of rules + * (the ordering depending on the weight of mSelector and the order of + * our rules to start with). + */ +struct RuleValue : RuleSelectorPair { + enum { + eMaxAncestorHashes = 4 + }; + + RuleValue(const RuleSelectorPair& aRuleSelectorPair, int32_t aIndex, + bool aQuirksMode) : + RuleSelectorPair(aRuleSelectorPair), + mIndex(aIndex) + { + CollectAncestorHashes(aQuirksMode); + } + + int32_t mIndex; // High index means high weight/order. + uint32_t mAncestorSelectorHashes[eMaxAncestorHashes]; + +private: + void CollectAncestorHashes(bool aQuirksMode) { + // Collect up our mAncestorSelectorHashes. It's not clear whether it's + // better to stop once we've found eMaxAncestorHashes of them or to keep + // going and preferentially collect information from selectors higher up the + // chain... Let's do the former for now. + size_t hashIndex = 0; + for (nsCSSSelector* sel = mSelector->mNext; sel; sel = sel->mNext) { + if (!NS_IS_ANCESTOR_OPERATOR(sel->mOperator)) { + // |sel| is going to select something that's not actually one of our + // ancestors, so don't add it to mAncestorSelectorHashes. But keep + // going, because it'll select a sibling of one of our ancestors, so its + // ancestors would be our ancestors too. + continue; + } + + // Now sel is supposed to select one of our ancestors. Grab + // whatever info we can from it into mAncestorSelectorHashes. + // But in qurks mode, don't grab IDs and classes because those + // need to be matched case-insensitively. + if (!aQuirksMode) { + nsAtomList* ids = sel->mIDList; + while (ids) { + mAncestorSelectorHashes[hashIndex++] = ids->mAtom->hash(); + if (hashIndex == eMaxAncestorHashes) { + return; + } + ids = ids->mNext; + } + + nsAtomList* classes = sel->mClassList; + while (classes) { + mAncestorSelectorHashes[hashIndex++] = classes->mAtom->hash(); + if (hashIndex == eMaxAncestorHashes) { + return; + } + classes = classes->mNext; + } + } + + // Only put in the tag name if it's all-lowercase. Otherwise we run into + // trouble because we may test the wrong one of mLowercaseTag and + // mCasedTag against the filter. + if (sel->mLowercaseTag && sel->mCasedTag == sel->mLowercaseTag) { + mAncestorSelectorHashes[hashIndex++] = sel->mLowercaseTag->hash(); + if (hashIndex == eMaxAncestorHashes) { + return; + } + } + } + + while (hashIndex != eMaxAncestorHashes) { + mAncestorSelectorHashes[hashIndex++] = 0; + } + } +}; + +// ------------------------------ +// Rule hash table +// + +// Uses any of the sets of ops below. +struct RuleHashTableEntry : public PLDHashEntryHdr { + // If you add members that have heap allocated memory be sure to change the + // logic in SizeOfRuleHashTableEntry(). + // Auto length 1, because we always have at least one entry in mRules. + nsAutoTArray mRules; +}; + +struct RuleHashTagTableEntry : public RuleHashTableEntry { + // If you add members that have heap allocated memory be sure to change the + // logic in RuleHash::SizeOf{In,Ex}cludingThis. + nsCOMPtr mTag; +}; + +static PLDHashNumber +RuleHash_CIHashKey(PLDHashTable *table, const void *key) +{ + nsIAtom *atom = const_cast(static_cast(key)); + + nsAutoString str; + atom->ToString(str); + nsContentUtils::ASCIIToLower(str); + return HashString(str); +} + +typedef nsIAtom* +(* RuleHashGetKey) (PLDHashTable *table, const PLDHashEntryHdr *entry); + +struct RuleHashTableOps { + const 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)); +} + +static bool +RuleHash_CIMatchEntry(PLDHashTable *table, const PLDHashEntryHdr *hdr, + const void *key) +{ + nsIAtom *match_atom = const_cast(static_cast + (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 true; + + // Use EqualsIgnoreASCIICase instead of full on unicode case conversion + // in order to save on performance. This is only used in quirks mode + // anyway. + + return + nsContentUtils::EqualsIgnoreASCIICase(nsDependentAtomString(entry_atom), + nsDependentAtomString(match_atom)); +} + +static bool +RuleHash_CSMatchEntry(PLDHashTable *table, const PLDHashEntryHdr *hdr, + const void *key) +{ + nsIAtom *match_atom = const_cast(static_cast + (key)); + // Use our extra |getKey| callback to avoid code duplication. + nsIAtom *entry_atom = ToLocalOps(table->ops)->getKey(table, hdr); + + return match_atom == entry_atom; +} + +static bool +RuleHash_InitEntry(PLDHashTable *table, PLDHashEntryHdr *hdr, + const void *key) +{ + RuleHashTableEntry* entry = static_cast(hdr); + new (entry) RuleHashTableEntry(); + return true; +} + +static void +RuleHash_ClearEntry(PLDHashTable *table, PLDHashEntryHdr *hdr) +{ + RuleHashTableEntry* entry = static_cast(hdr); + entry->~RuleHashTableEntry(); +} + +static void +RuleHash_MoveEntry(PLDHashTable *table, const PLDHashEntryHdr *from, + PLDHashEntryHdr *to) +{ + NS_PRECONDITION(from != to, "This is not going to work!"); + RuleHashTableEntry *oldEntry = + const_cast( + static_cast(from)); + RuleHashTableEntry *newEntry = new (to) RuleHashTableEntry(); + newEntry->mRules.SwapElements(oldEntry->mRules); + oldEntry->~RuleHashTableEntry(); +} + +static bool +RuleHash_TagTable_MatchEntry(PLDHashTable *table, const PLDHashEntryHdr *hdr, + const void *key) +{ + nsIAtom *match_atom = const_cast(static_cast + (key)); + nsIAtom *entry_atom = static_cast(hdr)->mTag; + + return match_atom == entry_atom; +} + +static bool +RuleHash_TagTable_InitEntry(PLDHashTable *table, PLDHashEntryHdr *hdr, + const void *key) +{ + RuleHashTagTableEntry* entry = static_cast(hdr); + new (entry) RuleHashTagTableEntry(); + entry->mTag = const_cast(static_cast(key)); + return true; +} + +static void +RuleHash_TagTable_ClearEntry(PLDHashTable *table, PLDHashEntryHdr *hdr) +{ + RuleHashTagTableEntry* entry = static_cast(hdr); + entry->~RuleHashTagTableEntry(); +} + +static void +RuleHash_TagTable_MoveEntry(PLDHashTable *table, const PLDHashEntryHdr *from, + PLDHashEntryHdr *to) +{ + NS_PRECONDITION(from != to, "This is not going to work!"); + RuleHashTagTableEntry *oldEntry = + const_cast( + static_cast(from)); + RuleHashTagTableEntry *newEntry = new (to) RuleHashTagTableEntry(); + newEntry->mTag.swap(oldEntry->mTag); + newEntry->mRules.SwapElements(oldEntry->mRules); + oldEntry->~RuleHashTagTableEntry(); +} + +static nsIAtom* +RuleHash_ClassTable_GetKey(PLDHashTable *table, const PLDHashEntryHdr *hdr) +{ + const RuleHashTableEntry *entry = + static_cast(hdr); + nsCSSSelector* selector = entry->mRules[0].mSelector; + if (selector->IsPseudoElement()) { + selector = selector->mNext; + } + return selector->mClassList->mAtom; +} + +static nsIAtom* +RuleHash_IdTable_GetKey(PLDHashTable *table, const PLDHashEntryHdr *hdr) +{ + const RuleHashTableEntry *entry = + static_cast(hdr); + nsCSSSelector* selector = entry->mRules[0].mSelector; + if (selector->IsPseudoElement()) { + selector = selector->mNext; + } + return selector->mIDList->mAtom; +} + +static PLDHashNumber +RuleHash_NameSpaceTable_HashKey(PLDHashTable *table, const void *key) +{ + return NS_PTR_TO_INT32(key); +} + +static bool +RuleHash_NameSpaceTable_MatchEntry(PLDHashTable *table, + const PLDHashEntryHdr *hdr, + const void *key) +{ + const RuleHashTableEntry *entry = + static_cast(hdr); + + nsCSSSelector* selector = entry->mRules[0].mSelector; + if (selector->IsPseudoElement()) { + selector = selector->mNext; + } + return NS_PTR_TO_INT32(key) == selector->mNameSpace; +} + +static const PLDHashTableOps RuleHash_TagTable_Ops = { + PL_DHashAllocTable, + PL_DHashFreeTable, + PL_DHashVoidPtrKeyStub, + RuleHash_TagTable_MatchEntry, + RuleHash_TagTable_MoveEntry, + RuleHash_TagTable_ClearEntry, + PL_DHashFinalizeStub, + RuleHash_TagTable_InitEntry +}; + +// Case-sensitive ops. +static const RuleHashTableOps RuleHash_ClassTable_CSOps = { + { + PL_DHashAllocTable, + PL_DHashFreeTable, + PL_DHashVoidPtrKeyStub, + RuleHash_CSMatchEntry, + RuleHash_MoveEntry, + RuleHash_ClearEntry, + PL_DHashFinalizeStub, + RuleHash_InitEntry + }, + RuleHash_ClassTable_GetKey +}; + +// Case-insensitive ops. +static const RuleHashTableOps RuleHash_ClassTable_CIOps = { + { + PL_DHashAllocTable, + PL_DHashFreeTable, + RuleHash_CIHashKey, + RuleHash_CIMatchEntry, + RuleHash_MoveEntry, + RuleHash_ClearEntry, + PL_DHashFinalizeStub, + RuleHash_InitEntry + }, + RuleHash_ClassTable_GetKey +}; + +// Case-sensitive ops. +static const RuleHashTableOps RuleHash_IdTable_CSOps = { + { + PL_DHashAllocTable, + PL_DHashFreeTable, + PL_DHashVoidPtrKeyStub, + RuleHash_CSMatchEntry, + RuleHash_MoveEntry, + RuleHash_ClearEntry, + PL_DHashFinalizeStub, + RuleHash_InitEntry + }, + RuleHash_IdTable_GetKey +}; + +// Case-insensitive ops. +static const RuleHashTableOps RuleHash_IdTable_CIOps = { + { + PL_DHashAllocTable, + PL_DHashFreeTable, + RuleHash_CIHashKey, + RuleHash_CIMatchEntry, + RuleHash_MoveEntry, + RuleHash_ClearEntry, + PL_DHashFinalizeStub, + RuleHash_InitEntry + }, + RuleHash_IdTable_GetKey +}; + +static const PLDHashTableOps RuleHash_NameSpaceTable_Ops = { + PL_DHashAllocTable, + PL_DHashFreeTable, + RuleHash_NameSpaceTable_HashKey, + RuleHash_NameSpaceTable_MatchEntry, + RuleHash_MoveEntry, + RuleHash_ClearEntry, + PL_DHashFinalizeStub, + RuleHash_InitEntry +}; + +#undef RULE_HASH_STATS +#undef PRINT_UNIVERSAL_RULES + +#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 + +struct NodeMatchContext; + +class RuleHash { +public: + RuleHash(bool aQuirksMode); + ~RuleHash(); + void AppendRule(const RuleSelectorPair &aRuleInfo); + void EnumerateAllRules(Element* aElement, ElementDependentRuleProcessorData* aData, + NodeMatchContext& aNodeMatchContext); + + size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const; + size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const; + +protected: + typedef nsTArray RuleValueList; + void AppendRuleToTable(PLDHashTable* aTable, const void* aKey, + const RuleSelectorPair& aRuleInfo); + void AppendUniversalRule(const RuleSelectorPair& aRuleInfo); + + int32_t mRuleCount; + // The hashtables are lazily initialized; we use a null .ops to + // indicate that they need initialization. + PLDHashTable mIdTable; + PLDHashTable mClassTable; + PLDHashTable mTagTable; + PLDHashTable mNameSpaceTable; + RuleValueList mUniversalRules; + + struct EnumData { + const RuleValue* mCurValue; + const RuleValue* mEnd; + }; + EnumData* mEnumList; + int32_t mEnumListSize; + + bool mQuirksMode; + + inline EnumData ToEnumData(const RuleValueList& arr) { + EnumData data = { arr.Elements(), arr.Elements() + arr.Length() }; + return data; + } + +#ifdef RULE_HASH_STATS + uint32_t mUniversalSelectors; + uint32_t mNameSpaceSelectors; + uint32_t mTagSelectors; + uint32_t mClassSelectors; + uint32_t mIdSelectors; + + uint32_t mElementsMatched; + + uint32_t mElementUniversalCalls; + uint32_t mElementNameSpaceCalls; + uint32_t mElementTagCalls; + uint32_t mElementClassCalls; + uint32_t mElementIdCalls; +#endif // RULE_HASH_STATS +}; + +RuleHash::RuleHash(bool aQuirksMode) + : mRuleCount(0), + mUniversalRules(0), + mEnumList(nullptr), mEnumListSize(0), + mQuirksMode(aQuirksMode) +#ifdef RULE_HASH_STATS + , + mUniversalSelectors(0), + mNameSpaceSelectors(0), + mTagSelectors(0), + mClassSelectors(0), + mIdSelectors(0), + mElementsMatched(0), + mElementUniversalCalls(0), + mElementNameSpaceCalls(0), + mElementTagCalls(0), + mElementClassCalls(0), + mElementIdCalls(0) +#endif +{ + MOZ_COUNT_CTOR(RuleHash); + + mTagTable.ops = nullptr; + mIdTable.ops = nullptr; + mClassTable.ops = nullptr; + mNameSpaceTable.ops = nullptr; +} + +RuleHash::~RuleHash() +{ + MOZ_COUNT_DTOR(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)\n" +" Element Calls: Universal(%u) NameSpace(%u) Tag(%u) Class(%u) Id(%u)\n" + static_cast(this), + mUniversalSelectors, mNameSpaceSelectors, mTagSelectors, + mClassSelectors, mIdSelectors, + mElementsMatched, + mElementUniversalCalls, mElementNameSpaceCalls, mElementTagCalls, + mElementClassCalls, mElementIdCalls); +#ifdef PRINT_UNIVERSAL_RULES + { + if (mUniversalRules.Length() > 0) { + printf(" Universal rules:\n"); + for (uint32_t i = 0; i < mUniversalRules.Length(); ++i) { + RuleValue* value = &(mUniversalRules[i]); + nsAutoString selectorText; + uint32_t lineNumber = value->mRule->GetLineNumber(); + nsCOMPtr sheet; + value->mRule->GetStyleSheet(*getter_AddRefs(sheet)); + nsRefPtr cssSheet = do_QueryObject(sheet); + value->mSelector->ToString(selectorText, cssSheet); + + printf(" line %d, %s\n", + lineNumber, NS_ConvertUTF16toUTF8(selectorText).get()); + } + } + } +#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 (nullptr != mEnumList) { + delete [] mEnumList; + } + // delete arena for strings and small objects + if (mIdTable.ops) { + PL_DHashTableFinish(&mIdTable); + } + if (mClassTable.ops) { + PL_DHashTableFinish(&mClassTable); + } + if (mTagTable.ops) { + PL_DHashTableFinish(&mTagTable); + } + if (mNameSpaceTable.ops) { + PL_DHashTableFinish(&mNameSpaceTable); + } +} + +void RuleHash::AppendRuleToTable(PLDHashTable* aTable, const void* aKey, + const RuleSelectorPair& aRuleInfo) +{ + // Get a new or existing entry. + RuleHashTableEntry *entry = static_cast + (PL_DHashTableOperate(aTable, aKey, PL_DHASH_ADD)); + if (!entry) + return; + entry->mRules.AppendElement(RuleValue(aRuleInfo, mRuleCount++, mQuirksMode)); +} + +static void +AppendRuleToTagTable(PLDHashTable* aTable, nsIAtom* aKey, + const RuleValue& aRuleInfo) +{ + // Get a new or exisiting entry + RuleHashTagTableEntry *entry = static_cast + (PL_DHashTableOperate(aTable, aKey, PL_DHASH_ADD)); + if (!entry) + return; + + entry->mRules.AppendElement(aRuleInfo); +} + +void RuleHash::AppendUniversalRule(const RuleSelectorPair& aRuleInfo) +{ + mUniversalRules.AppendElement(RuleValue(aRuleInfo, mRuleCount++, mQuirksMode)); +} + +void RuleHash::AppendRule(const RuleSelectorPair& aRuleInfo) +{ + nsCSSSelector *selector = aRuleInfo.mSelector; + if (selector->IsPseudoElement()) { + selector = selector->mNext; + } + if (nullptr != selector->mIDList) { + if (!mIdTable.ops) { + PL_DHashTableInit(&mIdTable, + mQuirksMode ? &RuleHash_IdTable_CIOps.ops + : &RuleHash_IdTable_CSOps.ops, + nullptr, sizeof(RuleHashTableEntry), 16); + } + AppendRuleToTable(&mIdTable, selector->mIDList->mAtom, aRuleInfo); + RULE_HASH_STAT_INCREMENT(mIdSelectors); + } + else if (nullptr != selector->mClassList) { + if (!mClassTable.ops) { + PL_DHashTableInit(&mClassTable, + mQuirksMode ? &RuleHash_ClassTable_CIOps.ops + : &RuleHash_ClassTable_CSOps.ops, + nullptr, sizeof(RuleHashTableEntry), 16); + } + AppendRuleToTable(&mClassTable, selector->mClassList->mAtom, aRuleInfo); + RULE_HASH_STAT_INCREMENT(mClassSelectors); + } + else if (selector->mLowercaseTag) { + RuleValue ruleValue(aRuleInfo, mRuleCount++, mQuirksMode); + if (!mTagTable.ops) { + PL_DHashTableInit(&mTagTable, &RuleHash_TagTable_Ops, nullptr, + sizeof(RuleHashTagTableEntry), 16); + } + AppendRuleToTagTable(&mTagTable, selector->mLowercaseTag, ruleValue); + RULE_HASH_STAT_INCREMENT(mTagSelectors); + if (selector->mCasedTag && + selector->mCasedTag != selector->mLowercaseTag) { + AppendRuleToTagTable(&mTagTable, selector->mCasedTag, ruleValue); + RULE_HASH_STAT_INCREMENT(mTagSelectors); + } + } + else if (kNameSpaceID_Unknown != selector->mNameSpace) { + if (!mNameSpaceTable.ops) { + PL_DHashTableInit(&mNameSpaceTable, &RuleHash_NameSpaceTable_Ops, nullptr, + sizeof(RuleHashTableEntry), 16); + } + AppendRuleToTable(&mNameSpaceTable, + NS_INT32_TO_PTR(selector->mNameSpace), aRuleInfo); + RULE_HASH_STAT_INCREMENT(mNameSpaceSelectors); + } + else { // universal tag selector + AppendUniversalRule(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_) \ + (var_) += (list_).Length() +#else +#define RULE_HASH_STAT_INCREMENT_LIST_COUNT(list_, var_) \ + PR_BEGIN_MACRO PR_END_MACRO +#endif + +static inline +void ContentEnumFunc(const RuleValue &value, nsCSSSelector* selector, + ElementDependentRuleProcessorData* data, NodeMatchContext& nodeContext, + AncestorFilter *ancestorFilter); + +void RuleHash::EnumerateAllRules(Element* aElement, ElementDependentRuleProcessorData* aData, + NodeMatchContext& aNodeContext) +{ + int32_t nameSpace = aElement->GetNameSpaceID(); + nsIAtom* tag = aElement->Tag(); + nsIAtom* id = aElement->GetID(); + const nsAttrValue* classList = aElement->GetClasses(); + + NS_ABORT_IF_FALSE(tag, "How could we not have a tag?"); + + int32_t classCount = classList ? classList->GetAtomCount() : 0; + + // assume 1 universal, tag, id, and namespace, rather than wasting + // time counting + int32_t testCount = classCount + 4; + + if (mEnumListSize < testCount) { + delete [] mEnumList; + mEnumListSize = std::max(testCount, MIN_ENUM_LIST_SIZE); + mEnumList = new EnumData[mEnumListSize]; + } + + int32_t valueCount = 0; + RULE_HASH_STAT_INCREMENT(mElementsMatched); + + if (mUniversalRules.Length() != 0) { // universal rules + mEnumList[valueCount++] = ToEnumData(mUniversalRules); + RULE_HASH_STAT_INCREMENT_LIST_COUNT(mUniversalRules, mElementUniversalCalls); + } + // universal rules within the namespace + if (kNameSpaceID_Unknown != nameSpace && mNameSpaceTable.ops) { + RuleHashTableEntry *entry = static_cast + (PL_DHashTableOperate(&mNameSpaceTable, NS_INT32_TO_PTR(nameSpace), + PL_DHASH_LOOKUP)); + if (PL_DHASH_ENTRY_IS_BUSY(entry)) { + mEnumList[valueCount++] = ToEnumData(entry->mRules); + RULE_HASH_STAT_INCREMENT_LIST_COUNT(entry->mRules, mElementNameSpaceCalls); + } + } + if (mTagTable.ops) { + RuleHashTableEntry *entry = static_cast + (PL_DHashTableOperate(&mTagTable, tag, PL_DHASH_LOOKUP)); + if (PL_DHASH_ENTRY_IS_BUSY(entry)) { + mEnumList[valueCount++] = ToEnumData(entry->mRules); + RULE_HASH_STAT_INCREMENT_LIST_COUNT(entry->mRules, mElementTagCalls); + } + } + if (id && mIdTable.ops) { + RuleHashTableEntry *entry = static_cast + (PL_DHashTableOperate(&mIdTable, id, PL_DHASH_LOOKUP)); + if (PL_DHASH_ENTRY_IS_BUSY(entry)) { + mEnumList[valueCount++] = ToEnumData(entry->mRules); + RULE_HASH_STAT_INCREMENT_LIST_COUNT(entry->mRules, mElementIdCalls); + } + } + if (mClassTable.ops) { + for (int32_t index = 0; index < classCount; ++index) { + RuleHashTableEntry *entry = static_cast + (PL_DHashTableOperate(&mClassTable, classList->AtomAt(index), + PL_DHASH_LOOKUP)); + if (PL_DHASH_ENTRY_IS_BUSY(entry)) { + mEnumList[valueCount++] = ToEnumData(entry->mRules); + RULE_HASH_STAT_INCREMENT_LIST_COUNT(entry->mRules, mElementClassCalls); + } + } + } + NS_ASSERTION(valueCount <= testCount, "values exceeded list size"); + + if (valueCount > 0) { + AncestorFilter *filter = + aData->mTreeMatchContext.mAncestorFilter.HasFilter() ? + &aData->mTreeMatchContext.mAncestorFilter : nullptr; +#ifdef DEBUG + if (filter) { + filter->AssertHasAllAncestors(aElement); + } +#endif + // Merge the lists while there are still multiple lists to merge. + while (valueCount > 1) { + int32_t valueIndex = 0; + int32_t lowestRuleIndex = mEnumList[valueIndex].mCurValue->mIndex; + for (int32_t index = 1; index < valueCount; ++index) { + int32_t ruleIndex = mEnumList[index].mCurValue->mIndex; + if (ruleIndex < lowestRuleIndex) { + valueIndex = index; + lowestRuleIndex = ruleIndex; + } + } + const RuleValue *cur = mEnumList[valueIndex].mCurValue; + ContentEnumFunc(*cur, cur->mSelector, aData, aNodeContext, filter); + cur++; + if (cur == mEnumList[valueIndex].mEnd) { + mEnumList[valueIndex] = mEnumList[--valueCount]; + } else { + mEnumList[valueIndex].mCurValue = cur; + } + } + + // Fast loop over single value. + for (const RuleValue *value = mEnumList[0].mCurValue, + *end = mEnumList[0].mEnd; + value != end; ++value) { + ContentEnumFunc(*value, value->mSelector, aData, aNodeContext, filter); + } + } +} + +static size_t +SizeOfRuleHashTableEntry(PLDHashEntryHdr* aHdr, MallocSizeOf aMallocSizeOf, void *) +{ + RuleHashTableEntry* entry = static_cast(aHdr); + return entry->mRules.SizeOfExcludingThis(aMallocSizeOf); +} + +size_t +RuleHash::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const +{ + size_t n = 0; + + if (mIdTable.ops) { + n += PL_DHashTableSizeOfExcludingThis(&mIdTable, + SizeOfRuleHashTableEntry, + aMallocSizeOf); + } + + if (mClassTable.ops) { + n += PL_DHashTableSizeOfExcludingThis(&mClassTable, + SizeOfRuleHashTableEntry, + aMallocSizeOf); + } + + if (mTagTable.ops) { + n += PL_DHashTableSizeOfExcludingThis(&mTagTable, + SizeOfRuleHashTableEntry, + aMallocSizeOf); + } + + if (mNameSpaceTable.ops) { + n += PL_DHashTableSizeOfExcludingThis(&mNameSpaceTable, + SizeOfRuleHashTableEntry, + aMallocSizeOf); + } + + n += mUniversalRules.SizeOfExcludingThis(aMallocSizeOf); + + return n; +} + +size_t +RuleHash::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const +{ + return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf); +} + +//-------------------------------- + +// A hash table mapping atoms to lists of selectors +struct AtomSelectorEntry : public PLDHashEntryHdr { + nsIAtom *mAtom; + // Auto length 2, because a decent fraction of these arrays ends up + // with 2 elements, and each entry is cheap. + nsAutoTArray mSelectors; +}; + +static void +AtomSelector_ClearEntry(PLDHashTable *table, PLDHashEntryHdr *hdr) +{ + (static_cast(hdr))->~AtomSelectorEntry(); +} + +static bool +AtomSelector_InitEntry(PLDHashTable *table, PLDHashEntryHdr *hdr, + const void *key) +{ + AtomSelectorEntry *entry = static_cast(hdr); + new (entry) AtomSelectorEntry(); + entry->mAtom = const_cast(static_cast(key)); + return true; +} + +static void +AtomSelector_MoveEntry(PLDHashTable *table, const PLDHashEntryHdr *from, + PLDHashEntryHdr *to) +{ + NS_PRECONDITION(from != to, "This is not going to work!"); + AtomSelectorEntry *oldEntry = + const_cast(static_cast(from)); + AtomSelectorEntry *newEntry = new (to) AtomSelectorEntry(); + newEntry->mAtom = oldEntry->mAtom; + newEntry->mSelectors.SwapElements(oldEntry->mSelectors); + oldEntry->~AtomSelectorEntry(); +} + +static nsIAtom* +AtomSelector_GetKey(PLDHashTable *table, const PLDHashEntryHdr *hdr) +{ + const AtomSelectorEntry *entry = static_cast(hdr); + return entry->mAtom; +} + +// Case-sensitive ops. +static const PLDHashTableOps AtomSelector_CSOps = { + PL_DHashAllocTable, + PL_DHashFreeTable, + PL_DHashVoidPtrKeyStub, + PL_DHashMatchEntryStub, + AtomSelector_MoveEntry, + AtomSelector_ClearEntry, + PL_DHashFinalizeStub, + AtomSelector_InitEntry +}; + +// Case-insensitive ops. +static const RuleHashTableOps AtomSelector_CIOps = { + { + PL_DHashAllocTable, + PL_DHashFreeTable, + RuleHash_CIHashKey, + RuleHash_CIMatchEntry, + AtomSelector_MoveEntry, + AtomSelector_ClearEntry, + PL_DHashFinalizeStub, + AtomSelector_InitEntry + }, + AtomSelector_GetKey +}; + +//-------------------------------- + +struct RuleCascadeData { + RuleCascadeData(nsIAtom *aMedium, bool aQuirksMode) + : mRuleHash(aQuirksMode), + mStateSelectors(), + mSelectorDocumentStates(0), + mKeyframesRuleTable(16), + mCacheKey(aMedium), + mNext(nullptr), + mQuirksMode(aQuirksMode) + { + // mAttributeSelectors is matching on the attribute _name_, not the value, + // and we case-fold names at parse-time, so this is a case-sensitive match. + PL_DHashTableInit(&mAttributeSelectors, &AtomSelector_CSOps, nullptr, + sizeof(AtomSelectorEntry), 16); + PL_DHashTableInit(&mAnonBoxRules, &RuleHash_TagTable_Ops, nullptr, + sizeof(RuleHashTagTableEntry), 16); + PL_DHashTableInit(&mIdSelectors, + aQuirksMode ? &AtomSelector_CIOps.ops : + &AtomSelector_CSOps, + nullptr, sizeof(AtomSelectorEntry), 16); + PL_DHashTableInit(&mClassSelectors, + aQuirksMode ? &AtomSelector_CIOps.ops : + &AtomSelector_CSOps, + nullptr, sizeof(AtomSelectorEntry), 16); + memset(mPseudoElementRuleHashes, 0, sizeof(mPseudoElementRuleHashes)); +#ifdef MOZ_XUL + PL_DHashTableInit(&mXULTreeRules, &RuleHash_TagTable_Ops, nullptr, + sizeof(RuleHashTagTableEntry), 16); +#endif + } + + ~RuleCascadeData() + { + PL_DHashTableFinish(&mAttributeSelectors); + PL_DHashTableFinish(&mAnonBoxRules); + PL_DHashTableFinish(&mIdSelectors); + PL_DHashTableFinish(&mClassSelectors); +#ifdef MOZ_XUL + PL_DHashTableFinish(&mXULTreeRules); +#endif + for (uint32_t i = 0; i < ArrayLength(mPseudoElementRuleHashes); ++i) { + delete mPseudoElementRuleHashes[i]; + } + } + + size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const; + + RuleHash mRuleHash; + RuleHash* + mPseudoElementRuleHashes[nsCSSPseudoElements::ePseudo_PseudoElementCount]; + nsTArray mStateSelectors; + EventStates mSelectorDocumentStates; + PLDHashTable mClassSelectors; + PLDHashTable mIdSelectors; + nsTArray mPossiblyNegatedClassSelectors; + nsTArray mPossiblyNegatedIDSelectors; + PLDHashTable mAttributeSelectors; + PLDHashTable mAnonBoxRules; +#ifdef MOZ_XUL + PLDHashTable mXULTreeRules; +#endif + + nsTArray mFontFaceRules; + nsTArray mKeyframesRules; + nsTArray mFontFeatureValuesRules; + nsTArray mPageRules; + + nsDataHashtable mKeyframesRuleTable; + + // Looks up or creates the appropriate list in |mAttributeSelectors|. + // Returns null only on allocation failure. + nsTArray* AttributeListFor(nsIAtom* aAttribute); + + nsMediaQueryResultCacheKey mCacheKey; + RuleCascadeData* mNext; // for a different medium + + const bool mQuirksMode; +}; + +static size_t +SizeOfSelectorsEntry(PLDHashEntryHdr* aHdr, MallocSizeOf aMallocSizeOf, void *) +{ + AtomSelectorEntry* entry = static_cast(aHdr); + return entry->mSelectors.SizeOfExcludingThis(aMallocSizeOf); +} + +size_t +RuleCascadeData::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const +{ + size_t n = aMallocSizeOf(this); + + n += mRuleHash.SizeOfExcludingThis(aMallocSizeOf); + for (uint32_t i = 0; i < ArrayLength(mPseudoElementRuleHashes); ++i) { + if (mPseudoElementRuleHashes[i]) + n += mPseudoElementRuleHashes[i]->SizeOfIncludingThis(aMallocSizeOf); + } + + n += mStateSelectors.SizeOfExcludingThis(aMallocSizeOf); + + n += PL_DHashTableSizeOfExcludingThis(&mIdSelectors, + SizeOfSelectorsEntry, aMallocSizeOf); + n += PL_DHashTableSizeOfExcludingThis(&mClassSelectors, + SizeOfSelectorsEntry, aMallocSizeOf); + + n += mPossiblyNegatedClassSelectors.SizeOfExcludingThis(aMallocSizeOf); + n += mPossiblyNegatedIDSelectors.SizeOfExcludingThis(aMallocSizeOf); + + n += PL_DHashTableSizeOfExcludingThis(&mAttributeSelectors, + SizeOfSelectorsEntry, aMallocSizeOf); + n += PL_DHashTableSizeOfExcludingThis(&mAnonBoxRules, + SizeOfRuleHashTableEntry, aMallocSizeOf); +#ifdef MOZ_XUL + n += PL_DHashTableSizeOfExcludingThis(&mXULTreeRules, + SizeOfRuleHashTableEntry, aMallocSizeOf); +#endif + + n += mFontFaceRules.SizeOfExcludingThis(aMallocSizeOf); + n += mKeyframesRules.SizeOfExcludingThis(aMallocSizeOf); + n += mFontFeatureValuesRules.SizeOfExcludingThis(aMallocSizeOf); + n += mPageRules.SizeOfExcludingThis(aMallocSizeOf); + + return n; +} + +nsTArray* +RuleCascadeData::AttributeListFor(nsIAtom* aAttribute) +{ + AtomSelectorEntry *entry = + static_cast + (PL_DHashTableOperate(&mAttributeSelectors, aAttribute, + PL_DHASH_ADD)); + if (!entry) + return nullptr; + return &entry->mSelectors; +} + +// ------------------------------- +// CSS Style rule processor implementation +// + +nsCSSRuleProcessor::nsCSSRuleProcessor(const sheet_array_type& aSheets, + uint8_t aSheetType, + Element* aScopeElement) + : mSheets(aSheets) + , mRuleCascades(nullptr) + , mLastPresContext(nullptr) + , mScopeElement(aScopeElement) + , mSheetType(aSheetType) +{ + NS_ASSERTION(!!mScopeElement == (aSheetType == nsStyleSet::eScopedDocSheet), + "aScopeElement must be specified iff aSheetType is " + "eScopedDocSheet"); + for (sheet_array_type::size_type i = mSheets.Length(); i-- != 0; ) { + mSheets[i]->AddRuleProcessor(this); + } +} + +nsCSSRuleProcessor::~nsCSSRuleProcessor() +{ + for (sheet_array_type::size_type i = mSheets.Length(); i-- != 0; ) { + mSheets[i]->DropRuleProcessor(this); + } + mSheets.Clear(); + ClearRuleCascades(); +} + +NS_IMPL_ISUPPORTS(nsCSSRuleProcessor, nsIStyleRuleProcessor) + +/* static */ nsresult +nsCSSRuleProcessor::Startup() +{ + Preferences::AddBoolVarCache(&gSupportVisitedPseudo, VISITED_PSEUDO_PREF, + true); + + return NS_OK; +} + +static bool +InitSystemMetrics() +{ + NS_ASSERTION(!sSystemMetrics, "already initialized"); + + sSystemMetrics = new nsTArray< nsCOMPtr >; + NS_ENSURE_TRUE(sSystemMetrics, false); + + /*************************************************************************** + * ANY METRICS ADDED HERE SHOULD ALSO BE ADDED AS MEDIA QUERIES IN * + * nsMediaFeatures.cpp * + ***************************************************************************/ + + int32_t metricResult = + LookAndFeel::GetInt(LookAndFeel::eIntID_ScrollArrowStyle); + if (metricResult & LookAndFeel::eScrollArrow_StartBackward) { + sSystemMetrics->AppendElement(nsGkAtoms::scrollbar_start_backward); + } + if (metricResult & LookAndFeel::eScrollArrow_StartForward) { + sSystemMetrics->AppendElement(nsGkAtoms::scrollbar_start_forward); + } + if (metricResult & LookAndFeel::eScrollArrow_EndBackward) { + sSystemMetrics->AppendElement(nsGkAtoms::scrollbar_end_backward); + } + if (metricResult & LookAndFeel::eScrollArrow_EndForward) { + sSystemMetrics->AppendElement(nsGkAtoms::scrollbar_end_forward); + } + + metricResult = + LookAndFeel::GetInt(LookAndFeel::eIntID_ScrollSliderStyle); + if (metricResult != LookAndFeel::eScrollThumbStyle_Normal) { + sSystemMetrics->AppendElement(nsGkAtoms::scrollbar_thumb_proportional); + } + + metricResult = + LookAndFeel::GetInt(LookAndFeel::eIntID_ImagesInMenus); + if (metricResult) { + sSystemMetrics->AppendElement(nsGkAtoms::images_in_menus); + } + + metricResult = + LookAndFeel::GetInt(LookAndFeel::eIntID_ImagesInButtons); + if (metricResult) { + sSystemMetrics->AppendElement(nsGkAtoms::images_in_buttons); + } + + metricResult = + LookAndFeel::GetInt(LookAndFeel::eIntID_UseOverlayScrollbars); + if (metricResult) { + sSystemMetrics->AppendElement(nsGkAtoms::overlay_scrollbars); + } + + metricResult = + LookAndFeel::GetInt(LookAndFeel::eIntID_MenuBarDrag); + if (metricResult) { + sSystemMetrics->AppendElement(nsGkAtoms::menubar_drag); + } + + nsresult rv = + LookAndFeel::GetInt(LookAndFeel::eIntID_WindowsDefaultTheme, &metricResult); + if (NS_SUCCEEDED(rv) && metricResult) { + sSystemMetrics->AppendElement(nsGkAtoms::windows_default_theme); + } + + rv = LookAndFeel::GetInt(LookAndFeel::eIntID_MacGraphiteTheme, &metricResult); + if (NS_SUCCEEDED(rv) && metricResult) { + sSystemMetrics->AppendElement(nsGkAtoms::mac_graphite_theme); + } + + rv = LookAndFeel::GetInt(LookAndFeel::eIntID_MacLionTheme, &metricResult); + if (NS_SUCCEEDED(rv) && metricResult) { + sSystemMetrics->AppendElement(nsGkAtoms::mac_lion_theme); + } + + rv = LookAndFeel::GetInt(LookAndFeel::eIntID_DWMCompositor, &metricResult); + if (NS_SUCCEEDED(rv) && metricResult) { + sSystemMetrics->AppendElement(nsGkAtoms::windows_compositor); + } + + rv = LookAndFeel::GetInt(LookAndFeel::eIntID_WindowsGlass, &metricResult); + if (NS_SUCCEEDED(rv) && metricResult) { + sSystemMetrics->AppendElement(nsGkAtoms::windows_glass); + } + + rv = LookAndFeel::GetInt(LookAndFeel::eIntID_ColorPickerAvailable, &metricResult); + if (NS_SUCCEEDED(rv) && metricResult) { + sSystemMetrics->AppendElement(nsGkAtoms::color_picker_available); + } + + rv = LookAndFeel::GetInt(LookAndFeel::eIntID_WindowsClassic, &metricResult); + if (NS_SUCCEEDED(rv) && metricResult) { + sSystemMetrics->AppendElement(nsGkAtoms::windows_classic); + } + + rv = LookAndFeel::GetInt(LookAndFeel::eIntID_TouchEnabled, &metricResult); + if (NS_SUCCEEDED(rv) && metricResult) { + sSystemMetrics->AppendElement(nsGkAtoms::touch_enabled); + } + + rv = LookAndFeel::GetInt(LookAndFeel::eIntID_SwipeAnimationEnabled, + &metricResult); + if (NS_SUCCEEDED(rv) && metricResult) { + sSystemMetrics->AppendElement(nsGkAtoms::swipe_animation_enabled); + } + + rv = LookAndFeel::GetInt(LookAndFeel::eIntID_PhysicalHomeButton, + &metricResult); + if (NS_SUCCEEDED(rv) && metricResult) { + sSystemMetrics->AppendElement(nsGkAtoms::physical_home_button); + } + +#ifdef XP_WIN + if (NS_SUCCEEDED( + LookAndFeel::GetInt(LookAndFeel::eIntID_WindowsThemeIdentifier, + &metricResult))) { + nsCSSRuleProcessor::SetWindowsThemeIdentifier(static_cast(metricResult)); + switch(metricResult) { + case LookAndFeel::eWindowsTheme_Aero: + sSystemMetrics->AppendElement(nsGkAtoms::windows_theme_aero); + break; + case LookAndFeel::eWindowsTheme_AeroLite: + sSystemMetrics->AppendElement(nsGkAtoms::windows_theme_aero_lite); + break; + case LookAndFeel::eWindowsTheme_LunaBlue: + sSystemMetrics->AppendElement(nsGkAtoms::windows_theme_luna_blue); + break; + case LookAndFeel::eWindowsTheme_LunaOlive: + sSystemMetrics->AppendElement(nsGkAtoms::windows_theme_luna_olive); + break; + case LookAndFeel::eWindowsTheme_LunaSilver: + sSystemMetrics->AppendElement(nsGkAtoms::windows_theme_luna_silver); + break; + case LookAndFeel::eWindowsTheme_Royale: + sSystemMetrics->AppendElement(nsGkAtoms::windows_theme_royale); + break; + case LookAndFeel::eWindowsTheme_Zune: + sSystemMetrics->AppendElement(nsGkAtoms::windows_theme_zune); + break; + case LookAndFeel::eWindowsTheme_Generic: + sSystemMetrics->AppendElement(nsGkAtoms::windows_theme_generic); + break; + } + } +#endif + + return true; +} + +/* static */ void +nsCSSRuleProcessor::FreeSystemMetrics() +{ + delete sSystemMetrics; + sSystemMetrics = nullptr; +} + +/* static */ void +nsCSSRuleProcessor::Shutdown() +{ + FreeSystemMetrics(); +} + +/* static */ bool +nsCSSRuleProcessor::HasSystemMetric(nsIAtom* aMetric) +{ + if (!sSystemMetrics && !InitSystemMetrics()) { + return false; + } + return sSystemMetrics->IndexOf(aMetric) != sSystemMetrics->NoIndex; +} + +#ifdef XP_WIN +/* static */ uint8_t +nsCSSRuleProcessor::GetWindowsThemeIdentifier() +{ + if (!sSystemMetrics) + InitSystemMetrics(); + return sWinThemeId; +} +#endif + +/* static */ +EventStates +nsCSSRuleProcessor::GetContentState(Element* aElement, const TreeMatchContext& aTreeMatchContext) +{ + EventStates state = aElement->StyleState(); + + // If we are not supposed to mark visited links as such, be sure to + // flip the bits appropriately. We want to do this here, rather + // than in GetContentStateForVisitedHandling, so that we don't + // expose that :visited support is disabled to the Web page. + if (state.HasState(NS_EVENT_STATE_VISITED) && + (!gSupportVisitedPseudo || + aElement->OwnerDoc()->IsBeingUsedAsImage() || + aTreeMatchContext.mUsingPrivateBrowsing)) { + state &= ~NS_EVENT_STATE_VISITED; + state |= NS_EVENT_STATE_UNVISITED; + } + return state; +} + +/* static */ +bool +nsCSSRuleProcessor::IsLink(Element* aElement) +{ + EventStates state = aElement->StyleState(); + return state.HasAtLeastOneOfStates(NS_EVENT_STATE_VISITED | NS_EVENT_STATE_UNVISITED); +} + +/* static */ +EventStates +nsCSSRuleProcessor::GetContentStateForVisitedHandling( + Element* aElement, + const TreeMatchContext& aTreeMatchContext, + nsRuleWalker::VisitedHandlingType aVisitedHandling, + bool aIsRelevantLink) +{ + EventStates contentState = GetContentState(aElement, aTreeMatchContext); + if (contentState.HasAtLeastOneOfStates(NS_EVENT_STATE_VISITED | NS_EVENT_STATE_UNVISITED)) { + NS_ABORT_IF_FALSE(IsLink(aElement), "IsLink() should match state"); + contentState &= ~(NS_EVENT_STATE_VISITED | NS_EVENT_STATE_UNVISITED); + if (aIsRelevantLink) { + switch (aVisitedHandling) { + case nsRuleWalker::eRelevantLinkUnvisited: + contentState |= NS_EVENT_STATE_UNVISITED; + break; + case nsRuleWalker::eRelevantLinkVisited: + contentState |= NS_EVENT_STATE_VISITED; + break; + case nsRuleWalker::eLinksVisitedOrUnvisited: + contentState |= NS_EVENT_STATE_UNVISITED | NS_EVENT_STATE_VISITED; + break; + } + } else { + contentState |= NS_EVENT_STATE_UNVISITED; + } + } + return contentState; +} + +/** + * A |NodeMatchContext| has data about matching a selector (without + * combinators) against a single node. It contains only input to the + * matching. + * + * Unlike |RuleProcessorData|, which is similar, a |NodeMatchContext| + * can vary depending on the selector matching process. In other words, + * there might be multiple NodeMatchContexts corresponding to a single + * node, but only one possible RuleProcessorData. + */ +struct NodeMatchContext { + // In order to implement nsCSSRuleProcessor::HasStateDependentStyle, + // we need to be able to see if a node might match an + // event-state-dependent selector for any value of that event state. + // So mStateMask contains the states that should NOT be tested. + // + // NOTE: For |mStateMask| to work correctly, it's important that any + // change that changes multiple state bits include all those state + // bits in the notification. Otherwise, if multiple states change but + // we do 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). + const EventStates mStateMask; + + // Is this link the unique link whose visitedness can affect the style + // of the node being matched? (That link is the nearest link to the + // node being matched that is itself or an ancestor.) + // + // Always false when TreeMatchContext::mForStyling is false. (We + // could figure it out for SelectorListMatches, but we're starting + // from the middle of the selector list when doing + // Has{Attribute,State}DependentStyle, so we can't tell. So when + // mForStyling is false, we have to assume we don't know.) + const bool mIsRelevantLink; + + NodeMatchContext(EventStates aStateMask, bool aIsRelevantLink) + : mStateMask(aStateMask) + , mIsRelevantLink(aIsRelevantLink) + { + } +}; + +static bool ValueIncludes(const nsSubstring& aValueList, + const nsSubstring& aValue, + const nsStringComparator& aComparator) +{ + const char16_t *p = aValueList.BeginReading(), + *p_end = aValueList.EndReading(); + + while (p < p_end) { + // skip leading space + while (p != p_end && nsContentUtils::IsHTMLWhitespace(*p)) + ++p; + + const char16_t *val_start = p; + + // look for space or end + while (p != p_end && !nsContentUtils::IsHTMLWhitespace(*p)) + ++p; + + const char16_t *val_end = p; + + if (val_start < val_end && + aValue.Equals(Substring(val_start, val_end), aComparator)) + return true; + + ++p; // we know the next character is not whitespace + } + return false; +} + +// Return whether we should apply a "global" (i.e., universal-tag) +// selector for event states in quirks mode. Note that +// |IsLink()| is checked separately by the caller, so we return +// false for |nsGkAtoms::a|, which here means a named anchor. +inline bool IsQuirkEventSensitive(nsIAtom *aContentTag) +{ + return bool ((nsGkAtoms::button == aContentTag) || + (nsGkAtoms::img == aContentTag) || + (nsGkAtoms::input == aContentTag) || + (nsGkAtoms::label == aContentTag) || + (nsGkAtoms::select == aContentTag) || + (nsGkAtoms::textarea == aContentTag)); +} + + +static inline bool +IsSignificantChild(nsIContent* aChild, bool aTextIsSignificant, + bool aWhitespaceIsSignificant) +{ + return nsStyleUtil::IsSignificantChild(aChild, aTextIsSignificant, + aWhitespaceIsSignificant); +} + +// 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 bool AttrMatchesValue(const nsAttrSelector* aAttrSelector, + const nsString& aValue, bool isHTML) +{ + NS_PRECONDITION(aAttrSelector, "Must have an attribute selector"); + + // http://lists.w3.org/Archives/Public/www-style/2008Apr/0038.html + // *= (CONTAINSMATCH) ~= (INCLUDES) ^= (BEGINSMATCH) $= (ENDSMATCH) + // all accept the empty string, but match nothing. + if (aAttrSelector->mValue.IsEmpty() && + (aAttrSelector->mFunction == NS_ATTR_FUNC_INCLUDES || + aAttrSelector->mFunction == NS_ATTR_FUNC_ENDSMATCH || + aAttrSelector->mFunction == NS_ATTR_FUNC_BEGINSMATCH || + aAttrSelector->mFunction == NS_ATTR_FUNC_CONTAINSMATCH)) + return false; + + const nsDefaultStringComparator defaultComparator; + const nsASCIICaseInsensitiveStringComparator ciComparator; + const nsStringComparator& comparator = + (aAttrSelector->mCaseSensitive || !isHTML) + ? static_cast(defaultComparator) + : static_cast(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 false; + } +} + +static inline bool +edgeChildMatches(Element* aElement, TreeMatchContext& aTreeMatchContext, + bool checkFirst, bool checkLast) +{ + nsIContent *parent = aElement->GetParent(); + if (!parent) { + return false; + } + + if (aTreeMatchContext.mForStyling) + parent->SetFlags(NODE_HAS_EDGE_CHILD_SELECTOR); + + return (!checkFirst || + aTreeMatchContext.mNthIndexCache. + GetNthIndex(aElement, false, false, true) == 1) && + (!checkLast || + aTreeMatchContext.mNthIndexCache. + GetNthIndex(aElement, false, true, true) == 1); +} + +static inline bool +nthChildGenericMatches(Element* aElement, + TreeMatchContext& aTreeMatchContext, + nsPseudoClassList* pseudoClass, + bool isOfType, bool isFromEnd) +{ + nsIContent *parent = aElement->GetParent(); + if (!parent) { + return false; + } + + if (aTreeMatchContext.mForStyling) { + if (isFromEnd) + parent->SetFlags(NODE_HAS_SLOW_SELECTOR); + else + parent->SetFlags(NODE_HAS_SLOW_SELECTOR_LATER_SIBLINGS); + } + + const int32_t index = aTreeMatchContext.mNthIndexCache. + GetNthIndex(aElement, isOfType, isFromEnd, false); + if (index <= 0) { + // Node is anonymous content (not really a child of its parent). + return false; + } + + const int32_t a = pseudoClass->u.mNumbers[0]; + const int32_t b = pseudoClass->u.mNumbers[1]; + // result should be true if there exists n >= 0 such that + // a * n + b == index. + if (a == 0) { + return b == index; + } + + // Integer division in C does truncation (towards 0). So + // check that the result is nonnegative, and that there was no + // truncation. + const int32_t n = (index - b) / a; + return n >= 0 && (a * n == index - b); +} + +static inline bool +edgeOfTypeMatches(Element* aElement, TreeMatchContext& aTreeMatchContext, + bool checkFirst, bool checkLast) +{ + nsIContent *parent = aElement->GetParent(); + if (!parent) { + return false; + } + + if (aTreeMatchContext.mForStyling) { + if (checkLast) + parent->SetFlags(NODE_HAS_SLOW_SELECTOR); + else + parent->SetFlags(NODE_HAS_SLOW_SELECTOR_LATER_SIBLINGS); + } + + return (!checkFirst || + aTreeMatchContext.mNthIndexCache. + GetNthIndex(aElement, true, false, true) == 1) && + (!checkLast || + aTreeMatchContext.mNthIndexCache. + GetNthIndex(aElement, true, true, true) == 1); +} + +static inline bool +checkGenericEmptyMatches(Element* aElement, + TreeMatchContext& aTreeMatchContext, + bool isWhitespaceSignificant) +{ + nsIContent *child = nullptr; + int32_t index = -1; + + if (aTreeMatchContext.mForStyling) + aElement->SetFlags(NODE_HAS_EMPTY_SELECTOR); + + do { + child = aElement->GetChildAt(++index); + // stop at first non-comment (and non-whitespace for + // :-moz-only-whitespace) node + } while (child && !IsSignificantChild(child, true, isWhitespaceSignificant)); + return (child == nullptr); +} + +// Arrays of the states that are relevant for various pseudoclasses. +static const EventStates sPseudoClassStateDependences[] = { +#define CSS_PSEUDO_CLASS(_name, _value, _pref) \ + EventStates(), +#define CSS_STATE_DEPENDENT_PSEUDO_CLASS(_name, _value, _pref, _states) \ + _states, +#include "nsCSSPseudoClassList.h" +#undef CSS_STATE_DEPENDENT_PSEUDO_CLASS +#undef CSS_PSEUDO_CLASS + // Add more entries for our fake values to make sure we can't + // index out of bounds into this array no matter what. + EventStates(), + EventStates() +}; + +static const EventStates sPseudoClassStates[] = { +#define CSS_PSEUDO_CLASS(_name, _value, _pref) \ + EventStates(), +#define CSS_STATE_PSEUDO_CLASS(_name, _value, _pref, _states) \ + _states, +#include "nsCSSPseudoClassList.h" +#undef CSS_STATE_PSEUDO_CLASS +#undef CSS_PSEUDO_CLASS + // Add more entries for our fake values to make sure we can't + // index out of bounds into this array no matter what. + EventStates(), + EventStates() +}; +static_assert(MOZ_ARRAY_LENGTH(sPseudoClassStates) == + nsCSSPseudoClasses::ePseudoClass_NotPseudoClass + 1, + "ePseudoClass_NotPseudoClass is no longer at the end of" + "sPseudoClassStates"); + +static bool +StateSelectorMatches(Element* aElement, + nsCSSSelector* aSelector, + NodeMatchContext& aNodeMatchContext, + TreeMatchContext& aTreeMatchContext, + bool* const aDependence, + EventStates aStatesToCheck) +{ + NS_PRECONDITION(!aStatesToCheck.IsEmpty(), + "should only need to call StateSelectorMatches if " + "aStatesToCheck is not empty"); + + const bool isNegated = aDependence != nullptr; + + // Bit-based pseudo-classes + if (aStatesToCheck.HasAtLeastOneOfStates(NS_EVENT_STATE_HOVER | NS_EVENT_STATE_ACTIVE) && + aTreeMatchContext.mCompatMode == eCompatibility_NavQuirks && + // global selector: + !aSelector->HasTagSelector() && !aSelector->mIDList && + !aSelector->mClassList && !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. + !isNegated && + // important for |IsQuirkEventSensitive|: + aElement->IsHTML() && !nsCSSRuleProcessor::IsLink(aElement) && + !IsQuirkEventSensitive(aElement->Tag())) { + // In quirks mode, only make certain elements sensitive to + // selectors ":hover" and ":active". + return false; + } + + if (aTreeMatchContext.mForStyling && + aStatesToCheck.HasAtLeastOneOfStates(NS_EVENT_STATE_HOVER)) { + // Mark the element as having :hover-dependent style + aElement->SetHasRelevantHoverRules(); + } + + if (aNodeMatchContext.mStateMask.HasAtLeastOneOfStates(aStatesToCheck)) { + if (aDependence) { + *aDependence = true; + } + } else { + EventStates contentState = + nsCSSRuleProcessor::GetContentStateForVisitedHandling( + aElement, + aTreeMatchContext, + aTreeMatchContext.VisitedHandling(), + aNodeMatchContext.mIsRelevantLink); + if (!contentState.HasAtLeastOneOfStates(aStatesToCheck)) { + return false; + } + } + + return true; +} + +static bool +StateSelectorMatches(Element* aElement, + nsCSSSelector* aSelector, + NodeMatchContext& aNodeMatchContext, + TreeMatchContext& aTreeMatchContext) +{ + for (nsPseudoClassList* pseudoClass = aSelector->mPseudoClassList; + pseudoClass; pseudoClass = pseudoClass->mNext) { + EventStates statesToCheck = sPseudoClassStates[pseudoClass->mType]; + if (!statesToCheck.IsEmpty() && + !StateSelectorMatches(aElement, aSelector, aNodeMatchContext, + aTreeMatchContext, nullptr, statesToCheck)) { + return false; + } + } + return true; +} + +// |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 aNodeMatchContent.mStateMask +static bool SelectorMatches(Element* aElement, + nsCSSSelector* aSelector, + NodeMatchContext& aNodeMatchContext, + TreeMatchContext& aTreeMatchContext, + bool* const aDependence = nullptr) + +{ + NS_PRECONDITION(!aSelector->IsPseudoElement(), + "Pseudo-element snuck into SelectorMatches?"); + NS_ABORT_IF_FALSE(aTreeMatchContext.mForStyling || + !aNodeMatchContext.mIsRelevantLink, + "mIsRelevantLink should be set to false when mForStyling " + "is false since we don't know how to set it correctly in " + "Has(Attribute|State)DependentStyle"); + + // namespace/tag match + // optimization : bail out early if we can + if ((kNameSpaceID_Unknown != aSelector->mNameSpace && + aElement->GetNameSpaceID() != aSelector->mNameSpace)) + return false; + + if (aSelector->mLowercaseTag) { + nsIAtom* selectorTag = + (aTreeMatchContext.mIsHTMLDocument && aElement->IsHTML()) ? + aSelector->mLowercaseTag : aSelector->mCasedTag; + if (selectorTag != aElement->Tag()) { + return false; + } + } + + nsAtomList* IDList = aSelector->mIDList; + if (IDList) { + nsIAtom* id = aElement->GetID(); + if (id) { + // case sensitivity: bug 93371 + const bool isCaseSensitive = + aTreeMatchContext.mCompatMode != eCompatibility_NavQuirks; + + if (isCaseSensitive) { + do { + if (IDList->mAtom != id) { + return false; + } + IDList = IDList->mNext; + } while (IDList); + } else { + // Use EqualsIgnoreASCIICase instead of full on unicode case conversion + // in order to save on performance. This is only used in quirks mode + // anyway. + nsDependentAtomString id1Str(id); + do { + if (!nsContentUtils::EqualsIgnoreASCIICase(id1Str, + nsDependentAtomString(IDList->mAtom))) { + return false; + } + IDList = IDList->mNext; + } while (IDList); + } + } else { + // Element has no id but we have an id selector + return false; + } + } + + nsAtomList* classList = aSelector->mClassList; + if (classList) { + // test for class match + const nsAttrValue *elementClasses = aElement->GetClasses(); + if (!elementClasses) { + // Element has no classes but we have a class selector + return false; + } + + // case sensitivity: bug 93371 + const bool isCaseSensitive = + aTreeMatchContext.mCompatMode != eCompatibility_NavQuirks; + + while (classList) { + if (!elementClasses->Contains(classList->mAtom, + isCaseSensitive ? + eCaseMatters : eIgnoreCase)) { + return false; + } + classList = classList->mNext; + } + } + + const bool isNegated = (aDependence != nullptr); + // The selectors for which we set node bits are, unfortunately, early + // in this function (because they're pseudo-classes, which are + // generally quick to test, and thus earlier). If they were later, + // we'd probably avoid setting those bits in more cases where setting + // them is unnecessary. + NS_ASSERTION(aNodeMatchContext.mStateMask.IsEmpty() || + !aTreeMatchContext.mForStyling, + "mForStyling must be false if we're just testing for " + "state-dependence"); + + // test for pseudo class match + for (nsPseudoClassList* pseudoClass = aSelector->mPseudoClassList; + pseudoClass; pseudoClass = pseudoClass->mNext) { + EventStates statesToCheck = sPseudoClassStates[pseudoClass->mType]; + if (statesToCheck.IsEmpty()) { + // keep the cases here in the same order as the list in + // nsCSSPseudoClassList.h + switch (pseudoClass->mType) { + case nsCSSPseudoClasses::ePseudoClass_empty: + if (!checkGenericEmptyMatches(aElement, aTreeMatchContext, true)) { + return false; + } + break; + + case nsCSSPseudoClasses::ePseudoClass_mozOnlyWhitespace: + if (!checkGenericEmptyMatches(aElement, aTreeMatchContext, false)) { + return false; + } + break; + + case nsCSSPseudoClasses::ePseudoClass_mozEmptyExceptChildrenWithLocalname: + { + NS_ASSERTION(pseudoClass->u.mString, "Must have string!"); + nsIContent *child = nullptr; + int32_t index = -1; + + if (aTreeMatchContext.mForStyling) + // FIXME: This isn't sufficient to handle: + // :-moz-empty-except-children-with-localname() + E + // :-moz-empty-except-children-with-localname() ~ E + // because we don't know to restyle the grandparent of the + // inserted/removed element (as in bug 534804 for :empty). + aElement->SetFlags(NODE_HAS_SLOW_SELECTOR); + do { + child = aElement->GetChildAt(++index); + } while (child && + (!IsSignificantChild(child, true, false) || + (child->GetNameSpaceID() == aElement->GetNameSpaceID() && + child->Tag()->Equals(nsDependentString(pseudoClass->u.mString))))); + if (child != nullptr) { + return false; + } + } + break; + + case nsCSSPseudoClasses::ePseudoClass_lang: + { + NS_ASSERTION(nullptr != pseudoClass->u.mString, "null lang parameter"); + if (!pseudoClass->u.mString || !*pseudoClass->u.mString) { + return false; + } + + // 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. + nsAutoString language; + aElement->GetLang(language); + if (!language.IsEmpty()) { + if (!nsStyleUtil::DashMatchCompare(language, + nsDependentString(pseudoClass->u.mString), + nsASCIICaseInsensitiveStringComparator())) { + return false; + } + // This pseudo-class matched; move on to the next thing + break; + } + + nsIDocument* doc = aTreeMatchContext.mDocument; + 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. + doc->GetContentLanguage(language); + + nsDependentString langString(pseudoClass->u.mString); + language.StripWhitespace(); + int32_t begin = 0; + int32_t len = language.Length(); + while (begin < len) { + int32_t end = language.FindChar(char16_t(','), begin); + if (end == kNotFound) { + end = len; + } + if (nsStyleUtil::DashMatchCompare(Substring(language, begin, + end-begin), + langString, + nsASCIICaseInsensitiveStringComparator())) { + break; + } + begin = end + 1; + } + if (begin < len) { + // This pseudo-class matched + break; + } + } + + return false; + } + break; + + case nsCSSPseudoClasses::ePseudoClass_mozBoundElement: + if (aTreeMatchContext.mScopedRoot != aElement) { + return false; + } + break; + + case nsCSSPseudoClasses::ePseudoClass_root: + if (aElement != aElement->OwnerDoc()->GetRootElement()) { + return false; + } + break; + + case nsCSSPseudoClasses::ePseudoClass_any: + { + nsCSSSelectorList *l; + for (l = pseudoClass->u.mSelectors; l; l = l->mNext) { + nsCSSSelector *s = l->mSelectors; + NS_ABORT_IF_FALSE(!s->mNext && !s->IsPseudoElement(), + "parser failed"); + if (SelectorMatches(aElement, s, aNodeMatchContext, + aTreeMatchContext)) { + break; + } + } + if (!l) { + return false; + } + } + break; + + case nsCSSPseudoClasses::ePseudoClass_firstChild: + if (!edgeChildMatches(aElement, aTreeMatchContext, true, false)) { + return false; + } + break; + + case nsCSSPseudoClasses::ePseudoClass_firstNode: + { + nsIContent *firstNode = nullptr; + nsIContent *parent = aElement->GetParent(); + if (parent) { + if (aTreeMatchContext.mForStyling) + parent->SetFlags(NODE_HAS_EDGE_CHILD_SELECTOR); + + int32_t index = -1; + do { + firstNode = parent->GetChildAt(++index); + // stop at first non-comment and non-whitespace node + } while (firstNode && + !IsSignificantChild(firstNode, true, false)); + } + if (aElement != firstNode) { + return false; + } + } + break; + + case nsCSSPseudoClasses::ePseudoClass_lastChild: + if (!edgeChildMatches(aElement, aTreeMatchContext, false, true)) { + return false; + } + break; + + case nsCSSPseudoClasses::ePseudoClass_lastNode: + { + nsIContent *lastNode = nullptr; + nsIContent *parent = aElement->GetParent(); + if (parent) { + if (aTreeMatchContext.mForStyling) + parent->SetFlags(NODE_HAS_EDGE_CHILD_SELECTOR); + + uint32_t index = parent->GetChildCount(); + do { + lastNode = parent->GetChildAt(--index); + // stop at first non-comment and non-whitespace node + } while (lastNode && + !IsSignificantChild(lastNode, true, false)); + } + if (aElement != lastNode) { + return false; + } + } + break; + + case nsCSSPseudoClasses::ePseudoClass_onlyChild: + if (!edgeChildMatches(aElement, aTreeMatchContext, true, true)) { + return false; + } + break; + + case nsCSSPseudoClasses::ePseudoClass_firstOfType: + if (!edgeOfTypeMatches(aElement, aTreeMatchContext, true, false)) { + return false; + } + break; + + case nsCSSPseudoClasses::ePseudoClass_lastOfType: + if (!edgeOfTypeMatches(aElement, aTreeMatchContext, false, true)) { + return false; + } + break; + + case nsCSSPseudoClasses::ePseudoClass_onlyOfType: + if (!edgeOfTypeMatches(aElement, aTreeMatchContext, true, true)) { + return false; + } + break; + + case nsCSSPseudoClasses::ePseudoClass_nthChild: + if (!nthChildGenericMatches(aElement, aTreeMatchContext, pseudoClass, + false, false)) { + return false; + } + break; + + case nsCSSPseudoClasses::ePseudoClass_nthLastChild: + if (!nthChildGenericMatches(aElement, aTreeMatchContext, pseudoClass, + false, true)) { + return false; + } + break; + + case nsCSSPseudoClasses::ePseudoClass_nthOfType: + if (!nthChildGenericMatches(aElement, aTreeMatchContext, pseudoClass, + true, false)) { + return false; + } + break; + + case nsCSSPseudoClasses::ePseudoClass_nthLastOfType: + if (!nthChildGenericMatches(aElement, aTreeMatchContext, pseudoClass, + true, true)) { + return false; + } + break; + + case nsCSSPseudoClasses::ePseudoClass_mozIsHTML: + if (!aTreeMatchContext.mIsHTMLDocument || !aElement->IsHTML()) { + return false; + } + break; + + case nsCSSPseudoClasses::ePseudoClass_mozSystemMetric: + { + nsCOMPtr metric = do_GetAtom(pseudoClass->u.mString); + if (!nsCSSRuleProcessor::HasSystemMetric(metric)) { + return false; + } + } + break; + + case nsCSSPseudoClasses::ePseudoClass_mozLocaleDir: + { + bool docIsRTL = + aTreeMatchContext.mDocument->GetDocumentState(). + HasState(NS_DOCUMENT_STATE_RTL_LOCALE); + + nsDependentString dirString(pseudoClass->u.mString); + NS_ASSERTION(dirString.EqualsLiteral("ltr") || + dirString.EqualsLiteral("rtl"), + "invalid value for -moz-locale-dir"); + + if (dirString.EqualsLiteral("rtl") != docIsRTL) { + return false; + } + } + break; + + case nsCSSPseudoClasses::ePseudoClass_mozLWTheme: + { + if (aTreeMatchContext.mDocument->GetDocumentLWTheme() <= + nsIDocument::Doc_Theme_None) { + return false; + } + } + break; + + case nsCSSPseudoClasses::ePseudoClass_mozLWThemeBrightText: + { + if (aTreeMatchContext.mDocument->GetDocumentLWTheme() != + nsIDocument::Doc_Theme_Bright) { + return false; + } + } + break; + + case nsCSSPseudoClasses::ePseudoClass_mozLWThemeDarkText: + { + if (aTreeMatchContext.mDocument->GetDocumentLWTheme() != + nsIDocument::Doc_Theme_Dark) { + return false; + } + } + break; + + case nsCSSPseudoClasses::ePseudoClass_mozWindowInactive: + if (!aTreeMatchContext.mDocument->GetDocumentState(). + HasState(NS_DOCUMENT_STATE_WINDOW_INACTIVE)) { + return false; + } + break; + + case nsCSSPseudoClasses::ePseudoClass_mozTableBorderNonzero: + { + if (!aElement->IsHTML(nsGkAtoms::table)) { + return false; + } + const nsAttrValue *val = aElement->GetParsedAttr(nsGkAtoms::border); + if (!val || + (val->Type() == nsAttrValue::eInteger && + val->GetIntegerValue() == 0)) { + return false; + } + } + break; + + case nsCSSPseudoClasses::ePseudoClass_dir: + { + if (aDependence) { + EventStates states + = sPseudoClassStateDependences[pseudoClass->mType]; + if (aNodeMatchContext.mStateMask.HasAtLeastOneOfStates(states)) { + *aDependence = true; + return false; + } + } + + // if we only had to consider HTML, directionality would be exclusively + // LTR or RTL, and this could be just + // + // if (dirString.EqualsLiteral("rtl") != + // aElement->StyleState().HasState(NS_EVENT_STATE_RTL) + // + // However, in markup languages where there is no direction attribute + // we have to consider the possibility that neither -moz-dir(rtl) nor + // -moz-dir(ltr) matches. + EventStates state = aElement->StyleState(); + bool elementIsRTL = state.HasState(NS_EVENT_STATE_RTL); + bool elementIsLTR = state.HasState(NS_EVENT_STATE_LTR); + nsDependentString dirString(pseudoClass->u.mString); + + if ((dirString.EqualsLiteral("rtl") && !elementIsRTL) || + (dirString.EqualsLiteral("ltr") && !elementIsLTR)) { + return false; + } + } + break; + + case nsCSSPseudoClasses::ePseudoClass_scope: + if (aTreeMatchContext.mForScopedStyle) { + if (aTreeMatchContext.mCurrentStyleScope) { + // If mCurrentStyleScope is null, aElement must be the style + // scope root. This is because the PopStyleScopeForSelectorMatching + // call in SelectorMatchesTree sets mCurrentStyleScope to null + // as soon as we visit the style scope element, and we won't + // progress further up the tree after this call to + // SelectorMatches. Thus if mCurrentStyleScope is still set, + // we know the selector does not match. + return false; + } + } else if (aTreeMatchContext.HasSpecifiedScope()) { + if (!aTreeMatchContext.IsScopeElement(aElement)) { + return false; + } + } else { + if (aElement != aElement->OwnerDoc()->GetRootElement()) { + return false; + } + } + break; + + default: + NS_ABORT_IF_FALSE(false, "How did that happen?"); + } + } else { + if (!StateSelectorMatches(aElement, aSelector, aNodeMatchContext, + aTreeMatchContext, aDependence, + statesToCheck)) { + return false; + } + } + } + + bool result = true; + if (aSelector->mAttrList) { + // test for attribute match + if (!aElement->HasAttrs()) { + // if no attributes on the content, no match + return false; + } else { + result = true; + nsAttrSelector* attr = aSelector->mAttrList; + nsIAtom* matchAttribute; + + do { + bool isHTML = + (aTreeMatchContext.mIsHTMLDocument && aElement->IsHTML()); + matchAttribute = isHTML ? attr->mLowercaseAttr : attr->mCasedAttr; + if (attr->mNameSpace == kNameSpaceID_Unknown) { + // Attr selector with a wildcard namespace. We have to examine all + // the attributes on our content node.... This sort of selector is + // essentially a boolean OR, over all namespaces, of equivalent attr + // selectors with those namespaces. So to evaluate whether it + // matches, evaluate for each namespace (the only namespaces that + // have a chance at matching, of course, are ones that the element + // actually has attributes in), short-circuiting if we ever match. + result = false; + const nsAttrName* attrName; + for (uint32_t i = 0; (attrName = aElement->GetAttrNameAt(i)); ++i) { + if (attrName->LocalName() != matchAttribute) { + continue; + } + if (attr->mFunction == NS_ATTR_FUNC_SET) { + result = true; + } else { + nsAutoString value; +#ifdef DEBUG + bool hasAttr = +#endif + aElement->GetAttr(attrName->NamespaceID(), + attrName->LocalName(), value); + NS_ASSERTION(hasAttr, "GetAttrNameAt lied"); + result = AttrMatchesValue(attr, value, isHTML); + } + + // At this point |result| has been set by us + // explicitly in this loop. If it's false, we may still match + // -- the content may have another attribute with the same name but + // in a different namespace. But if it's true, we are done (we + // can short-circuit the boolean OR described above). + if (result) { + break; + } + } + } + else if (attr->mFunction == NS_ATTR_FUNC_EQUALS) { + result = + aElement-> + AttrValueIs(attr->mNameSpace, matchAttribute, attr->mValue, + (!isHTML || attr->mCaseSensitive) ? eCaseMatters + : eIgnoreCase); + } + else if (!aElement->HasAttr(attr->mNameSpace, matchAttribute)) { + result = false; + } + else if (attr->mFunction != NS_ATTR_FUNC_SET) { + nsAutoString value; +#ifdef DEBUG + bool hasAttr = +#endif + aElement->GetAttr(attr->mNameSpace, matchAttribute, value); + NS_ASSERTION(hasAttr, "HasAttr lied"); + result = AttrMatchesValue(attr, value, isHTML); + } + + attr = attr->mNext; + } while (attr && result); + } + } + + // apply SelectorMatches to the negated selectors in the chain + if (!isNegated) { + for (nsCSSSelector *negation = aSelector->mNegations; + result && negation; negation = negation->mNegations) { + bool dependence = false; + result = !SelectorMatches(aElement, negation, aNodeMatchContext, + aTreeMatchContext, &dependence); + // If the selector does match due to the dependence on + // aNodeMatchContext.mStateMask, then we want to keep result true + // so that the final result of SelectorMatches is true. Doing so + // tells StateEnumFunc that there is a dependence on the state. + result = result || dependence; + } + } + return result; +} + +#undef STATE_CHECK + +// Right now, there are four operators: +// ' ', the descendant combinator, is greedy +// '~', the indirect adjacent sibling combinator, is greedy +// '+' and '>', the direct adjacent sibling and child combinators, are not +#define NS_IS_GREEDY_OPERATOR(ch) \ + ((ch) == char16_t(' ') || (ch) == char16_t('~')) + +static bool SelectorMatchesTree(Element* aPrevElement, + nsCSSSelector* aSelector, + TreeMatchContext& aTreeMatchContext, + bool aLookForRelevantLink) +{ + MOZ_ASSERT(!aSelector || !aSelector->IsPseudoElement()); + nsCSSSelector* selector = aSelector; + Element* prevElement = aPrevElement; + while (selector) { // check compound selectors + NS_ASSERTION(!selector->mNext || + selector->mNext->mOperator != char16_t(0), + "compound selector without combinator"); + + // If after the previous selector match we are now outside the + // current style scope, we don't need to match any further. + if (aTreeMatchContext.mForScopedStyle && + !aTreeMatchContext.IsWithinStyleScopeForSelectorMatching()) { + return false; + } + + // for adjacent sibling combinators, the content to test against the + // selector is the previous sibling *element* + Element* element = nullptr; + if (char16_t('+') == selector->mOperator || + char16_t('~') == selector->mOperator) { + // The relevant link must be an ancestor of the node being matched. + aLookForRelevantLink = false; + nsIContent* parent = prevElement->GetParent(); + if (parent) { + if (aTreeMatchContext.mForStyling) + parent->SetFlags(NODE_HAS_SLOW_SELECTOR_LATER_SIBLINGS); + + element = prevElement->GetPreviousElementSibling(); + } + } + // for descendant combinators and child combinators, the element + // to test against is the parent + else { + nsIContent *content = prevElement->GetParent(); + // GetParent could return a document fragment; we only want + // element parents. + if (content && content->IsElement()) { + element = content->AsElement(); + if (aTreeMatchContext.mForScopedStyle) { + // We are moving up to the parent element; tell the + // TreeMatchContext, so that in case this element is the + // style scope element, selector matching stops before we + // traverse further up the tree. + aTreeMatchContext.PopStyleScopeForSelectorMatching(element); + } + + // Compatibility hack: First try matching this selector as though the + // element wasn't in the tree to allow old selectors + // were written before participated in CSS selector + // matching to work. + if (selector->mOperator == '>' && element->IsActiveChildrenElement()) { + Element* styleScope = aTreeMatchContext.mCurrentStyleScope; + if (SelectorMatchesTree(element, selector, aTreeMatchContext, + aLookForRelevantLink)) { + // It matched, don't try matching on the element at + // all. + return true; + } + // We want to reset mCurrentStyleScope on aTreeMatchContext + // back to its state before the SelectorMatchesTree call, in + // case that call happens to traverse past the style scope element + // and sets it to null. + aTreeMatchContext.mCurrentStyleScope = styleScope; + } + } + } + if (!element) { + return false; + } + NodeMatchContext nodeContext(EventStates(), + aLookForRelevantLink && + nsCSSRuleProcessor::IsLink(element)); + if (nodeContext.mIsRelevantLink) { + // If we find an ancestor of the matched node that is a link + // during the matching process, then it's the relevant link (see + // constructor call above). + // Since we are still matching against selectors that contain + // :visited (they'll just fail), we will always find such a node + // during the selector matching process if there is a relevant + // link that can influence selector matching. + aLookForRelevantLink = false; + aTreeMatchContext.SetHaveRelevantLink(); + } + if (SelectorMatches(element, selector, nodeContext, aTreeMatchContext)) { + // to avoid greedy matching, we need to recur if this is a + // descendant or general sibling combinator and the next + // combinator is different, but we can make an exception for + // sibling, then parent, since a sibling's parent is always the + // same. + if (NS_IS_GREEDY_OPERATOR(selector->mOperator) && + selector->mNext && + selector->mNext->mOperator != selector->mOperator && + !(selector->mOperator == '~' && + NS_IS_ANCESTOR_OPERATOR(selector->mNext->mOperator))) { + + // pretend the selector didn't match, and step through content + // while testing the same selector + + // This approach is slightly strange in that when it recurs + // it tests from the top of the content tree, down. This + // doesn't matter much for performance since most selectors + // don't match. (If most did, it might be faster...) + Element* styleScope = aTreeMatchContext.mCurrentStyleScope; + if (SelectorMatchesTree(element, selector, aTreeMatchContext, + aLookForRelevantLink)) { + return true; + } + // We want to reset mCurrentStyleScope on aTreeMatchContext + // back to its state before the SelectorMatchesTree call, in + // case that call happens to traverse past the style scope element + // and sets it to null. + aTreeMatchContext.mCurrentStyleScope = styleScope; + } + selector = selector->mNext; + } + else { + // for adjacent sibling and child combinators, if we didn't find + // a match, we're done + if (!NS_IS_GREEDY_OPERATOR(selector->mOperator)) { + return false; // parent was required to match + } + } + prevElement = element; + } + return true; // all the selectors matched. +} + +static inline +void ContentEnumFunc(const RuleValue& value, nsCSSSelector* aSelector, + ElementDependentRuleProcessorData* data, NodeMatchContext& nodeContext, + AncestorFilter *ancestorFilter) +{ + if (nodeContext.mIsRelevantLink) { + data->mTreeMatchContext.SetHaveRelevantLink(); + } + if (ancestorFilter && + !ancestorFilter->MightHaveMatchingAncestor( + value.mAncestorSelectorHashes)) { + // We won't match; nothing else to do here + return; + } + if (!data->mTreeMatchContext.SetStyleScopeForSelectorMatching(data->mElement, + data->mScope)) { + // The selector is for a rule in a scoped style sheet, and the subject + // of the selector matching is not in its scope. + return; + } + nsCSSSelector* selector = aSelector; + if (selector->IsPseudoElement()) { + PseudoElementRuleProcessorData* pdata = + static_cast(data); + if (!pdata->mPseudoElement && selector->mPseudoClassList) { + // We can get here when calling getComputedStyle(aElt, aPseudo) if: + // + // * aPseudo is a pseudo-element that supports a user action + // pseudo-class, like "::-moz-placeholder"; + // * there is a style rule that uses a pseudo-class on this + // pseudo-element in the document, like ::-moz-placeholder:hover; and + // * aElt does not have such a pseudo-element. + // + // We know that the selector can't match, since there is no element for + // the user action pseudo-class to match against. + return; + } + if (!StateSelectorMatches(pdata->mPseudoElement, aSelector, nodeContext, + data->mTreeMatchContext)) { + return; + } + selector = selector->mNext; + } + if (SelectorMatches(data->mElement, selector, nodeContext, + data->mTreeMatchContext)) { + nsCSSSelector *next = selector->mNext; + if (!next || SelectorMatchesTree(data->mElement, next, + data->mTreeMatchContext, + !nodeContext.mIsRelevantLink)) { + css::StyleRule *rule = value.mRule; + rule->RuleMatched(); + data->mRuleWalker->Forward(rule); + // nsStyleSet will deal with the !important rule + } + } +} + +/* virtual */ void +nsCSSRuleProcessor::RulesMatching(ElementRuleProcessorData *aData) +{ + RuleCascadeData* cascade = GetRuleCascade(aData->mPresContext); + + if (cascade) { + NodeMatchContext nodeContext(EventStates(), + nsCSSRuleProcessor::IsLink(aData->mElement)); + cascade->mRuleHash.EnumerateAllRules(aData->mElement, aData, nodeContext); + } +} + +/* virtual */ void +nsCSSRuleProcessor::RulesMatching(PseudoElementRuleProcessorData* aData) +{ + RuleCascadeData* cascade = GetRuleCascade(aData->mPresContext); + + if (cascade) { + RuleHash* ruleHash = cascade->mPseudoElementRuleHashes[aData->mPseudoType]; + if (ruleHash) { + NodeMatchContext nodeContext(EventStates(), + nsCSSRuleProcessor::IsLink(aData->mElement)); + ruleHash->EnumerateAllRules(aData->mElement, aData, nodeContext); + } + } +} + +/* virtual */ void +nsCSSRuleProcessor::RulesMatching(AnonBoxRuleProcessorData* aData) +{ + RuleCascadeData* cascade = GetRuleCascade(aData->mPresContext); + + if (cascade && cascade->mAnonBoxRules.entryCount) { + RuleHashTagTableEntry* entry = static_cast + (PL_DHashTableOperate(&cascade->mAnonBoxRules, aData->mPseudoTag, + PL_DHASH_LOOKUP)); + if (PL_DHASH_ENTRY_IS_BUSY(entry)) { + nsTArray& rules = entry->mRules; + for (RuleValue *value = rules.Elements(), *end = value + rules.Length(); + value != end; ++value) { + value->mRule->RuleMatched(); + aData->mRuleWalker->Forward(value->mRule); + } + } + } +} + +#ifdef MOZ_XUL +/* virtual */ void +nsCSSRuleProcessor::RulesMatching(XULTreeRuleProcessorData* aData) +{ + RuleCascadeData* cascade = GetRuleCascade(aData->mPresContext); + + if (cascade && cascade->mXULTreeRules.entryCount) { + RuleHashTagTableEntry* entry = static_cast + (PL_DHashTableOperate(&cascade->mXULTreeRules, aData->mPseudoTag, + PL_DHASH_LOOKUP)); + if (PL_DHASH_ENTRY_IS_BUSY(entry)) { + NodeMatchContext nodeContext(EventStates(), + nsCSSRuleProcessor::IsLink(aData->mElement)); + nsTArray& rules = entry->mRules; + for (RuleValue *value = rules.Elements(), *end = value + rules.Length(); + value != end; ++value) { + if (aData->mComparator->PseudoMatches(value->mSelector)) { + ContentEnumFunc(*value, value->mSelector->mNext, aData, nodeContext, + nullptr); + } + } + } + } +} +#endif + +static inline nsRestyleHint RestyleHintForOp(char16_t oper) +{ + if (oper == char16_t('+') || oper == char16_t('~')) { + return eRestyle_LaterSiblings; + } + + if (oper != char16_t(0)) { + return eRestyle_Subtree; + } + + return eRestyle_Self; +} + +nsRestyleHint +nsCSSRuleProcessor::HasStateDependentStyle(ElementDependentRuleProcessorData* aData, + Element* aStatefulElement, + nsCSSPseudoElements::Type aPseudoType, + EventStates aStateMask) +{ + MOZ_ASSERT(!aData->mTreeMatchContext.mForScopedStyle, + "mCurrentStyleScope will need to be saved and restored after the " + "SelectorMatchesTree call"); + + bool isPseudoElement = + aPseudoType != nsCSSPseudoElements::ePseudo_NotPseudoElement; + + RuleCascadeData* cascade = GetRuleCascade(aData->mPresContext); + + // Look up the content node in the state rule list, which points to + // any (CSS2 definition) simple selector (whether or not it is the + // subject) that has a state pseudo-class on it. This means that this + // code will be matching selectors that aren't real selectors in any + // stylesheet (e.g., if there is a selector "body > p:hover > a", then + // "body > p:hover" will be in |cascade->mStateSelectors|). Note that + // |ComputeSelectorStateDependence| below determines which selectors are in + // |cascade->mStateSelectors|. + nsRestyleHint hint = nsRestyleHint(0); + if (cascade) { + StateSelector *iter = cascade->mStateSelectors.Elements(), + *end = iter + cascade->mStateSelectors.Length(); + NodeMatchContext nodeContext(aStateMask, false); + for(; iter != end; ++iter) { + nsCSSSelector* selector = iter->mSelector; + EventStates states = iter->mStates; + + if (selector->IsPseudoElement() != isPseudoElement) { + continue; + } + + nsCSSSelector* selectorForPseudo; + if (isPseudoElement) { + if (selector->PseudoType() != aPseudoType) { + continue; + } + selectorForPseudo = selector; + selector = selector->mNext; + } + + nsRestyleHint possibleChange = RestyleHintForOp(selector->mOperator); + + // If hint already includes all the bits of possibleChange, + // don't bother calling SelectorMatches, since even if it returns false + // hint won't change. + // Also don't bother calling SelectorMatches if none of the + // states passed in are relevant here. + if ((possibleChange & ~hint) && + states.HasAtLeastOneOfStates(aStateMask) && + // We can optimize away testing selectors that only involve :hover, a + // namespace, and a tag name against nodes that don't have the + // NodeHasRelevantHoverRules flag: such a selector didn't match + // the tag name or namespace the first time around (since the :hover + // didn't set the NodeHasRelevantHoverRules flag), so it won't + // match it now. Check for our selector only having :hover states, or + // the element having the hover rules flag, or the selector having + // some sort of non-namespace, non-tagname data in it. + (states != NS_EVENT_STATE_HOVER || + aStatefulElement->HasRelevantHoverRules() || + selector->mIDList || selector->mClassList || + // We generally expect an mPseudoClassList, since we have a :hover. + // The question is whether we have anything else in there. + (selector->mPseudoClassList && + (selector->mPseudoClassList->mNext || + selector->mPseudoClassList->mType != + nsCSSPseudoClasses::ePseudoClass_hover)) || + selector->mAttrList || selector->mNegations) && + (!isPseudoElement || + StateSelectorMatches(aStatefulElement, selectorForPseudo, + nodeContext, aData->mTreeMatchContext, + nullptr, aStateMask)) && + SelectorMatches(aData->mElement, selector, nodeContext, + aData->mTreeMatchContext) && + SelectorMatchesTree(aData->mElement, selector->mNext, + aData->mTreeMatchContext, + false)) + { + hint = nsRestyleHint(hint | possibleChange); + } + } + } + return hint; +} + +nsRestyleHint +nsCSSRuleProcessor::HasStateDependentStyle(StateRuleProcessorData* aData) +{ + return HasStateDependentStyle(aData, + aData->mElement, + nsCSSPseudoElements::ePseudo_NotPseudoElement, + aData->mStateMask); +} + +nsRestyleHint +nsCSSRuleProcessor::HasStateDependentStyle(PseudoElementStateRuleProcessorData* aData) +{ + return HasStateDependentStyle(aData, + aData->mPseudoElement, + aData->mPseudoType, + aData->mStateMask); +} + +bool +nsCSSRuleProcessor::HasDocumentStateDependentStyle(StateRuleProcessorData* aData) +{ + RuleCascadeData* cascade = GetRuleCascade(aData->mPresContext); + + return cascade && cascade->mSelectorDocumentStates.HasAtLeastOneOfStates(aData->mStateMask); +} + +struct AttributeEnumData { + AttributeEnumData(AttributeRuleProcessorData *aData) + : data(aData), change(nsRestyleHint(0)) {} + + AttributeRuleProcessorData *data; + nsRestyleHint change; +}; + + +static void +AttributeEnumFunc(nsCSSSelector* aSelector, AttributeEnumData* aData) +{ + AttributeRuleProcessorData *data = aData->data; + + if (!data->mTreeMatchContext.SetStyleScopeForSelectorMatching(data->mElement, + data->mScope)) { + // The selector is for a rule in a scoped style sheet, and the subject + // of the selector matching is not in its scope. + return; + } + + nsRestyleHint possibleChange = RestyleHintForOp(aSelector->mOperator); + + // If enumData->change already includes all the bits of possibleChange, don't + // bother calling SelectorMatches, since even if it returns false + // enumData->change won't change. + NodeMatchContext nodeContext(EventStates(), false); + if ((possibleChange & ~(aData->change)) && + SelectorMatches(data->mElement, aSelector, nodeContext, + data->mTreeMatchContext) && + SelectorMatchesTree(data->mElement, aSelector->mNext, + data->mTreeMatchContext, false)) { + aData->change = nsRestyleHint(aData->change | possibleChange); + } +} + +static MOZ_ALWAYS_INLINE void +EnumerateSelectors(nsTArray& aSelectors, AttributeEnumData* aData) +{ + nsCSSSelector **iter = aSelectors.Elements(), + **end = iter + aSelectors.Length(); + for (; iter != end; ++iter) { + AttributeEnumFunc(*iter, aData); + } +} + +nsRestyleHint +nsCSSRuleProcessor::HasAttributeDependentStyle(AttributeRuleProcessorData* aData) +{ + // We could try making use of aData->mModType, but :not rules make it a bit + // of a pain to do so... So just ignore it for now. + + AttributeEnumData data(aData); + + // Don't do our special handling of certain attributes if the attr + // hasn't changed yet. + if (aData->mAttrHasChanged) { + // check for the lwtheme and lwthemetextcolor attribute on root XUL elements + if ((aData->mAttribute == nsGkAtoms::lwtheme || + aData->mAttribute == nsGkAtoms::lwthemetextcolor) && + aData->mElement->GetNameSpaceID() == kNameSpaceID_XUL && + aData->mElement == aData->mElement->OwnerDoc()->GetRootElement()) + { + data.change = nsRestyleHint(data.change | eRestyle_Subtree); + } + + // We don't know the namespace of the attribute, and xml:lang applies to + // all elements. If the lang attribute changes, we need to restyle our + // whole subtree, since the :lang selector on our descendants can examine + // our lang attribute. + if (aData->mAttribute == nsGkAtoms::lang) { + data.change = nsRestyleHint(data.change | eRestyle_Subtree); + } + } + + RuleCascadeData* cascade = GetRuleCascade(aData->mPresContext); + + // Since we get both before and after notifications for attributes, we + // don't have to ignore aData->mAttribute while matching. Just check + // whether we have selectors relevant to aData->mAttribute that we + // match. If this is the before change notification, that will catch + // rules we might stop matching; if the after change notification, the + // ones we might have started matching. + if (cascade) { + if (aData->mAttribute == aData->mElement->GetIDAttributeName()) { + nsIAtom* id = aData->mElement->GetID(); + if (id) { + AtomSelectorEntry *entry = + static_cast + (PL_DHashTableOperate(&cascade->mIdSelectors, + id, PL_DHASH_LOOKUP)); + if (PL_DHASH_ENTRY_IS_BUSY(entry)) { + EnumerateSelectors(entry->mSelectors, &data); + } + } + + EnumerateSelectors(cascade->mPossiblyNegatedIDSelectors, &data); + } + + if (aData->mAttribute == aData->mElement->GetClassAttributeName()) { + const nsAttrValue* elementClasses = aData->mElement->GetClasses(); + if (elementClasses) { + int32_t atomCount = elementClasses->GetAtomCount(); + for (int32_t i = 0; i < atomCount; ++i) { + nsIAtom* curClass = elementClasses->AtomAt(i); + AtomSelectorEntry *entry = + static_cast + (PL_DHashTableOperate(&cascade->mClassSelectors, + curClass, PL_DHASH_LOOKUP)); + if (PL_DHASH_ENTRY_IS_BUSY(entry)) { + EnumerateSelectors(entry->mSelectors, &data); + } + } + } + + EnumerateSelectors(cascade->mPossiblyNegatedClassSelectors, &data); + } + + AtomSelectorEntry *entry = + static_cast + (PL_DHashTableOperate(&cascade->mAttributeSelectors, + aData->mAttribute, PL_DHASH_LOOKUP)); + if (PL_DHASH_ENTRY_IS_BUSY(entry)) { + EnumerateSelectors(entry->mSelectors, &data); + } + } + + return data.change; +} + +/* virtual */ bool +nsCSSRuleProcessor::MediumFeaturesChanged(nsPresContext* aPresContext) +{ + RuleCascadeData *old = mRuleCascades; + // We don't want to do anything if there aren't any sets of rules + // cached yet (or somebody cleared them and is thus responsible for + // rebuilding things), since we should not build the rule cascade too + // early (e.g., before we know whether the quirk style sheet should be + // enabled). And if there's nothing cached, it doesn't matter if + // anything changed. See bug 448281. + if (old) { + RefreshRuleCascade(aPresContext); + } + return (old != mRuleCascades); +} + +/* virtual */ size_t +nsCSSRuleProcessor::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const +{ + size_t n = 0; + n += mSheets.SizeOfExcludingThis(aMallocSizeOf); + for (RuleCascadeData* cascade = mRuleCascades; cascade; + cascade = cascade->mNext) { + n += cascade->SizeOfIncludingThis(aMallocSizeOf); + } + + return n; +} + +/* virtual */ size_t +nsCSSRuleProcessor::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const +{ + return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf); +} + +// Append all the currently-active font face rules to aArray. Return +// true for success and false for failure. +bool +nsCSSRuleProcessor::AppendFontFaceRules( + nsPresContext *aPresContext, + nsTArray& aArray) +{ + RuleCascadeData* cascade = GetRuleCascade(aPresContext); + + if (cascade) { + if (!aArray.AppendElements(cascade->mFontFaceRules)) + return false; + } + + return true; +} + +nsCSSKeyframesRule* +nsCSSRuleProcessor::KeyframesRuleForName(nsPresContext* aPresContext, + const nsString& aName) +{ + RuleCascadeData* cascade = GetRuleCascade(aPresContext); + + if (cascade) { + return cascade->mKeyframesRuleTable.Get(aName); + } + + return nullptr; +} + +// Append all the currently-active page rules to aArray. Return +// true for success and false for failure. +bool +nsCSSRuleProcessor::AppendPageRules( + nsPresContext* aPresContext, + nsTArray& aArray) +{ + RuleCascadeData* cascade = GetRuleCascade(aPresContext); + + if (cascade) { + if (!aArray.AppendElements(cascade->mPageRules)) { + return false; + } + } + + return true; +} + +bool +nsCSSRuleProcessor::AppendFontFeatureValuesRules( + nsPresContext *aPresContext, + nsTArray& aArray) +{ + RuleCascadeData* cascade = GetRuleCascade(aPresContext); + + if (cascade) { + if (!aArray.AppendElements(cascade->mFontFeatureValuesRules)) + return false; + } + + return true; +} + +nsresult +nsCSSRuleProcessor::ClearRuleCascades() +{ + // We rely on our caller (perhaps indirectly) to do something that + // will rebuild style data and the user font set (either + // nsIPresShell::ReconstructStyleData or + // nsPresContext::RebuildAllStyleData). + RuleCascadeData *data = mRuleCascades; + mRuleCascades = nullptr; + while (data) { + RuleCascadeData *next = data->mNext; + delete data; + data = next; + } + return NS_OK; +} + + +// This function should return the set of states that this selector +// depends on; this is used to implement HasStateDependentStyle. It +// does NOT recur down into things like :not and :-moz-any. +inline +EventStates ComputeSelectorStateDependence(nsCSSSelector& aSelector) +{ + EventStates states; + for (nsPseudoClassList* pseudoClass = aSelector.mPseudoClassList; + pseudoClass; pseudoClass = pseudoClass->mNext) { + // Tree pseudo-elements overload mPseudoClassList for things that + // aren't pseudo-classes. + if (pseudoClass->mType >= nsCSSPseudoClasses::ePseudoClass_Count) { + continue; + } + states |= sPseudoClassStateDependences[pseudoClass->mType]; + } + return states; +} + +static bool +AddSelector(RuleCascadeData* aCascade, + // The part between combinators at the top level of the selector + nsCSSSelector* aSelectorInTopLevel, + // The part we should look through (might be in :not or :-moz-any()) + nsCSSSelector* aSelectorPart) +{ + // It's worth noting that this loop over negations isn't quite + // optimal for two reasons. One, we could add something to one of + // these lists twice, which means we'll check it twice, but I don't + // think that's worth worrying about. (We do the same for multiple + // attribute selectors on the same attribute.) Two, we don't really + // need to check negations past the first in the current + // implementation (and they're rare as well), but that might change + // in the future if :not() is extended. + for (nsCSSSelector* negation = aSelectorPart; negation; + negation = negation->mNegations) { + // Track both document states and attribute dependence in pseudo-classes. + for (nsPseudoClassList* pseudoClass = negation->mPseudoClassList; + pseudoClass; pseudoClass = pseudoClass->mNext) { + switch (pseudoClass->mType) { + case nsCSSPseudoClasses::ePseudoClass_mozLocaleDir: { + aCascade->mSelectorDocumentStates |= NS_DOCUMENT_STATE_RTL_LOCALE; + break; + } + case nsCSSPseudoClasses::ePseudoClass_mozWindowInactive: { + aCascade->mSelectorDocumentStates |= NS_DOCUMENT_STATE_WINDOW_INACTIVE; + break; + } + case nsCSSPseudoClasses::ePseudoClass_mozTableBorderNonzero: { + nsTArray *array = + aCascade->AttributeListFor(nsGkAtoms::border); + if (!array) { + return false; + } + array->AppendElement(aSelectorInTopLevel); + break; + } + default: { + break; + } + } + } + + // Build mStateSelectors. + EventStates dependentStates = ComputeSelectorStateDependence(*negation); + if (!dependentStates.IsEmpty()) { + aCascade->mStateSelectors.AppendElement( + nsCSSRuleProcessor::StateSelector(dependentStates, + aSelectorInTopLevel)); + } + + // Build mIDSelectors + if (negation == aSelectorInTopLevel) { + for (nsAtomList* curID = negation->mIDList; curID; + curID = curID->mNext) { + AtomSelectorEntry *entry = + static_cast(PL_DHashTableOperate(&aCascade->mIdSelectors, + curID->mAtom, + PL_DHASH_ADD)); + if (entry) { + entry->mSelectors.AppendElement(aSelectorInTopLevel); + } + } + } else if (negation->mIDList) { + aCascade->mPossiblyNegatedIDSelectors.AppendElement(aSelectorInTopLevel); + } + + // Build mClassSelectors + if (negation == aSelectorInTopLevel) { + for (nsAtomList* curClass = negation->mClassList; curClass; + curClass = curClass->mNext) { + AtomSelectorEntry *entry = + static_cast(PL_DHashTableOperate(&aCascade->mClassSelectors, + curClass->mAtom, + PL_DHASH_ADD)); + if (entry) { + entry->mSelectors.AppendElement(aSelectorInTopLevel); + } + } + } else if (negation->mClassList) { + aCascade->mPossiblyNegatedClassSelectors.AppendElement(aSelectorInTopLevel); + } + + // Build mAttributeSelectors. + for (nsAttrSelector *attr = negation->mAttrList; attr; + attr = attr->mNext) { + nsTArray *array = + aCascade->AttributeListFor(attr->mCasedAttr); + if (!array) { + return false; + } + array->AppendElement(aSelectorInTopLevel); + if (attr->mLowercaseAttr != attr->mCasedAttr) { + array = aCascade->AttributeListFor(attr->mLowercaseAttr); + if (!array) { + return false; + } + array->AppendElement(aSelectorInTopLevel); + } + } + + // Recur through any :-moz-any selectors + for (nsPseudoClassList* pseudoClass = negation->mPseudoClassList; + pseudoClass; pseudoClass = pseudoClass->mNext) { + if (pseudoClass->mType == nsCSSPseudoClasses::ePseudoClass_any) { + for (nsCSSSelectorList *l = pseudoClass->u.mSelectors; l; l = l->mNext) { + nsCSSSelector *s = l->mSelectors; + if (!AddSelector(aCascade, aSelectorInTopLevel, s)) { + return false; + } + } + } + } + } + + return true; +} + +static bool +AddRule(RuleSelectorPair* aRuleInfo, RuleCascadeData* aCascade) +{ + RuleCascadeData * const cascade = aCascade; + + // Build the rule hash. + nsCSSPseudoElements::Type pseudoType = aRuleInfo->mSelector->PseudoType(); + if (MOZ_LIKELY(pseudoType == nsCSSPseudoElements::ePseudo_NotPseudoElement)) { + cascade->mRuleHash.AppendRule(*aRuleInfo); + } else if (pseudoType < nsCSSPseudoElements::ePseudo_PseudoElementCount) { + RuleHash*& ruleHash = cascade->mPseudoElementRuleHashes[pseudoType]; + if (!ruleHash) { + ruleHash = new RuleHash(cascade->mQuirksMode); + if (!ruleHash) { + // Out of memory; give up + return false; + } + } + NS_ASSERTION(aRuleInfo->mSelector->mNext, + "Must have mNext; parser screwed up"); + NS_ASSERTION(aRuleInfo->mSelector->mNext->mOperator == ':', + "Unexpected mNext combinator"); + ruleHash->AppendRule(*aRuleInfo); + } else if (pseudoType == nsCSSPseudoElements::ePseudo_AnonBox) { + NS_ASSERTION(!aRuleInfo->mSelector->mCasedTag && + !aRuleInfo->mSelector->mIDList && + !aRuleInfo->mSelector->mClassList && + !aRuleInfo->mSelector->mPseudoClassList && + !aRuleInfo->mSelector->mAttrList && + !aRuleInfo->mSelector->mNegations && + !aRuleInfo->mSelector->mNext && + aRuleInfo->mSelector->mNameSpace == kNameSpaceID_Unknown, + "Parser messed up with anon box selector"); + + // Index doesn't matter here, since we'll just be walking these + // rules in order; just pass 0. + AppendRuleToTagTable(&cascade->mAnonBoxRules, + aRuleInfo->mSelector->mLowercaseTag, + RuleValue(*aRuleInfo, 0, aCascade->mQuirksMode)); + } else { +#ifdef MOZ_XUL + NS_ASSERTION(pseudoType == nsCSSPseudoElements::ePseudo_XULTree, + "Unexpected pseudo type"); + // Index doesn't matter here, since we'll just be walking these + // rules in order; just pass 0. + AppendRuleToTagTable(&cascade->mXULTreeRules, + aRuleInfo->mSelector->mLowercaseTag, + RuleValue(*aRuleInfo, 0, aCascade->mQuirksMode)); +#else + NS_NOTREACHED("Unexpected pseudo type"); +#endif + } + + for (nsCSSSelector* selector = aRuleInfo->mSelector; + selector; selector = selector->mNext) { + if (selector->IsPseudoElement()) { + nsCSSPseudoElements::Type pseudo = selector->PseudoType(); + if (pseudo >= nsCSSPseudoElements::ePseudo_PseudoElementCount || + !nsCSSPseudoElements::PseudoElementSupportsUserActionState(pseudo)) { + NS_ASSERTION(!selector->mNegations, "Shouldn't have negations"); + // We do store selectors ending with pseudo-elements that allow :hover + // and :active after them in the hashtables corresponding to that + // selector's mNext (i.e. the thing that matches against the element), + // but we want to make sure that selectors for any other kinds of + // pseudo-elements don't end up in the hashtables. In particular, tree + // pseudos store strange things in mPseudoClassList that we don't want + // to try to match elements against. + continue; + } + } + if (!AddSelector(cascade, selector, selector)) { + return false; + } + } + + return true; +} + +struct PerWeightDataListItem : public RuleSelectorPair { + PerWeightDataListItem(css::StyleRule* aRule, nsCSSSelector* aSelector) + : RuleSelectorPair(aRule, aSelector) + , mNext(nullptr) + {} + // No destructor; these are arena-allocated + + + // Placement new to arena allocate the PerWeightDataListItem + void *operator new(size_t aSize, PLArenaPool &aArena) CPP_THROW_NEW { + void *mem; + PL_ARENA_ALLOCATE(mem, &aArena, aSize); + return mem; + } + + PerWeightDataListItem *mNext; +}; + +struct PerWeightData { + PerWeightData() + : mRuleSelectorPairs(nullptr) + , mTail(&mRuleSelectorPairs) + {} + + int32_t mWeight; + PerWeightDataListItem *mRuleSelectorPairs; + PerWeightDataListItem **mTail; +}; + +struct RuleByWeightEntry : public PLDHashEntryHdr { + PerWeightData data; // mWeight is key, mRuleSelectorPairs are value +}; + +static PLDHashNumber +HashIntKey(PLDHashTable *table, const void *key) +{ + return PLDHashNumber(NS_PTR_TO_INT32(key)); +} + +static bool +MatchWeightEntry(PLDHashTable *table, const PLDHashEntryHdr *hdr, + const void *key) +{ + const RuleByWeightEntry *entry = (const RuleByWeightEntry *)hdr; + return entry->data.mWeight == NS_PTR_TO_INT32(key); +} + +static bool +InitWeightEntry(PLDHashTable *table, PLDHashEntryHdr *hdr, + const void *key) +{ + RuleByWeightEntry* entry = static_cast(hdr); + new (entry) RuleByWeightEntry(); + return true; +} + +static const PLDHashTableOps gRulesByWeightOps = { + PL_DHashAllocTable, + PL_DHashFreeTable, + HashIntKey, + MatchWeightEntry, + PL_DHashMoveEntryStub, + PL_DHashClearEntryStub, + PL_DHashFinalizeStub, + InitWeightEntry +}; + +struct CascadeEnumData { + CascadeEnumData(nsPresContext* aPresContext, + nsTArray& aFontFaceRules, + nsTArray& aKeyframesRules, + nsTArray& aFontFeatureValuesRules, + nsTArray& aPageRules, + nsMediaQueryResultCacheKey& aKey, + uint8_t aSheetType) + : mPresContext(aPresContext), + mFontFaceRules(aFontFaceRules), + mKeyframesRules(aKeyframesRules), + mFontFeatureValuesRules(aFontFeatureValuesRules), + mPageRules(aPageRules), + mCacheKey(aKey), + mSheetType(aSheetType) + { + if (!PL_DHashTableInit(&mRulesByWeight, &gRulesByWeightOps, nullptr, + sizeof(RuleByWeightEntry), 64, fallible_t())) + mRulesByWeight.ops = nullptr; + + // Initialize our arena + PL_INIT_ARENA_POOL(&mArena, "CascadeEnumDataArena", + NS_CASCADEENUMDATA_ARENA_BLOCK_SIZE); + } + + ~CascadeEnumData() + { + if (mRulesByWeight.ops) + PL_DHashTableFinish(&mRulesByWeight); + PL_FinishArenaPool(&mArena); + } + + nsPresContext* mPresContext; + nsTArray& mFontFaceRules; + nsTArray& mKeyframesRules; + nsTArray& mFontFeatureValuesRules; + nsTArray& mPageRules; + nsMediaQueryResultCacheKey& mCacheKey; + PLArenaPool mArena; + // Hooray, a manual PLDHashTable since nsClassHashtable doesn't + // provide a getter that gives me a *reference* to the value. + PLDHashTable mRulesByWeight; // of PerWeightDataListItem linked lists + uint8_t mSheetType; +}; + +/* + * This enumerates style rules in a sheet (and recursively into any + * grouping rules) in order to: + * (1) add any style rules, in order, into data->mRulesByWeight (for + * the primary CSS cascade), where they are separated by weight + * but kept in order per-weight, and + * (2) add any @font-face rules, in order, into data->mFontFaceRules. + * (3) add any @keyframes rules, in order, into data->mKeyframesRules. + * (4) add any @font-feature-value rules, in order, + * into data->mFontFeatureValuesRules. + * (5) add any @page rules, in order, into data->mPageRules. + */ +static bool +CascadeRuleEnumFunc(css::Rule* aRule, void* aData) +{ + CascadeEnumData* data = (CascadeEnumData*)aData; + int32_t type = aRule->GetType(); + + if (css::Rule::STYLE_RULE == type) { + css::StyleRule* styleRule = static_cast(aRule); + + for (nsCSSSelectorList *sel = styleRule->Selector(); + sel; sel = sel->mNext) { + int32_t weight = sel->mWeight; + RuleByWeightEntry *entry = static_cast( + PL_DHashTableOperate(&data->mRulesByWeight, NS_INT32_TO_PTR(weight), + PL_DHASH_ADD)); + if (!entry) + return false; + entry->data.mWeight = weight; + // entry->data.mRuleSelectorPairs should be linked in forward order; + // entry->data.mTail is the slot to write to. + PerWeightDataListItem *newItem = + new (data->mArena) PerWeightDataListItem(styleRule, sel->mSelectors); + if (newItem) { + *(entry->data.mTail) = newItem; + entry->data.mTail = &newItem->mNext; + } + } + } + else if (css::Rule::MEDIA_RULE == type || + css::Rule::DOCUMENT_RULE == type || + css::Rule::SUPPORTS_RULE == type) { + css::GroupRule* groupRule = static_cast(aRule); + if (groupRule->UseForPresentation(data->mPresContext, data->mCacheKey)) + if (!groupRule->EnumerateRulesForwards(CascadeRuleEnumFunc, aData)) + return false; + } + else if (css::Rule::FONT_FACE_RULE == type) { + nsCSSFontFaceRule *fontFaceRule = static_cast(aRule); + nsFontFaceRuleContainer *ptr = data->mFontFaceRules.AppendElement(); + if (!ptr) + return false; + ptr->mRule = fontFaceRule; + ptr->mSheetType = data->mSheetType; + } + else if (css::Rule::KEYFRAMES_RULE == type) { + nsCSSKeyframesRule *keyframesRule = + static_cast(aRule); + if (!data->mKeyframesRules.AppendElement(keyframesRule)) { + return false; + } + } + else if (css::Rule::FONT_FEATURE_VALUES_RULE == type) { + nsCSSFontFeatureValuesRule *fontFeatureValuesRule = + static_cast(aRule); + if (!data->mFontFeatureValuesRules.AppendElement(fontFeatureValuesRule)) { + return false; + } + } + else if (css::Rule::PAGE_RULE == type) { + nsCSSPageRule* pageRule = static_cast(aRule); + if (!data->mPageRules.AppendElement(pageRule)) { + return false; + } + } + return true; +} + +/* static */ bool +nsCSSRuleProcessor::CascadeSheet(nsCSSStyleSheet* aSheet, CascadeEnumData* aData) +{ + if (aSheet->IsApplicable() && + aSheet->UseForPresentation(aData->mPresContext, aData->mCacheKey) && + aSheet->mInner) { + nsCSSStyleSheet* child = aSheet->mInner->mFirstChild; + while (child) { + CascadeSheet(child, aData); + child = child->mNext; + } + + if (!aSheet->mInner->mOrderedRules.EnumerateForwards(CascadeRuleEnumFunc, + aData)) + return false; + } + return true; +} + +static int CompareWeightData(const void* aArg1, const void* aArg2, + void* closure) +{ + const PerWeightData* arg1 = static_cast(aArg1); + const PerWeightData* arg2 = static_cast(aArg2); + return arg1->mWeight - arg2->mWeight; // put lower weight first +} + + +struct FillWeightArrayData { + FillWeightArrayData(PerWeightData* aArrayData) : + mIndex(0), + mWeightArray(aArrayData) + { + } + int32_t mIndex; + PerWeightData* mWeightArray; +}; + + +static PLDHashOperator +FillWeightArray(PLDHashTable *table, PLDHashEntryHdr *hdr, + uint32_t number, void *arg) +{ + FillWeightArrayData* data = static_cast(arg); + const RuleByWeightEntry *entry = (const RuleByWeightEntry *)hdr; + + data->mWeightArray[data->mIndex++] = entry->data; + + return PL_DHASH_NEXT; +} + +RuleCascadeData* +nsCSSRuleProcessor::GetRuleCascade(nsPresContext* aPresContext) +{ + // FIXME: Make this infallible! + + // If anything changes about the presentation context, we will be + // notified. Otherwise, our cache is valid if mLastPresContext + // matches aPresContext. (The only rule processors used for multiple + // pres contexts are for XBL. These rule processors are probably less + // likely to have @media rules, and thus the cache is pretty likely to + // hit instantly even when we're switching between pres contexts.) + + if (!mRuleCascades || aPresContext != mLastPresContext) { + RefreshRuleCascade(aPresContext); + } + mLastPresContext = aPresContext; + + return mRuleCascades; +} + +void +nsCSSRuleProcessor::RefreshRuleCascade(nsPresContext* aPresContext) +{ + // Having RuleCascadeData objects be per-medium (over all variation + // caused by media queries, handled through mCacheKey) works for now + // since nsCSSRuleProcessor objects are per-document. (For a given + // set of stylesheets they can vary based on medium (@media) or + // document (@-moz-document).) + + for (RuleCascadeData **cascadep = &mRuleCascades, *cascade; + (cascade = *cascadep); cascadep = &cascade->mNext) { + if (cascade->mCacheKey.Matches(aPresContext)) { + // Ensure that the current one is always mRuleCascades. + *cascadep = cascade->mNext; + cascade->mNext = mRuleCascades; + mRuleCascades = cascade; + + return; + } + } + + if (mSheets.Length() != 0) { + nsAutoPtr newCascade( + new RuleCascadeData(aPresContext->Medium(), + eCompatibility_NavQuirks == aPresContext->CompatibilityMode())); + if (newCascade) { + CascadeEnumData data(aPresContext, newCascade->mFontFaceRules, + newCascade->mKeyframesRules, + newCascade->mFontFeatureValuesRules, + newCascade->mPageRules, + newCascade->mCacheKey, + mSheetType); + if (!data.mRulesByWeight.ops) + return; /* out of memory */ + + for (uint32_t i = 0; i < mSheets.Length(); ++i) { + if (!CascadeSheet(mSheets.ElementAt(i), &data)) + return; /* out of memory */ + } + + // Sort the hash table of per-weight linked lists by weight. + uint32_t weightCount = data.mRulesByWeight.entryCount; + nsAutoArrayPtr weightArray(new PerWeightData[weightCount]); + FillWeightArrayData fwData(weightArray); + PL_DHashTableEnumerate(&data.mRulesByWeight, FillWeightArray, &fwData); + NS_QuickSort(weightArray, weightCount, sizeof(PerWeightData), + CompareWeightData, nullptr); + + // Put things into the rule hash. + // The primary sort is by weight... + for (uint32_t i = 0; i < weightCount; ++i) { + // and the secondary sort is by order. mRuleSelectorPairs is already in + // the right order.. + for (PerWeightDataListItem *cur = weightArray[i].mRuleSelectorPairs; + cur; + cur = cur->mNext) { + if (!AddRule(cur, newCascade)) + return; /* out of memory */ + } + } + + // Build mKeyframesRuleTable. + for (nsTArray::size_type i = 0, + iEnd = newCascade->mKeyframesRules.Length(); i < iEnd; ++i) { + nsCSSKeyframesRule* rule = newCascade->mKeyframesRules[i]; + newCascade->mKeyframesRuleTable.Put(rule->GetName(), rule); + } + + // Ensure that the current one is always mRuleCascades. + newCascade->mNext = mRuleCascades; + mRuleCascades = newCascade.forget(); + } + } + return; +} + +/* static */ bool +nsCSSRuleProcessor::SelectorListMatches(Element* aElement, + TreeMatchContext& aTreeMatchContext, + nsCSSSelectorList* aSelectorList) +{ + MOZ_ASSERT(!aTreeMatchContext.mForScopedStyle, + "mCurrentStyleScope will need to be saved and restored after the " + "SelectorMatchesTree call"); + + while (aSelectorList) { + nsCSSSelector* sel = aSelectorList->mSelectors; + NS_ASSERTION(sel, "Should have *some* selectors"); + NS_ASSERTION(!sel->IsPseudoElement(), "Shouldn't have been called"); + NodeMatchContext nodeContext(EventStates(), false); + if (SelectorMatches(aElement, sel, nodeContext, aTreeMatchContext)) { + nsCSSSelector* next = sel->mNext; + if (!next || + SelectorMatchesTree(aElement, next, aTreeMatchContext, false)) { + return true; + } + } + + aSelectorList = aSelectorList->mNext; + } + + return false; +} + +// TreeMatchContext and AncestorFilter out of line methods +void +TreeMatchContext::InitAncestors(Element *aElement) +{ + MOZ_ASSERT(!mAncestorFilter.mFilter); + MOZ_ASSERT(mAncestorFilter.mHashes.IsEmpty()); + MOZ_ASSERT(mStyleScopes.IsEmpty()); + + mAncestorFilter.mFilter = new AncestorFilter::Filter(); + + if (MOZ_LIKELY(aElement)) { + MOZ_ASSERT(aElement->IsInDoc(), + "aElement must be in the document for the assumption that " + "GetParentNode() is non-null on all element ancestors of " + "aElement to be true"); + // Collect up the ancestors + nsAutoTArray ancestors; + Element* cur = aElement; + do { + ancestors.AppendElement(cur); + nsINode* parent = cur->GetParentNode(); + if (!parent->IsElement()) { + break; + } + + cur = parent->AsElement(); + } while (true); + + // Now push them in reverse order. + for (uint32_t i = ancestors.Length(); i-- != 0; ) { + mAncestorFilter.PushAncestor(ancestors[i]); + PushStyleScope(ancestors[i]); + } + } +} + +void +TreeMatchContext::InitStyleScopes(Element* aElement) +{ + MOZ_ASSERT(mStyleScopes.IsEmpty()); + + if (MOZ_LIKELY(aElement)) { + // Collect up the ancestors + nsAutoTArray ancestors; + Element* cur = aElement; + do { + ancestors.AppendElement(cur); + nsINode* parent = cur->GetParentNode(); + if (!parent || !parent->IsElement()) { + break; + } + + cur = parent->AsElement(); + } while (true); + + // Now push them in reverse order. + for (uint32_t i = ancestors.Length(); i-- != 0; ) { + PushStyleScope(ancestors[i]); + } + } +} + +void +AncestorFilter::PushAncestor(Element *aElement) +{ + MOZ_ASSERT(mFilter); + + uint32_t oldLength = mHashes.Length(); + + mPopTargets.AppendElement(oldLength); +#ifdef DEBUG + mElements.AppendElement(aElement); +#endif + mHashes.AppendElement(aElement->Tag()->hash()); + nsIAtom *id = aElement->GetID(); + if (id) { + mHashes.AppendElement(id->hash()); + } + const nsAttrValue *classes = aElement->GetClasses(); + if (classes) { + uint32_t classCount = classes->GetAtomCount(); + for (uint32_t i = 0; i < classCount; ++i) { + mHashes.AppendElement(classes->AtomAt(i)->hash()); + } + } + + uint32_t newLength = mHashes.Length(); + for (uint32_t i = oldLength; i < newLength; ++i) { + mFilter->add(mHashes[i]); + } +} + +void +AncestorFilter::PopAncestor() +{ + MOZ_ASSERT(!mPopTargets.IsEmpty()); + MOZ_ASSERT(mPopTargets.Length() == mElements.Length()); + + uint32_t popTargetLength = mPopTargets.Length(); + uint32_t newLength = mPopTargets[popTargetLength-1]; + + mPopTargets.TruncateLength(popTargetLength-1); +#ifdef DEBUG + mElements.TruncateLength(popTargetLength-1); +#endif + + uint32_t oldLength = mHashes.Length(); + for (uint32_t i = newLength; i < oldLength; ++i) { + mFilter->remove(mHashes[i]); + } + mHashes.TruncateLength(newLength); +} + +#ifdef DEBUG +void +AncestorFilter::AssertHasAllAncestors(Element *aElement) const +{ + nsINode* cur = aElement->GetParentNode(); + while (cur && cur->IsElement()) { + MOZ_ASSERT(mElements.Contains(cur)); + cur = cur->GetParentNode(); + } +} +#endif