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