From 03f29e75ecc32e718203f0820e3d78462a51bd96 Mon Sep 17 00:00:00 2001 From: Francis Dominic Fajardo Date: Thu, 17 Jul 2025 22:13:53 +0800 Subject: [PATCH] Issue #2828 - Part 3: Refactor selector matching and rule cascade data into separate files/classes --- dom/base/Element.cpp | 14 +- dom/base/nsINode.cpp | 14 +- layout/base/RestyleManager.cpp | 6 +- layout/base/nsPresContext.cpp | 6 +- layout/build/nsLayoutStatics.cpp | 6 +- layout/inspector/inDOMUtils.cpp | 30 +- layout/style/RuleCascadeData.cpp | 1783 ++++++++++++ layout/style/RuleCascadeData.h | 411 +++ layout/style/StyleRule.h | 10 + layout/style/moz.build | 8 +- layout/style/nsCSSPseudoClasses.cpp | 34 + layout/style/nsCSSPseudoClasses.h | 3 + layout/style/nsCSSRuleProcessor.cpp | 3949 +-------------------------- layout/style/nsCSSRuleProcessor.h | 78 - layout/style/nsCSSRuleUtils.cpp | 1746 ++++++++++++ layout/style/nsCSSRuleUtils.h | 137 + layout/style/nsHTMLStyleSheet.cpp | 10 +- layout/style/nsMediaFeatures.cpp | 6 +- layout/style/nsRuleProcessorData.h | 96 + layout/style/nsStyleSet.cpp | 4 +- 20 files changed, 4306 insertions(+), 4045 deletions(-) create mode 100644 layout/style/RuleCascadeData.cpp create mode 100644 layout/style/RuleCascadeData.h create mode 100644 layout/style/nsCSSRuleUtils.cpp create mode 100644 layout/style/nsCSSRuleUtils.h diff --git a/dom/base/Element.cpp b/dom/base/Element.cpp index 05d5f583ed..364022644d 100644 --- a/dom/base/Element.cpp +++ b/dom/base/Element.cpp @@ -107,7 +107,7 @@ #include "nsIScrollableFrame.h" #include "mozilla/css/StyleRule.h" /* For nsCSSSelectorList */ #include "mozilla/css/Declaration.h" -#include "nsCSSRuleProcessor.h" +#include "nsCSSRuleUtils.h" #include "nsRuleProcessorData.h" #include "nsTextNode.h" @@ -3434,9 +3434,9 @@ Element::Closest(const nsAString& aSelector, ErrorResult& aResult) matchingContext.AddScopeElement(this); for (nsINode* node = this; node; node = node->GetParentNode()) { if (node->IsElement() && - nsCSSRuleProcessor::RestrictedSelectorListMatches(node->AsElement(), - matchingContext, - selectorList)) { + nsCSSRuleUtils::RestrictedSelectorListMatches(node->AsElement(), + matchingContext, + selectorList)) { return node->AsElement(); } } @@ -3460,9 +3460,9 @@ Element::Matches(const nsAString& aSelector, ErrorResult& aError) TreeMatchContext::eNeverMatchVisited); matchingContext.SetHasSpecifiedScope(); matchingContext.AddScopeElement(this); - return nsCSSRuleProcessor::RestrictedSelectorListMatches(this, - matchingContext, - selectorList); + return nsCSSRuleUtils::RestrictedSelectorListMatches(this, + matchingContext, + selectorList); } static const nsAttrValue::EnumTable kCORSAttributeTable[] = { diff --git a/dom/base/nsINode.cpp b/dom/base/nsINode.cpp index d8c3f44708..5218938ca3 100644 --- a/dom/base/nsINode.cpp +++ b/dom/base/nsINode.cpp @@ -91,7 +91,7 @@ #include "mozilla/Preferences.h" #include "prprf.h" #include "xpcpublic.h" -#include "nsCSSRuleProcessor.h" +#include "nsCSSRuleUtils.h" #include "nsCSSParser.h" #include "HTMLLegendElement.h" #include "nsWrapperCacheInlines.h" @@ -2912,9 +2912,9 @@ FindMatchingElementsWithId(const nsAString& aId, nsINode* aRoot, // We have an element with the right id and it's a strict descendant // of aRoot. Make sure it really matches the selector. if (!aMatchInfo || - nsCSSRuleProcessor::RestrictedSelectorListMatches(element, - aMatchInfo->mMatchContext, - aMatchInfo->mSelectorList)) { + nsCSSRuleUtils::RestrictedSelectorListMatches(element, + aMatchInfo->mMatchContext, + aMatchInfo->mSelectorList)) { aList.AppendElement(element); if (onlyFirstMatch) { return; @@ -2965,9 +2965,9 @@ FindMatchingElements(nsINode* aRoot, nsCSSSelectorList* aSelectorList, T &aList, cur; cur = cur->GetNextNode(aRoot)) { if (cur->IsElement() && - nsCSSRuleProcessor::RestrictedSelectorListMatches(cur->AsElement(), - matchingContext, - aSelectorList)) { + nsCSSRuleUtils::RestrictedSelectorListMatches(cur->AsElement(), + matchingContext, + aSelectorList)) { if (onlyFirstMatch) { aList.AppendElement(cur->AsElement()); return; diff --git a/layout/base/RestyleManager.cpp b/layout/base/RestyleManager.cpp index 23eaea9355..62c9aaf606 100644 --- a/layout/base/RestyleManager.cpp +++ b/layout/base/RestyleManager.cpp @@ -47,7 +47,7 @@ #include "nsDisplayList.h" #include "RestyleTrackerInlines.h" #include "nsSMILAnimationController.h" -#include "nsCSSRuleProcessor.h" +#include "nsCSSRuleUtils.h" #include "ChildIterator.h" #include "Layers.h" @@ -2550,8 +2550,8 @@ ElementRestyler::SelectorMatchesForRestyle(Element* aElement) return false; } for (nsCSSSelector* selector : mSelectorsForDescendants) { - if (nsCSSRuleProcessor::RestrictedSelectorMatches(aElement, selector, - mTreeMatchContext)) { + if (nsCSSRuleUtils::RestrictedSelectorMatches(aElement, selector, + mTreeMatchContext)) { return true; } } diff --git a/layout/base/nsPresContext.cpp b/layout/base/nsPresContext.cpp index 367d0f8233..874f2087a3 100644 --- a/layout/base/nsPresContext.cpp +++ b/layout/base/nsPresContext.cpp @@ -35,7 +35,7 @@ #include "nsViewManager.h" #include "mozilla/RestyleManager.h" #include "SurfaceCacheUtils.h" -#include "nsCSSRuleProcessor.h" +#include "nsCSSRuleUtils.h" #include "nsRuleNode.h" #include "gfxPlatform.h" #include "nsCSSRules.h" @@ -1650,7 +1650,7 @@ nsPresContext::ThemeChangedInternal() } // This will force the system metrics to be generated the next time they're used - nsCSSRuleProcessor::FreeSystemMetrics(); + nsCSSRuleUtils::FreeSystemMetrics(); // Changes to system metrics can change media queries on them, or // :-moz-system-metric selectors (which requires eRestyle_Subtree). @@ -1691,7 +1691,7 @@ nsPresContext::SysColorChangedInternal() } // Invalidate cached '-moz-windows-accent-color-applies' media query: - nsCSSRuleProcessor::FreeSystemMetrics(); + nsCSSRuleUtils::FreeSystemMetrics(); // Reset default background and foreground colors for the document since // they may be using system colors diff --git a/layout/build/nsLayoutStatics.cpp b/layout/build/nsLayoutStatics.cpp index ffdf624e49..6eac7bd9fa 100644 --- a/layout/build/nsLayoutStatics.cpp +++ b/layout/build/nsLayoutStatics.cpp @@ -46,7 +46,7 @@ #include "nsTextFrame.h" #include "nsCCUncollectableMarker.h" #include "nsTextFragment.h" -#include "nsCSSRuleProcessor.h" +#include "nsCSSRuleUtils.h" #include "nsCORSListenerProxy.h" #include "nsHTMLDNSPrefetch.h" #include "nsHtml5Atoms.h" @@ -242,7 +242,7 @@ nsLayoutStatics::Initialize() } nsCSSParser::Startup(); - nsCSSRuleProcessor::Startup(); + nsCSSRuleUtils::Startup(); #ifdef MOZ_XUL rv = nsXULPopupManager::Init(); @@ -330,7 +330,7 @@ nsLayoutStatics::Shutdown() EventListenerManager::Shutdown(); IMEStateManager::Shutdown(); nsCSSParser::Shutdown(); - nsCSSRuleProcessor::Shutdown(); + nsCSSRuleUtils::Shutdown(); nsHTMLDNSPrefetch::Shutdown(); nsCSSRendering::Shutdown(); StaticPresData::Shutdown(); diff --git a/layout/inspector/inDOMUtils.cpp b/layout/inspector/inDOMUtils.cpp index 793e973e3b..2f527855d6 100644 --- a/layout/inspector/inDOMUtils.cpp +++ b/layout/inspector/inDOMUtils.cpp @@ -40,7 +40,7 @@ #include "nsRuleWalker.h" #include "nsRuleProcessorData.h" #include "nsCSSPseudoClasses.h" -#include "nsCSSRuleProcessor.h" +#include "nsCSSRuleUtils.h" #include "mozilla/dom/CSSLexer.h" #include "mozilla/dom/InspectorUtilsBinding.h" #include "mozilla/dom/ToJSValue.h" @@ -467,9 +467,9 @@ inDOMUtils::SelectorMatchesElement(nsIDOMElement* aElement, nsRuleWalker::eRelevantLinkUnvisited, element->OwnerDoc(), TreeMatchContext::eNeverMatchVisited); - *aMatches = nsCSSRuleProcessor::RestrictedSelectorListMatches(element, - matchingContext, - sel); + *aMatches = nsCSSRuleUtils::RestrictedSelectorListMatches(element, + matchingContext, + sel); return NS_OK; } @@ -1183,26 +1183,6 @@ inDOMUtils::GetUsedFontFaces(nsIDOMRange* aRange, static EventStates GetStatesForPseudoClass(const nsAString& aStatePseudo) { - // An array of the states that are relevant for various pseudoclasses. - // XXXbz this duplicates code in nsCSSRuleProcessor - static const EventStates sPseudoClassStates[] = { -#define CSS_PSEUDO_CLASS(_name, _value, _flags, _pref) \ - EventStates(), -#define CSS_STATE_PSEUDO_CLASS(_name, _value, _flags, _pref, _states) \ - _states, -#include "nsCSSPseudoClassList.h" -#undef CSS_STATE_PSEUDO_CLASS -#undef CSS_PSEUDO_CLASS - - // Add more entries for our fake values to make sure we can't - // index out of bounds into this array no matter what. - EventStates(), - EventStates() - }; - static_assert(MOZ_ARRAY_LENGTH(sPseudoClassStates) == - static_cast(CSSPseudoClassType::MAX), - "Length of PseudoClassStates array is incorrect"); - nsCOMPtr atom = NS_Atomize(aStatePseudo); CSSPseudoClassType type = nsCSSPseudoClasses:: GetPseudoType(atom, CSSEnabledState::eIgnoreEnabledState); @@ -1215,7 +1195,7 @@ GetStatesForPseudoClass(const nsAString& aStatePseudo) } // Our array above is long enough that indexing into it with // NotPseudo is ok. - return sPseudoClassStates[static_cast(type)]; + return nsCSSPseudoClasses::sPseudoClassStates[static_cast(type)]; } NS_IMETHODIMP diff --git a/layout/style/RuleCascadeData.cpp b/layout/style/RuleCascadeData.cpp new file mode 100644 index 0000000000..851446bf5e --- /dev/null +++ b/layout/style/RuleCascadeData.cpp @@ -0,0 +1,1783 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#define PL_ARENA_CONST_ALIGN_MASK 7 +// We want page-sized arenas so there's no fragmentation involved. +// Including plarena.h must come first to avoid it being included by some +// header file thereby making PL_ARENA_CONST_ALIGN_MASK ineffective. +#define NS_CASCADEENUMDATA_ARENA_BLOCK_SIZE (4096) +#include "plarena.h" + +#include "RuleCascadeData.h" +#include "nsAutoPtr.h" +#include "nsCSSRuleProcessor.h" +#include "nsRuleProcessorData.h" +#include +#include "nsIAtom.h" +#include "PLDHashTable.h" +#include "nsICSSPseudoComparator.h" +#include "mozilla/MemoryReporting.h" +#include "mozilla/css/GroupRule.h" +#include "nsIDocument.h" +#include "nsPresContext.h" +#include "nsGkAtoms.h" +#include "nsUnicharUtils.h" +#include "nsError.h" +#include "nsRuleWalker.h" +#include "nsCSSPseudoClasses.h" +#include "nsCSSPseudoElements.h" +#include "nsIContent.h" +#include "nsCOMPtr.h" +#include "nsHashKeys.h" +#include "nsStyleUtil.h" +#include "nsQuickSort.h" +#include "nsAttrValue.h" +#include "nsAttrValueInlines.h" +#include "nsAttrName.h" +#include "nsTArray.h" +#include "nsIMediaList.h" +#include "nsCSSRules.h" +#include "nsStyleSet.h" +#include "mozilla/dom/Element.h" +#include "mozilla/dom/HTMLSlotElement.h" +#include "mozilla/dom/ShadowRoot.h" +#include "nsNthIndexCache.h" +#include "mozilla/ArrayUtils.h" +#include "mozilla/EventStates.h" +#include "mozilla/Preferences.h" +#include "mozilla/LookAndFeel.h" +#include "mozilla/Likely.h" +#include "mozilla/OperatorNewExtensions.h" +#include "mozilla/TypedEnumBits.h" +#include "RuleProcessorCache.h" +#include "nsIDOMMutationEvent.h" +#include "nsIMozBrowserFrame.h" +#include "nsCSSRuleUtils.h" + +using namespace mozilla; +using namespace mozilla::dom; + +// ------------------------------ +// Rule hash table +// + +// Uses any of the sets of ops below. +struct RuleHashTableEntry : public PLDHashEntryHdr +{ + // If you add members that have heap allocated memory be sure to change the + // logic in SizeOfRuleHashTable(). + // Auto length 1, because we always have at least one entry in mRules. + AutoTArray mRules; +}; + +struct RuleHashTagTableEntry : public RuleHashTableEntry +{ + // If you add members that have heap allocated memory be sure to change the + // logic in RuleHash::SizeOf{In,Ex}cludingThis. + nsCOMPtr mTag; +}; + +static PLDHashNumber +RuleHash_CIHashKey(const void* key) +{ + nsIAtom* atom = const_cast(static_cast(key)); + + nsAutoString str; + atom->ToString(str); + nsContentUtils::ASCIIToLower(str); + return HashString(str); +} + +static inline nsCSSSelector* +SubjectSelectorForRuleHash(const PLDHashEntryHdr* hdr) +{ + auto entry = static_cast(hdr); + nsCSSSelector* selector = entry->mRules[0].mSelector; + if (selector->IsPseudoElement()) { + selector = selector->mNext; + } + return selector; +} + +static inline bool +CIMatchAtoms(const void* key, nsIAtom* entry_atom) +{ + auto match_atom = const_cast(static_cast(key)); + + // Check for case-sensitive match first. + if (match_atom == entry_atom) { + return true; + } + + // Use EqualsIgnoreASCIICase instead of full on unicode case conversion + // in order to save on performance. This is only used in quirks mode + // anyway. + return nsContentUtils::EqualsIgnoreASCIICase( + nsDependentAtomString(entry_atom), nsDependentAtomString(match_atom)); +} + +static inline bool +CSMatchAtoms(const void* key, nsIAtom* entry_atom) +{ + auto match_atom = const_cast(static_cast(key)); + return match_atom == entry_atom; +} + +static bool +RuleHash_ClassCIMatchEntry(const PLDHashEntryHdr* hdr, const void* key) +{ + return CIMatchAtoms(key, SubjectSelectorForRuleHash(hdr)->mClassList->mAtom); +} + +static bool +RuleHash_IdCIMatchEntry(const PLDHashEntryHdr* hdr, const void* key) +{ + return CIMatchAtoms(key, SubjectSelectorForRuleHash(hdr)->mIDList->mAtom); +} + +static bool +RuleHash_ClassCSMatchEntry(const PLDHashEntryHdr* hdr, const void* key) +{ + return CSMatchAtoms(key, SubjectSelectorForRuleHash(hdr)->mClassList->mAtom); +} + +static bool +RuleHash_IdCSMatchEntry(const PLDHashEntryHdr* hdr, const void* key) +{ + return CSMatchAtoms(key, SubjectSelectorForRuleHash(hdr)->mIDList->mAtom); +} + +static void +RuleHash_InitEntry(PLDHashEntryHdr* hdr, const void* key) +{ + RuleHashTableEntry* entry = static_cast(hdr); + new (KnownNotNull, entry) RuleHashTableEntry(); +} + +static void +RuleHash_ClearEntry(PLDHashTable* table, PLDHashEntryHdr* hdr) +{ + RuleHashTableEntry* entry = static_cast(hdr); + entry->~RuleHashTableEntry(); +} + +static void +RuleHash_MoveEntry(PLDHashTable* table, + const PLDHashEntryHdr* from, + PLDHashEntryHdr* to) +{ + NS_PRECONDITION(from != to, "This is not going to work!"); + RuleHashTableEntry* oldEntry = const_cast( + static_cast(from)); + auto* newEntry = new (KnownNotNull, to) RuleHashTableEntry(); + newEntry->mRules.SwapElements(oldEntry->mRules); + oldEntry->~RuleHashTableEntry(); +} + +static bool +RuleHash_TagTable_MatchEntry(const PLDHashEntryHdr* hdr, const void* key) +{ + nsIAtom* match_atom = const_cast(static_cast(key)); + nsIAtom* entry_atom = static_cast(hdr)->mTag; + + return match_atom == entry_atom; +} + +static void +RuleHash_TagTable_InitEntry(PLDHashEntryHdr* hdr, const void* key) +{ + RuleHashTagTableEntry* entry = static_cast(hdr); + new (KnownNotNull, entry) RuleHashTagTableEntry(); + entry->mTag = const_cast(static_cast(key)); +} + +static void +RuleHash_TagTable_ClearEntry(PLDHashTable* table, PLDHashEntryHdr* hdr) +{ + RuleHashTagTableEntry* entry = static_cast(hdr); + entry->~RuleHashTagTableEntry(); +} + +static void +RuleHash_TagTable_MoveEntry(PLDHashTable* table, + const PLDHashEntryHdr* from, + PLDHashEntryHdr* to) +{ + NS_PRECONDITION(from != to, "This is not going to work!"); + RuleHashTagTableEntry* oldEntry = const_cast( + static_cast(from)); + auto* newEntry = new (KnownNotNull, to) RuleHashTagTableEntry(); + newEntry->mTag.swap(oldEntry->mTag); + newEntry->mRules.SwapElements(oldEntry->mRules); + oldEntry->~RuleHashTagTableEntry(); +} + +static PLDHashNumber +RuleHash_NameSpaceTable_HashKey(const void* key) +{ + return NS_PTR_TO_INT32(key); +} + +static bool +RuleHash_NameSpaceTable_MatchEntry(const PLDHashEntryHdr* hdr, const void* key) +{ + const RuleHashTableEntry* entry = static_cast(hdr); + + nsCSSSelector* selector = entry->mRules[0].mSelector; + if (selector->IsPseudoElement()) { + selector = selector->mNext; + } + return NS_PTR_TO_INT32(key) == selector->mNameSpace; +} + +/* static */ const PLDHashTableOps RuleHash::TagTable_Ops = { + PLDHashTable::HashVoidPtrKeyStub, + RuleHash_TagTable_MatchEntry, + RuleHash_TagTable_MoveEntry, + RuleHash_TagTable_ClearEntry, + RuleHash_TagTable_InitEntry +}; + +// Case-sensitive ops. +/* static */ const PLDHashTableOps RuleHash::ClassTable_CSOps = { + PLDHashTable::HashVoidPtrKeyStub, + RuleHash_ClassCSMatchEntry, + RuleHash_MoveEntry, + RuleHash_ClearEntry, + RuleHash_InitEntry +}; + +// Case-insensitive ops. +/* static */ const PLDHashTableOps RuleHash::ClassTable_CIOps = { + RuleHash_CIHashKey, + RuleHash_ClassCIMatchEntry, + RuleHash_MoveEntry, + RuleHash_ClearEntry, + RuleHash_InitEntry +}; + +// Case-sensitive ops. +/* static */ const PLDHashTableOps RuleHash::IdTable_CSOps = { + PLDHashTable::HashVoidPtrKeyStub, + RuleHash_IdCSMatchEntry, + RuleHash_MoveEntry, + RuleHash_ClearEntry, + RuleHash_InitEntry +}; + +// Case-insensitive ops. +/* static */ const PLDHashTableOps RuleHash::IdTable_CIOps = { + RuleHash_CIHashKey, + RuleHash_IdCIMatchEntry, + RuleHash_MoveEntry, + RuleHash_ClearEntry, + RuleHash_InitEntry +}; + +/* static */ const PLDHashTableOps RuleHash::NameSpaceTable_Ops = { + RuleHash_NameSpaceTable_HashKey, + RuleHash_NameSpaceTable_MatchEntry, + RuleHash_MoveEntry, + RuleHash_ClearEntry, + RuleHash_InitEntry +}; + +#undef RULE_HASH_STATS +#undef PRINT_UNIVERSAL_RULES + +#ifdef RULE_HASH_STATS +#define RULE_HASH_STAT_INCREMENT(var_) \ + PR_BEGIN_MACRO++(var_); \ + PR_END_MACRO +#else +#define RULE_HASH_STAT_INCREMENT(var_) PR_BEGIN_MACRO PR_END_MACRO +#endif + +RuleHash::RuleHash(bool aQuirksMode) + : mRuleCount(0) + , mIdTable(aQuirksMode ? &RuleHash::IdTable_CIOps : &RuleHash::IdTable_CSOps, + sizeof(RuleHashTableEntry)) + , mClassTable(aQuirksMode ? &RuleHash::ClassTable_CIOps + : &RuleHash::ClassTable_CSOps, + sizeof(RuleHashTableEntry)) + , mTagTable(&RuleHash::TagTable_Ops, sizeof(RuleHashTagTableEntry)) + , mNameSpaceTable(&RuleHash::NameSpaceTable_Ops, sizeof(RuleHashTableEntry)) + , mUniversalRules(0) + , mEnumList(nullptr) + , mEnumListSize(0) + , mQuirksMode(aQuirksMode) +#ifdef RULE_HASH_STATS + , mUniversalSelectors(0) + , mNameSpaceSelectors(0) + , mTagSelectors(0) + , mClassSelectors(0) + , mIdSelectors(0) + , mElementsMatched(0) + , mElementUniversalCalls(0) + , mElementNameSpaceCalls(0) + , mElementTagCalls(0) + , mElementClassCalls(0) + , mElementIdCalls(0) +#endif +{ + MOZ_COUNT_CTOR(RuleHash); +} + +RuleHash::~RuleHash() +{ + MOZ_COUNT_DTOR(RuleHash); +#ifdef RULE_HASH_STATS + printf("RuleHash(%p):\n" + " Selectors: Universal (%u) NameSpace(%u) Tag(%u) Class(%u) Id(%u)\n" + " Content Nodes: Elements(%u)\n" + " Element Calls: Universal(%u) NameSpace(%u) Tag(%u) Class(%u) " + "Id(%u)\n" static_cast(this), + mUniversalSelectors, + mNameSpaceSelectors, + mTagSelectors, + mClassSelectors, + mIdSelectors, + mElementsMatched, + mElementUniversalCalls, + mElementNameSpaceCalls, + mElementTagCalls, + mElementClassCalls, + mElementIdCalls); +#ifdef PRINT_UNIVERSAL_RULES + { + if (mUniversalRules.Length() > 0) { + printf(" Universal rules:\n"); + for (uint32_t i = 0; i < mUniversalRules.Length(); ++i) { + RuleValue* value = &(mUniversalRules[i]); + nsAutoString selectorText; + uint32_t lineNumber = value->mRule->GetLineNumber(); + RefPtr cssSheet = value->mRule->GetStyleSheet(); + value->mSelector->ToString(selectorText, cssSheet); + + printf(" line %d, %s\n", + lineNumber, + NS_ConvertUTF16toUTF8(selectorText).get()); + } + } + } +#endif // PRINT_UNIVERSAL_RULES +#endif // RULE_HASH_STATS + // Rule Values are arena allocated no need to delete them. Their destructor + // isn't doing any cleanup. So we dont even bother to enumerate through + // the hash tables and call their destructors. + if (nullptr != mEnumList) { + delete[] mEnumList; + } +} + +void +RuleHash::AppendRuleToTable(PLDHashTable* aTable, + const void* aKey, + const RuleSelectorPair& aRuleInfo) +{ + // Get a new or existing entry. + auto entry = static_cast(aTable->Add(aKey, fallible)); + if (!entry) + return; + entry->mRules.AppendElement(RuleValue(aRuleInfo, mRuleCount++, mQuirksMode)); +} + +/* static */ void +RuleHash::AppendRuleToTagTable(PLDHashTable* aTable, + nsIAtom* aKey, + const RuleValue& aRuleInfo) +{ + // Get a new or exisiting entry + auto entry = static_cast(aTable->Add(aKey, fallible)); + if (!entry) + return; + + entry->mRules.AppendElement(aRuleInfo); +} + +void +RuleHash::AppendUniversalRule(const RuleSelectorPair& aRuleInfo) +{ + mUniversalRules.AppendElement( + RuleValue(aRuleInfo, mRuleCount++, mQuirksMode)); +} + +void +RuleHash::AppendRule(const RuleSelectorPair& aRuleInfo) +{ + nsCSSSelector* selector = aRuleInfo.mSelector; + if (selector->IsPseudoElement()) { + selector = selector->mNext; + } + if (nullptr != selector->mIDList) { + AppendRuleToTable(&mIdTable, selector->mIDList->mAtom, aRuleInfo); + RULE_HASH_STAT_INCREMENT(mIdSelectors); + } else if (nullptr != selector->mClassList) { + AppendRuleToTable(&mClassTable, selector->mClassList->mAtom, aRuleInfo); + RULE_HASH_STAT_INCREMENT(mClassSelectors); + } else if (selector->mLowercaseTag) { + RuleValue ruleValue(aRuleInfo, mRuleCount++, mQuirksMode); + AppendRuleToTagTable(&mTagTable, selector->mLowercaseTag, ruleValue); + RULE_HASH_STAT_INCREMENT(mTagSelectors); + if (selector->mCasedTag && selector->mCasedTag != selector->mLowercaseTag) { + AppendRuleToTagTable(&mTagTable, selector->mCasedTag, ruleValue); + RULE_HASH_STAT_INCREMENT(mTagSelectors); + } + } else if (kNameSpaceID_Unknown != selector->mNameSpace) { + AppendRuleToTable( + &mNameSpaceTable, NS_INT32_TO_PTR(selector->mNameSpace), aRuleInfo); + RULE_HASH_STAT_INCREMENT(mNameSpaceSelectors); + } else { // universal tag selector + AppendUniversalRule(aRuleInfo); + RULE_HASH_STAT_INCREMENT(mUniversalSelectors); + } +} + +// this should cover practically all cases so we don't need to reallocate +#define MIN_ENUM_LIST_SIZE 8 + +#ifdef RULE_HASH_STATS +#define RULE_HASH_STAT_INCREMENT_LIST_COUNT(list_, var_) \ + (var_) += (list_).Length() +#else +#define RULE_HASH_STAT_INCREMENT_LIST_COUNT(list_, var_) \ + PR_BEGIN_MACRO PR_END_MACRO +#endif + +static inline bool +LookForTargetPseudo(nsCSSSelector* aSelector, + TreeMatchContext* aMatchContext, + nsRestyleHint* possibleChange) +{ + if (aMatchContext->mOnlyMatchHostPseudo) { + while (aSelector && aSelector->mNext != nullptr) { + aSelector = aSelector->mNext; + } + + for (nsPseudoClassList* pseudoClass = aSelector->mPseudoClassList; + pseudoClass; + pseudoClass = pseudoClass->mNext) { + if (pseudoClass->mType == CSSPseudoClassType::host || + pseudoClass->mType == CSSPseudoClassType::hostContext) { + if (possibleChange) { + // :host-context will walk ancestors looking for a match of a + // compound selector, thus any changes to ancestors may require + // restyling the subtree. + *possibleChange |= eRestyle_Subtree; + } + return true; + } + } + return false; + } else if (aMatchContext->mRestrictToSlottedPseudo) { + for (nsCSSSelector* selector = aSelector; selector; + selector = selector->mNext) { + if (!selector->mPseudoClassList) { + continue; + } + for (nsPseudoClassList* pseudoClass = selector->mPseudoClassList; + pseudoClass; + pseudoClass = pseudoClass->mNext) { + if (pseudoClass->mType == CSSPseudoClassType::slotted) { + return true; + } + } + } + return false; + } + // We're not restricted to a specific pseudo-class. + return true; +} + +static inline void +ContentEnumFunc(const RuleValue& value, + nsCSSSelector* aSelector, + ElementDependentRuleProcessorData* data, + NodeMatchContext& nodeContext, + AncestorFilter* ancestorFilter) +{ + if (nodeContext.mIsRelevantLink) { + data->mTreeMatchContext.SetHaveRelevantLink(); + } + // XXX: Ignore the ancestor filter if we're testing the assigned slot. + bool useAncestorFilter = !(data->mTreeMatchContext.mForAssignedSlot); + if (useAncestorFilter && ancestorFilter && + !ancestorFilter->MightHaveMatchingAncestor( + value.mAncestorSelectorHashes)) { + // We won't match; nothing else to do here + return; + } + + if (!LookForTargetPseudo(aSelector, &data->mTreeMatchContext, nullptr)) { + return; + } + + if (!data->mTreeMatchContext.SetStyleScopeForSelectorMatching(data->mElement, + data->mScope)) { + // The selector is for a rule in a scoped style sheet, and the subject + // of the selector matching is not in its scope. + return; + } + nsCSSSelector* selector = aSelector; + if (selector->IsPseudoElement()) { + PseudoElementRuleProcessorData* pdata = + static_cast(data); + if (!pdata->mPseudoElement && selector->mPseudoClassList) { + // We can get here when calling getComputedStyle(aElt, aPseudo) if: + // + // * aPseudo is a pseudo-element that supports a user action + // pseudo-class, like "::placeholder"; + // * there is a style rule that uses a pseudo-class on this + // pseudo-element in the document, like ::placeholder:hover; and + // * aElt does not have such a pseudo-element. + // + // We know that the selector can't match, since there is no element for + // the user action pseudo-class to match against. + return; + } + if (!nsCSSRuleUtils::StateSelectorMatches(pdata->mPseudoElement, + aSelector, + nodeContext, + data->mTreeMatchContext, + SelectorMatchesFlags::NONE)) { + return; + } + selector = selector->mNext; + } + + SelectorMatchesFlags selectorFlags = SelectorMatchesFlags::NONE; + if (aSelector->IsPseudoElement()) { + selectorFlags |= SelectorMatchesFlags::HAS_PSEUDO_ELEMENT; + } + if (nsCSSRuleUtils::SelectorMatches(data->mElement, + selector, + nodeContext, + data->mTreeMatchContext, + selectorFlags)) { + nsCSSSelector* next = selector->mNext; + if (!next || nsCSSRuleUtils::SelectorMatchesTree( + data->mElement, + next, + data->mTreeMatchContext, + nodeContext.mIsRelevantLink ? SelectorMatchesTreeFlags(0) + : eLookForRelevantLink)) { + css::Declaration* declaration = value.mRule->GetDeclaration(); + declaration->SetImmutable(); + data->mRuleWalker->Forward(declaration); + // nsStyleSet will deal with the !important rule + } + } +} + +void +RuleHash::EnumerateAllRules(Element* aElement, + ElementDependentRuleProcessorData* aData, + NodeMatchContext& aNodeContext) +{ + int32_t nameSpace = aElement->GetNameSpaceID(); + nsIAtom* tag = aElement->NodeInfo()->NameAtom(); + nsIAtom* id = aElement->GetID(); + const nsAttrValue* classList = aElement->GetClasses(); + + MOZ_ASSERT(tag, "How could we not have a tag?"); + + int32_t classCount = classList ? classList->GetAtomCount() : 0; + + // assume 1 universal, tag, id, and namespace, rather than wasting + // time counting + int32_t testCount = classCount + 4; + + if (mEnumListSize < testCount) { + delete[] mEnumList; + mEnumListSize = std::max(testCount, MIN_ENUM_LIST_SIZE); + mEnumList = new EnumData[mEnumListSize]; + } + + int32_t valueCount = 0; + RULE_HASH_STAT_INCREMENT(mElementsMatched); + + if (mUniversalRules.Length() != 0) { // universal rules + mEnumList[valueCount++] = ToEnumData(mUniversalRules); + RULE_HASH_STAT_INCREMENT_LIST_COUNT(mUniversalRules, + mElementUniversalCalls); + } + // universal rules within the namespace + if (kNameSpaceID_Unknown != nameSpace && mNameSpaceTable.EntryCount() > 0) { + auto entry = static_cast( + mNameSpaceTable.Search(NS_INT32_TO_PTR(nameSpace))); + if (entry) { + mEnumList[valueCount++] = ToEnumData(entry->mRules); + RULE_HASH_STAT_INCREMENT_LIST_COUNT(entry->mRules, + mElementNameSpaceCalls); + } + } + if (mTagTable.EntryCount() > 0) { + auto entry = static_cast(mTagTable.Search(tag)); + if (entry) { + mEnumList[valueCount++] = ToEnumData(entry->mRules); + RULE_HASH_STAT_INCREMENT_LIST_COUNT(entry->mRules, mElementTagCalls); + } + } + if (id && mIdTable.EntryCount() > 0) { + auto entry = static_cast(mIdTable.Search(id)); + if (entry) { + mEnumList[valueCount++] = ToEnumData(entry->mRules); + RULE_HASH_STAT_INCREMENT_LIST_COUNT(entry->mRules, mElementIdCalls); + } + } + if (mClassTable.EntryCount() > 0) { + for (int32_t index = 0; index < classCount; ++index) { + auto entry = static_cast( + mClassTable.Search(classList->AtomAt(index))); + if (entry) { + mEnumList[valueCount++] = ToEnumData(entry->mRules); + RULE_HASH_STAT_INCREMENT_LIST_COUNT(entry->mRules, mElementClassCalls); + } + } + } + NS_ASSERTION(valueCount <= testCount, "values exceeded list size"); + + if (valueCount > 0) { + AncestorFilter* filter = + aData->mTreeMatchContext.mAncestorFilter.HasFilter() + ? &aData->mTreeMatchContext.mAncestorFilter + : nullptr; +#ifdef DEBUG + bool isRestricted = (aData->mTreeMatchContext.mShadowHosts.Length() > 0 || + aData->mTreeMatchContext.mRestrictToSlottedPseudo || + aData->mTreeMatchContext.mOnlyMatchHostPseudo || + aData->mTreeMatchContext.mForAssignedSlot); + if (filter && !isRestricted) { + filter->AssertHasAllAncestors(aElement); + } +#endif + bool isForAssignedSlot = aData->mTreeMatchContext.mForAssignedSlot; + // Merge the lists while there are still multiple lists to merge. + while (valueCount > 1) { + int32_t valueIndex = 0; + int32_t lowestRuleIndex = mEnumList[valueIndex].mCurValue->mIndex; + for (int32_t index = 1; index < valueCount; ++index) { + int32_t ruleIndex = mEnumList[index].mCurValue->mIndex; + if (ruleIndex < lowestRuleIndex) { + valueIndex = index; + lowestRuleIndex = ruleIndex; + } + } + const RuleValue* cur = mEnumList[valueIndex].mCurValue; + aData->mTreeMatchContext.mForAssignedSlot = isForAssignedSlot; + ContentEnumFunc(*cur, cur->mSelector, aData, aNodeContext, filter); + cur++; + if (cur == mEnumList[valueIndex].mEnd) { + mEnumList[valueIndex] = mEnumList[--valueCount]; + } else { + mEnumList[valueIndex].mCurValue = cur; + } + } + + // Fast loop over single value. + for (const RuleValue *value = mEnumList[0].mCurValue, + *end = mEnumList[0].mEnd; + value != end; + ++value) { + aData->mTreeMatchContext.mForAssignedSlot = isForAssignedSlot; + ContentEnumFunc(*value, value->mSelector, aData, aNodeContext, filter); + } + } +} + +static size_t +SizeOfRuleHashTable(const PLDHashTable& aTable, MallocSizeOf aMallocSizeOf) +{ + size_t n = aTable.ShallowSizeOfExcludingThis(aMallocSizeOf); + for (auto iter = aTable.ConstIter(); !iter.Done(); iter.Next()) { + auto entry = static_cast(iter.Get()); + n += entry->mRules.ShallowSizeOfExcludingThis(aMallocSizeOf); + } + return n; +} + +size_t +RuleHash::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const +{ + size_t n = 0; + + n += SizeOfRuleHashTable(mIdTable, aMallocSizeOf); + + n += SizeOfRuleHashTable(mClassTable, aMallocSizeOf); + + n += SizeOfRuleHashTable(mTagTable, aMallocSizeOf); + + n += SizeOfRuleHashTable(mNameSpaceTable, aMallocSizeOf); + + n += mUniversalRules.ShallowSizeOfExcludingThis(aMallocSizeOf); + + return n; +} + +size_t +RuleHash::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const +{ + return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf); +} + +//-------------------------------- + +// A hash table mapping atoms to lists of selectors +struct AtomSelectorEntry : public PLDHashEntryHdr +{ + nsIAtom* mAtom; + // Auto length 2, because a decent fraction of these arrays ends up + // with 2 elements, and each entry is cheap. + AutoTArray mSelectors; +}; + +static void +AtomSelector_ClearEntry(PLDHashTable* table, PLDHashEntryHdr* hdr) +{ + (static_cast(hdr))->~AtomSelectorEntry(); +} + +static void +AtomSelector_InitEntry(PLDHashEntryHdr* hdr, const void* key) +{ + AtomSelectorEntry* entry = static_cast(hdr); + new (KnownNotNull, entry) AtomSelectorEntry(); + entry->mAtom = const_cast(static_cast(key)); +} + +static void +AtomSelector_MoveEntry(PLDHashTable* table, + const PLDHashEntryHdr* from, + PLDHashEntryHdr* to) +{ + NS_PRECONDITION(from != to, "This is not going to work!"); + AtomSelectorEntry* oldEntry = + const_cast(static_cast(from)); + auto* newEntry = new (KnownNotNull, to) AtomSelectorEntry(); + newEntry->mAtom = oldEntry->mAtom; + newEntry->mSelectors.SwapElements(oldEntry->mSelectors); + oldEntry->~AtomSelectorEntry(); +} + +static bool +AtomSelector_CIMatchEntry(const PLDHashEntryHdr* hdr, const void* key) +{ + const AtomSelectorEntry* entry = static_cast(hdr); + return CIMatchAtoms(key, entry->mAtom); +} + +// Case-sensitive ops. +/* static */ const PLDHashTableOps RuleCascadeData::AtomSelector_CSOps = { + PLDHashTable::HashVoidPtrKeyStub, + PLDHashTable::MatchEntryStub, + AtomSelector_MoveEntry, + AtomSelector_ClearEntry, + AtomSelector_InitEntry +}; + +// Case-insensitive ops. +/* static */ const PLDHashTableOps RuleCascadeData::AtomSelector_CIOps = { + RuleHash_CIHashKey, + AtomSelector_CIMatchEntry, + AtomSelector_MoveEntry, + AtomSelector_ClearEntry, + AtomSelector_InitEntry +}; + +RuleCascadeData::RuleCascadeData(nsIAtom* aMedium, bool aQuirksMode) + : mRuleHash(aQuirksMode) + , mStateSelectors() + , mSelectorDocumentStates(0) + , mClassSelectors(aQuirksMode ? &AtomSelector_CIOps : &AtomSelector_CSOps, + sizeof(AtomSelectorEntry)) + , mIdSelectors(aQuirksMode ? &AtomSelector_CIOps : &AtomSelector_CSOps, + sizeof(AtomSelectorEntry)) + , + // mAttributeSelectors is matching on the attribute _name_, not the + // value, and we case-fold names at parse-time, so this is a + // case-sensitive match. + mAttributeSelectors(&AtomSelector_CSOps, sizeof(AtomSelectorEntry)) + , mAnonBoxRules(&RuleHash::TagTable_Ops, sizeof(RuleHashTagTableEntry)) + , mXULTreeRules(&RuleHash::TagTable_Ops, sizeof(RuleHashTagTableEntry)) + , mKeyframesRuleTable() + , mCounterStyleRuleTable() + , mCacheKey(aMedium) + , mQuirksMode(aQuirksMode) +{ + memset(mPseudoElementRuleHashes, 0, sizeof(mPseudoElementRuleHashes)); +} + +RuleCascadeData::~RuleCascadeData() +{ + for (uint32_t i = 0; i < ArrayLength(mPseudoElementRuleHashes); ++i) { + delete mPseudoElementRuleHashes[i]; + } +} + +static size_t +SizeOfSelectorsHashTable(const PLDHashTable& aTable, MallocSizeOf aMallocSizeOf) +{ + size_t n = aTable.ShallowSizeOfExcludingThis(aMallocSizeOf); + for (auto iter = aTable.ConstIter(); !iter.Done(); iter.Next()) { + auto entry = static_cast(iter.Get()); + n += entry->mSelectors.ShallowSizeOfExcludingThis(aMallocSizeOf); + } + return n; +} + +size_t +RuleCascadeData::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const +{ + size_t n = aMallocSizeOf(this); + + n += mRuleHash.SizeOfExcludingThis(aMallocSizeOf); + for (uint32_t i = 0; i < ArrayLength(mPseudoElementRuleHashes); ++i) { + if (mPseudoElementRuleHashes[i]) + n += mPseudoElementRuleHashes[i]->SizeOfIncludingThis(aMallocSizeOf); + } + + n += mStateSelectors.ShallowSizeOfExcludingThis(aMallocSizeOf); + + n += SizeOfSelectorsHashTable(mIdSelectors, aMallocSizeOf); + n += SizeOfSelectorsHashTable(mClassSelectors, aMallocSizeOf); + + n += mPossiblyNegatedClassSelectors.ShallowSizeOfExcludingThis(aMallocSizeOf); + n += mPossiblyNegatedIDSelectors.ShallowSizeOfExcludingThis(aMallocSizeOf); + + n += SizeOfSelectorsHashTable(mAttributeSelectors, aMallocSizeOf); + n += SizeOfRuleHashTable(mAnonBoxRules, aMallocSizeOf); + n += SizeOfRuleHashTable(mXULTreeRules, aMallocSizeOf); + + n += mFontFaceRules.ShallowSizeOfExcludingThis(aMallocSizeOf); + n += mKeyframesRules.ShallowSizeOfExcludingThis(aMallocSizeOf); + n += mFontFeatureValuesRules.ShallowSizeOfExcludingThis(aMallocSizeOf); + n += mPageRules.ShallowSizeOfExcludingThis(aMallocSizeOf); + n += mCounterStyleRules.ShallowSizeOfExcludingThis(aMallocSizeOf); + + n += mKeyframesRuleTable.ShallowSizeOfExcludingThis(aMallocSizeOf); + for (auto iter = mKeyframesRuleTable.ConstIter(); !iter.Done(); iter.Next()) { + // We don't own the nsCSSKeyframesRule objects so we don't count them. We + // do care about the size of the keys' nsAString members' buffers though. + // + // Note that we depend on nsStringHashKey::GetKey() returning a reference, + // since otherwise aKey would be a copy of the string key and we would not + // be measuring the right object here. + n += iter.Key().SizeOfExcludingThisIfUnshared(aMallocSizeOf); + } + + return n; +} + +nsTArray* +RuleCascadeData::AttributeListFor(nsIAtom* aAttribute) +{ + auto entry = static_cast( + mAttributeSelectors.Add(aAttribute, fallible)); + if (!entry) { + return nullptr; + } + return &entry->mSelectors; +} + +void +RuleCascadeData::RulesMatching(ElementRuleProcessorData* aData) +{ + NodeMatchContext nodeContext(EventStates(), + nsCSSRuleUtils::IsLink(aData->mElement), + aData->mElementIsFeatureless); + // Test against the assigned slot rather than the slottable if we're + // matching the ::slotted() pseudo. + Element* targetElement = aData->mElement; + if (aData->mTreeMatchContext.mForAssignedSlot) { + targetElement = aData->mElement->GetAssignedSlot()->AsElement(); + } + mRuleHash.EnumerateAllRules(targetElement, aData, nodeContext); +} + +void +RuleCascadeData::RulesMatching(PseudoElementRuleProcessorData* aData) +{ + RuleHash* ruleHash = + mPseudoElementRuleHashes[static_cast( + aData->mPseudoType)]; + if (ruleHash) { + NodeMatchContext nodeContext( + EventStates(), nsCSSRuleUtils::IsLink(aData->mElement)); + ruleHash->EnumerateAllRules(aData->mElement, aData, nodeContext); + } +} + +void +RuleCascadeData::RulesMatching(AnonBoxRuleProcessorData* aData) +{ + if (mAnonBoxRules.EntryCount() == 0) { + return; + } + auto entry = static_cast( + mAnonBoxRules.Search(aData->mPseudoTag)); + if (entry) { + nsTArray& rules = entry->mRules; + for (RuleValue *value = rules.Elements(), + *end = value + rules.Length(); + value != end; + ++value) { + css::Declaration* declaration = value->mRule->GetDeclaration(); + declaration->SetImmutable(); + aData->mRuleWalker->Forward(declaration); + } + } +} + +void +RuleCascadeData::RulesMatching(XULTreeRuleProcessorData* aData) +{ + if (mXULTreeRules.EntryCount() == 0) { + return; + } + auto entry = static_cast( + mXULTreeRules.Search(aData->mPseudoTag)); + if (entry) { + NodeMatchContext nodeContext( + EventStates(), nsCSSRuleUtils::IsLink(aData->mElement)); + nsTArray& rules = entry->mRules; + for (RuleValue *value = rules.Elements(), *end = value + rules.Length(); + value != end; + ++value) { + if (aData->mComparator->PseudoMatches(value->mSelector)) { + ContentEnumFunc( + *value, value->mSelector->mNext, aData, nodeContext, nullptr); + } + } + } +} + +static inline nsRestyleHint +RestyleHintForOp(char16_t oper) +{ + if (oper == char16_t('+') || oper == char16_t('~')) { + return eRestyle_LaterSiblings; + } + + if (oper != char16_t(0)) { + return eRestyle_Subtree; + } + + return eRestyle_Self; +} + +/** + * Look up the content node in the state rule list, which points to + * any (CSS2 definition) simple selector (whether or not it is the + * subject) that has a state pseudo-class on it. This means that this + * code will be matching selectors that aren't real selectors in any + * stylesheet (e.g., if there is a selector "body > p:hover > a", then + * "body > p:hover" will be in |mStateSelectors|). Note that + * |ComputeSelectorStateDependence| determines which selectors are in + * |mStateSelectors|. + */ +void +RuleCascadeData::HasStateDependentStyle( + ElementDependentRuleProcessorData* aData, + Element* aStatefulElement, + CSSPseudoElementType aPseudoType, + EventStates aStateMask, + nsRestyleHint& aHint) +{ + bool isPseudoElement = aPseudoType != CSSPseudoElementType::NotPseudo; + + StateSelector *iter = mStateSelectors.Elements(), + *end = iter + mStateSelectors.Length(); + NodeMatchContext nodeContext(aStateMask, false); + for (; iter != end; ++iter) { + nsCSSSelector* selector = iter->mSelector; + EventStates states = iter->mStates; + + if (selector->IsPseudoElement() != isPseudoElement) { + continue; + } + + nsCSSSelector* selectorForPseudo; + if (isPseudoElement) { + if (selector->PseudoType() != aPseudoType) { + continue; + } + selectorForPseudo = selector; + selector = selector->mNext; + } + + nsRestyleHint possibleChange = RestyleHintForOp(selector->mOperator); + SelectorMatchesFlags selectorFlags = SelectorMatchesFlags::UNKNOWN; + + // If hint already includes all the bits of possibleChange, + // don't bother calling SelectorMatches, since even if it returns false + // hint won't change. + // Also don't bother calling SelectorMatches if none of the + // states passed in are relevant here. + if ((possibleChange & ~aHint) && states.HasAtLeastOneOfStates(aStateMask) && + // We can optimize away testing selectors that only involve :hover, a + // namespace, and a tag name against nodes that don't have the + // NodeHasRelevantHoverRules flag: such a selector didn't match + // the tag name or namespace the first time around (since the :hover + // didn't set the NodeHasRelevantHoverRules flag), so it won't + // match it now. Check for our selector only having :hover states, or + // the element having the hover rules flag, or the selector having + // some sort of non-namespace, non-tagname data in it. + (states != NS_EVENT_STATE_HOVER || + aStatefulElement->HasRelevantHoverRules() || selector->mIDList || + selector->mClassList || + // We generally expect an mPseudoClassList, since we have a :hover. + // The question is whether we have anything else in there. + (selector->mPseudoClassList && + (selector->mPseudoClassList->mNext || + selector->mPseudoClassList->mType != CSSPseudoClassType::hover)) || + selector->mAttrList || selector->mNegations) && + (!isPseudoElement || + nsCSSRuleUtils::StateSelectorMatches(aStatefulElement, + selectorForPseudo, + nodeContext, + aData->mTreeMatchContext, + selectorFlags, + nullptr, + aStateMask)) && + nsCSSRuleUtils::SelectorMatches(aData->mElement, + selector, + nodeContext, + aData->mTreeMatchContext, + selectorFlags) && + nsCSSRuleUtils::SelectorMatchesTree( + aData->mElement, + selector->mNext, + aData->mTreeMatchContext, + eMatchOnConditionalRestyleAncestor)) { + aHint = nsRestyleHint(aHint | possibleChange); + } + } +} + +static inline nsRestyleHint +RestyleHintForSelectorWithAttributeChange(nsRestyleHint aCurrentHint, + nsCSSSelector* aSelector, + nsCSSSelector* aRightmostSelector) +{ + MOZ_ASSERT(aSelector); + + char16_t oper = aSelector->mOperator; + + if (oper == char16_t('+') || oper == char16_t('~')) { + return eRestyle_LaterSiblings; + } + + if (oper == char16_t(':')) { + return eRestyle_Subtree; + } + + if (oper != char16_t(0)) { + // Check whether the selector is in a form that supports + // eRestyle_SomeDescendants. If it isn't, return eRestyle_Subtree. + + if (aCurrentHint & eRestyle_Subtree) { + // No point checking, since we'll end up restyling the whole + // subtree anyway. + return eRestyle_Subtree; + } + + if (!aRightmostSelector) { + // aSelector wasn't a top-level selector, which means we were inside + // a :not() or :-moz-any(). We don't support that. + return eRestyle_Subtree; + } + + MOZ_ASSERT(aSelector != aRightmostSelector, + "if aSelector == aRightmostSelector then we should have " + "no operator"); + + // Check that aRightmostSelector can be passed to RestrictedSelectorMatches. + if (!aRightmostSelector->IsRestrictedSelector()) { + return eRestyle_Subtree; + } + + // We also don't support pseudo-elements on any of the selectors + // between aRightmostSelector and aSelector. + // XXX Can we lift this restriction, so that we don't have to loop + // over all the selectors? + for (nsCSSSelector* sel = aRightmostSelector->mNext; sel != aSelector; + sel = sel->mNext) { + MOZ_ASSERT(sel, "aSelector must be reachable from aRightmostSelector"); + if (sel->PseudoType() != CSSPseudoElementType::NotPseudo) { + return eRestyle_Subtree; + } + } + + return eRestyle_SomeDescendants; + } + + return eRestyle_Self; +} + +static void +AttributeEnumFunc(nsCSSSelector* aSelector, + nsCSSSelector* aRightmostSelector, + AttributeEnumData* aData) +{ + AttributeRuleProcessorData* data = aData->data; + + if (!data->mTreeMatchContext.SetStyleScopeForSelectorMatching(data->mElement, + data->mScope)) { + // The selector is for a rule in a scoped style sheet, and the subject + // of the selector matching is not in its scope. + return; + } + + nsRestyleHint possibleChange = RestyleHintForSelectorWithAttributeChange( + aData->change, aSelector, aRightmostSelector); + + if (!LookForTargetPseudo( + aSelector, &data->mTreeMatchContext, &possibleChange)) { + return; + } + + // If, ignoring eRestyle_SomeDescendants, enumData->change already includes + // all the bits of possibleChange, don't bother calling SelectorMatches, since + // even if it returns false enumData->change won't change. If possibleChange + // has eRestyle_SomeDescendants, we need to call SelectorMatches(Tree) + // regardless as it might give us new selectors to append to + // mSelectorsForDescendants. + NodeMatchContext nodeContext(EventStates(), false); + if (((possibleChange & (~(aData->change) | eRestyle_SomeDescendants))) && + nsCSSRuleUtils::SelectorMatches(data->mElement, + aSelector, + nodeContext, + data->mTreeMatchContext, + SelectorMatchesFlags::UNKNOWN) && + nsCSSRuleUtils::SelectorMatchesTree(data->mElement, + aSelector->mNext, + data->mTreeMatchContext, + eMatchOnConditionalRestyleAncestor)) { + aData->change = nsRestyleHint(aData->change | possibleChange); + if (possibleChange & eRestyle_SomeDescendants) { + aData->hintData.mSelectorsForDescendants.AppendElement( + aRightmostSelector); + } + } +} + +static MOZ_ALWAYS_INLINE void +EnumerateSelectors(nsTArray& aSelectors, AttributeEnumData* aData) +{ + SelectorPair *iter = aSelectors.Elements(), *end = iter + aSelectors.Length(); + for (; iter != end; ++iter) { + AttributeEnumFunc(iter->mSelector, iter->mRightmostSelector, aData); + } +} + +static MOZ_ALWAYS_INLINE void +EnumerateSelectors(nsTArray& aSelectors, + AttributeEnumData* aData) +{ + nsCSSSelector **iter = aSelectors.Elements(), + **end = iter + aSelectors.Length(); + for (; iter != end; ++iter) { + AttributeEnumFunc(*iter, nullptr, aData); + } +} + +void +RuleCascadeData::HasAttributeDependentStyle( + AttributeRuleProcessorData* aData, + AttributeEnumData* aEnumData, + mozilla::RestyleHintData& aRestyleHintDataResult) +{ + if (aData->mAttribute == nsGkAtoms::id) { + nsIAtom* id = aData->mElement->GetID(); + if (id) { + auto entry = + static_cast(mIdSelectors.Search(id)); + if (entry) { + EnumerateSelectors(entry->mSelectors, aEnumData); + } + } + + EnumerateSelectors(mPossiblyNegatedIDSelectors, aEnumData); + } + + if (aData->mAttribute == nsGkAtoms::_class && + aData->mNameSpaceID == kNameSpaceID_None) { + const nsAttrValue* otherClasses = aData->mOtherValue; + NS_ASSERTION(otherClasses || + aData->mModType == nsIDOMMutationEvent::REMOVAL, + "All class values should be StoresOwnData and parsed" + "via Element::BeforeSetAttr, so available here"); + // For WillChange, enumerate classes that will be removed to see which + // rules apply before the change. + // For Changed, enumerate classes that have been added to see which rules + // apply after the change. + // In both cases we're interested in the classes that are currently on + // the element but not in mOtherValue. + const nsAttrValue* elementClasses = aData->mElement->GetClasses(); + if (elementClasses) { + int32_t atomCount = elementClasses->GetAtomCount(); + if (atomCount > 0) { + nsTHashtable> otherClassesTable; + if (otherClasses) { + int32_t otherClassesCount = otherClasses->GetAtomCount(); + for (int32_t i = 0; i < otherClassesCount; ++i) { + otherClassesTable.PutEntry(otherClasses->AtomAt(i)); + } + } + for (int32_t i = 0; i < atomCount; ++i) { + nsIAtom* curClass = elementClasses->AtomAt(i); + if (!otherClassesTable.Contains(curClass)) { + auto entry = static_cast( + mClassSelectors.Search(curClass)); + if (entry) { + EnumerateSelectors(entry->mSelectors, aEnumData); + } + } + } + } + } + + EnumerateSelectors(mPossiblyNegatedClassSelectors, aEnumData); + } + + auto entry = static_cast( + mAttributeSelectors.Search(aData->mAttribute)); + if (entry) { + EnumerateSelectors(entry->mSelectors, aEnumData); + } +} + +// This function should return the set of states that this selector +// depends on; this is used to implement HasStateDependentStyle. It +// does NOT recur down into things like :not and :-moz-any. +inline EventStates +ComputeSelectorStateDependence(nsCSSSelector& aSelector) +{ + EventStates states; + for (nsPseudoClassList* pseudoClass = aSelector.mPseudoClassList; pseudoClass; + pseudoClass = pseudoClass->mNext) { + // Tree pseudo-elements overload mPseudoClassList for things that + // aren't pseudo-classes. + if (pseudoClass->mType >= CSSPseudoClassType::Count) { + continue; + } + + auto idx = static_cast(pseudoClass->mType); + states |= nsCSSPseudoClasses::sPseudoClassStateDependences[idx]; + } + return states; +} + +bool +RuleCascadeData::AddSelector( + // The part between combinators at the top level of the selector + nsCSSSelector* aSelectorInTopLevel, + // The part we should look through (might be in :not or :-moz-any()) + nsCSSSelector* aSelectorPart, + // The right-most selector at the top level + nsCSSSelector* aRightmostSelector) +{ + // It's worth noting that this loop over negations isn't quite + // optimal for two reasons. One, we could add something to one of + // these lists twice, which means we'll check it twice, but I don't + // think that's worth worrying about. (We do the same for multiple + // attribute selectors on the same attribute.) Two, we don't really + // need to check negations past the first in the current + // implementation (and they're rare as well), but that might change + // in the future if :not() is extended. + for (nsCSSSelector* negation = aSelectorPart; negation; + negation = negation->mNegations) { + // Track both document states and attribute dependence in pseudo-classes. + for (nsPseudoClassList* pseudoClass = negation->mPseudoClassList; + pseudoClass; + pseudoClass = pseudoClass->mNext) { + switch (pseudoClass->mType) { + case CSSPseudoClassType::mozLocaleDir: { + mSelectorDocumentStates |= NS_DOCUMENT_STATE_RTL_LOCALE; + break; + } + case CSSPseudoClassType::mozWindowInactive: { + mSelectorDocumentStates |= + NS_DOCUMENT_STATE_WINDOW_INACTIVE; + break; + } + case CSSPseudoClassType::mozTableBorderNonzero: { + nsTArray* array = + AttributeListFor(nsGkAtoms::border); + if (!array) { + return false; + } + array->AppendElement( + SelectorPair(aSelectorInTopLevel, aRightmostSelector)); + break; + } + default: { + break; + } + } + } + + // Build mStateSelectors. + EventStates dependentStates = ComputeSelectorStateDependence(*negation); + if (!dependentStates.IsEmpty()) { + mStateSelectors.AppendElement( + StateSelector(dependentStates, aSelectorInTopLevel)); + } + + // Build mIDSelectors + if (negation == aSelectorInTopLevel) { + for (nsAtomList* curID = negation->mIDList; curID; curID = curID->mNext) { + auto entry = static_cast( + mIdSelectors.Add(curID->mAtom, fallible)); + if (entry) { + entry->mSelectors.AppendElement( + SelectorPair(aSelectorInTopLevel, aRightmostSelector)); + } + } + } else if (negation->mIDList) { + mPossiblyNegatedIDSelectors.AppendElement(aSelectorInTopLevel); + } + + // Build mClassSelectors + if (negation == aSelectorInTopLevel) { + for (nsAtomList* curClass = negation->mClassList; curClass; + curClass = curClass->mNext) { + auto entry = static_cast( + mClassSelectors.Add(curClass->mAtom, fallible)); + if (entry) { + entry->mSelectors.AppendElement( + SelectorPair(aSelectorInTopLevel, aRightmostSelector)); + } + } + } else if (negation->mClassList) { + mPossiblyNegatedClassSelectors.AppendElement( + aSelectorInTopLevel); + } + + // Build mAttributeSelectors. + for (nsAttrSelector* attr = negation->mAttrList; attr; attr = attr->mNext) { + nsTArray* array = AttributeListFor(attr->mCasedAttr); + if (!array) { + return false; + } + array->AppendElement( + SelectorPair(aSelectorInTopLevel, aRightmostSelector)); + if (attr->mLowercaseAttr != attr->mCasedAttr) { + array = AttributeListFor(attr->mLowercaseAttr); + if (!array) { + return false; + } + array->AppendElement( + SelectorPair(aSelectorInTopLevel, aRightmostSelector)); + } + } + + // Recur through any pseudo-class that has a selector list argument. + for (nsPseudoClassList* pseudoClass = negation->mPseudoClassList; + pseudoClass; + pseudoClass = pseudoClass->mNext) { + if (nsCSSPseudoClasses::HasSelectorListArg(pseudoClass->mType)) { + for (nsCSSSelectorList* l = pseudoClass->u.mSelectorList; l; + l = l->mNext) { + nsCSSSelector* s = l->mSelectors; + if (!AddSelector(aSelectorInTopLevel, s, aRightmostSelector)) { + return false; + } + } + } + } + } + + return true; +} + +bool +RuleCascadeData::AddRule(RuleSelectorPair* aRuleInfo) +{ + // Build the rule hash. + CSSPseudoElementType pseudoType = aRuleInfo->mSelector->PseudoType(); + if (MOZ_LIKELY(pseudoType == CSSPseudoElementType::NotPseudo)) { + mRuleHash.AppendRule(*aRuleInfo); + } else if (pseudoType < CSSPseudoElementType::Count) { + RuleHash*& ruleHash = + mPseudoElementRuleHashes[static_cast( + pseudoType)]; + if (!ruleHash) { + ruleHash = new RuleHash(mQuirksMode); + if (!ruleHash) { + // Out of memory; give up + return false; + } + } + NS_ASSERTION(aRuleInfo->mSelector->mNext, + "Must have mNext; parser screwed up"); + NS_ASSERTION(aRuleInfo->mSelector->mNext->mOperator == ':', + "Unexpected mNext combinator"); + ruleHash->AppendRule(*aRuleInfo); + } else if (pseudoType == CSSPseudoElementType::AnonBox) { + NS_ASSERTION( + !aRuleInfo->mSelector->mCasedTag && !aRuleInfo->mSelector->mIDList && + !aRuleInfo->mSelector->mClassList && + !aRuleInfo->mSelector->mPseudoClassList && + !aRuleInfo->mSelector->mAttrList && !aRuleInfo->mSelector->mNegations && + !aRuleInfo->mSelector->mNext && + aRuleInfo->mSelector->mNameSpace == kNameSpaceID_Unknown, + "Parser messed up with anon box selector"); + + // Index doesn't matter here, since we'll just be walking these + // rules in order; just pass 0. + RuleHash::AppendRuleToTagTable( + &mAnonBoxRules, + aRuleInfo->mSelector->mLowercaseTag, + RuleValue(*aRuleInfo, 0, mQuirksMode)); + } else { +#ifdef MOZ_XUL + NS_ASSERTION(pseudoType == CSSPseudoElementType::XULTree, + "Unexpected pseudo type"); + // Index doesn't matter here, since we'll just be walking these + // rules in order; just pass 0. + RuleHash::AppendRuleToTagTable( + &mXULTreeRules, + aRuleInfo->mSelector->mLowercaseTag, + RuleValue(*aRuleInfo, 0, mQuirksMode)); +#else + NS_NOTREACHED("Unexpected pseudo type"); +#endif + } + + for (nsCSSSelector* selector = aRuleInfo->mSelector; selector; + selector = selector->mNext) { + if (selector->IsPseudoElement()) { + CSSPseudoElementType pseudo = selector->PseudoType(); + if (pseudo >= CSSPseudoElementType::Count || + !nsCSSPseudoElements::PseudoElementSupportsUserActionState(pseudo)) { + NS_ASSERTION(!selector->mNegations, "Shouldn't have negations"); + // We do store selectors ending with pseudo-elements that allow :hover + // and :active after them in the hashtables corresponding to that + // selector's mNext (i.e. the thing that matches against the element), + // but we want to make sure that selectors for any other kinds of + // pseudo-elements don't end up in the hashtables. In particular, tree + // pseudos store strange things in mPseudoClassList that we don't want + // to try to match elements against. + continue; + } + } + if (!AddSelector(selector, selector, aRuleInfo->mSelector)) { + return false; + } + } + + return true; +} + +size_t +ResolvedRuleCascades::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const +{ + size_t n = aMallocSizeOf(this); + n += mOrderedData.ShallowSizeOfExcludingThis(aMallocSizeOf); + for (RuleCascadeData* data : mOrderedData) { + n += data->SizeOfIncludingThis(aMallocSizeOf); + } + return n; +} + +struct PerWeightDataListItem : public RuleSelectorPair +{ + PerWeightDataListItem(css::StyleRule* aRule, nsCSSSelector* aSelector) + : RuleSelectorPair(aRule, aSelector) + , mNext(nullptr) + { + } + // No destructor; these are arena-allocated + + // Placement new to arena allocate the PerWeightDataListItem + void* operator new(size_t aSize, PLArenaPool& aArena) CPP_THROW_NEW + { + void* mem; + PL_ARENA_ALLOCATE(mem, &aArena, aSize); + return mem; + } + + PerWeightDataListItem* mNext; +}; + +struct PerWeightData +{ + PerWeightData() + : mRuleSelectorPairs(nullptr) + , mTail(&mRuleSelectorPairs) + { + } + + int32_t mWeight; + PerWeightDataListItem* mRuleSelectorPairs; + PerWeightDataListItem** mTail; +}; + +struct RuleByWeightEntry : public PLDHashEntryHdr +{ + PerWeightData data; // mWeight is key, mRuleSelectorPairs are value +}; + +static PLDHashNumber +HashIntKey(const void* key) +{ + return PLDHashNumber(NS_PTR_TO_INT32(key)); +} + +static bool +MatchWeightEntry(const PLDHashEntryHdr* hdr, const void* key) +{ + const RuleByWeightEntry* entry = (const RuleByWeightEntry*)hdr; + return entry->data.mWeight == NS_PTR_TO_INT32(key); +} + +static void +InitWeightEntry(PLDHashEntryHdr* hdr, const void* key) +{ + RuleByWeightEntry* entry = static_cast(hdr); + new (KnownNotNull, entry) RuleByWeightEntry(); +} + +/* static */ const PLDHashTableOps CascadeEnumData::sRulesByWeightOps = { + HashIntKey, + MatchWeightEntry, + PLDHashTable::MoveEntryStub, + PLDHashTable::ClearEntryStub, + InitWeightEntry +}; + +CascadeEnumData::CascadeEnumData(nsPresContext* aPresContext, + nsString aName, +#ifdef DEBUG + CascadeEnumData* aParent, +#endif + nsAutoPtr& aContainer, + bool aIsWeak, + nsTArray& aDocumentRules, + nsDocumentRuleResultCacheKey& aDocumentKey, + SheetType aSheetType, + bool aMustGatherDocumentRules) + : mPresContext(aPresContext) + , mName(aName) + , mIsAnonymous(mName.IsEmpty()) + , mIsWeak(aIsWeak) + , mWasFlattened(false) +#ifdef DEBUG + , mParent(aParent) + , mIsRoot(false) +#endif + , mContainer(aContainer) + , mDocumentRules(aDocumentRules) + , mDocumentCacheKey(aDocumentKey) + , mSheetType(aSheetType) + , mMustGatherDocumentRules(aMustGatherDocumentRules) + , mRulesByWeight(&sRulesByWeightOps, sizeof(RuleByWeightEntry), 32) +{ + Initialize(); +} + +CascadeEnumData::CascadeEnumData(nsPresContext* aPresContext, + nsAutoPtr& aContainer, + nsTArray& aDocumentRules, + nsDocumentRuleResultCacheKey& aDocumentKey, + SheetType aSheetType, + bool aMustGatherDocumentRules) + : mPresContext(aPresContext) + , mContainer(aContainer) + , mIsAnonymous(false) + , mIsWeak(false) + , mWasFlattened(false) +#ifdef DEBUG + , mParent(nullptr) + , mIsRoot(true) +#endif + , mDocumentRules(aDocumentRules) + , mDocumentCacheKey(aDocumentKey) + , mSheetType(aSheetType) + , mMustGatherDocumentRules(aMustGatherDocumentRules) + , mRulesByWeight(&sRulesByWeightOps, sizeof(RuleByWeightEntry), 32) +{ + Initialize(); +} + +CascadeEnumData::~CascadeEnumData() +{ + PL_FinishArenaPool(&mArena); +} + +CascadeEnumData* +CascadeEnumData::CreateNamedChildLayer(const nsTArray& aPath) +{ + if (aPath.IsEmpty()) { + return this; + } + + const nsString& name = aPath[0]; + CascadeEnumData* childLayer = nullptr; + + // Create new layer if it doesn't exist. + if (!mLayers.Get(name, &childLayer)) { + childLayer = new CascadeEnumData(mPresContext, + name, +#ifdef DEBUG + this, +#endif + mContainer, + false, + mDocumentRules, + mDocumentCacheKey, + mSheetType, + mMustGatherDocumentRules); + mPreLayers.AppendElement(childLayer); + mLayers.Put(name, childLayer); + } + + // Final layer in the path. + if (aPath.Length() == 1) { + return childLayer; + } + + // Continue with the tail of the path. + nsTArray tail; + tail.AppendElements(aPath.Elements() + 1, aPath.Length() - 1); + return childLayer->CreateNamedChildLayer(tail); +} + +CascadeEnumData* +CascadeEnumData::CreateAnonymousChildLayer() +{ + nsString name; + CascadeEnumData* childLayer = new CascadeEnumData(mPresContext, + name, +#ifdef DEBUG + this, +#endif + mContainer, + false, + mDocumentRules, + mDocumentCacheKey, + mSheetType, + mMustGatherDocumentRules); + mPreLayers.AppendElement(childLayer); + return childLayer; +} + +static int +CompareWeightData(const void* aArg1, const void* aArg2, void* closure) +{ + const PerWeightData* arg1 = static_cast(aArg1); + const PerWeightData* arg2 = static_cast(aArg2); + return arg1->mWeight - arg2->mWeight; // put lower weight first +} + +void +CascadeEnumData::AddRules() +{ + for (css::StyleRule* styleRule : mStyleRules) { + for (nsCSSSelectorList* sel = styleRule->Selector(); sel; + sel = sel->mNext) { + int32_t weight = sel->mWeight; + auto entry = static_cast( + mRulesByWeight.Add(NS_INT32_TO_PTR(weight), fallible)); + if (!entry) { + return; + } + entry->data.mWeight = weight; + // entry->data.mRuleSelectorPairs should be linked in forward order; + // entry->data.mTail is the slot to write to. + auto* newItem = + new (mArena) PerWeightDataListItem(styleRule, sel->mSelectors); + if (newItem) { + *(entry->data.mTail) = newItem; + entry->data.mTail = &newItem->mNext; + } + } + } + + // Sort the hash table of per-weight linked lists by weight. + uint32_t weightCount = mRulesByWeight.EntryCount(); + auto weightArray = MakeUnique(weightCount); + int32_t j = 0; + for (auto iter = mRulesByWeight.Iter(); !iter.Done(); iter.Next()) { + auto entry = static_cast(iter.Get()); + weightArray[j++] = entry->data; + } + NS_QuickSort(weightArray.get(), + weightCount, + sizeof(PerWeightData), + CompareWeightData, + nullptr); + + // Put things into the rule hash. + // The primary sort is by weight... + for (uint32_t i = 0; i < weightCount; ++i) { + // and the secondary sort is by order. mRuleSelectorPairs is already in + // the right order.. + for (PerWeightDataListItem* cur = weightArray[i].mRuleSelectorPairs; cur; + cur = cur->mNext) { + if (!mData->AddRule(cur)) { + return; /* out of memory */ + } + } + } + + // Build mKeyframesRuleTable. + for (nsTArray::size_type + i = 0, + iEnd = mData->mKeyframesRules.Length(); + i < iEnd; + ++i) { + nsCSSKeyframesRule* rule = mData->mKeyframesRules[i]; + mData->mKeyframesRuleTable.Put(rule->GetName(), rule); + } + + // Build mCounterStyleRuleTable + for (nsTArray::size_type + i = 0, + iEnd = mData->mCounterStyleRules.Length(); + i < iEnd; + ++i) { + nsCSSCounterStyleRule* rule = mData->mCounterStyleRules[i]; + mData->mCounterStyleRuleTable.Put(rule->GetName(), rule); + } +} + +void +CascadeEnumData::Flatten() +{ + if (mWasFlattened) { + return; + } + + if (mPreLayers.Length() > 0) { + for (CascadeEnumData* pre : mPreLayers) { + pre->Flatten(); + } + } + + mContainer->mOrderedData.AppendElement(mData); + AddRules(); + + if (mPostLayers.Length() > 0) { + for (CascadeEnumData* post : mPostLayers) { + post->Flatten(); + } + } + + mWasFlattened = true; +} + +void +CascadeEnumData::Initialize() +{ + mData = new RuleCascadeData(mPresContext->Medium(), + eCompatibility_NavQuirks == + mPresContext->CompatibilityMode()); + + // Initialize our arena + PL_INIT_ARENA_POOL( + &mArena, "CascadeEnumDataArena", NS_CASCADEENUMDATA_ARENA_BLOCK_SIZE); +} diff --git a/layout/style/RuleCascadeData.h b/layout/style/RuleCascadeData.h new file mode 100644 index 0000000000..3358c4efa5 --- /dev/null +++ b/layout/style/RuleCascadeData.h @@ -0,0 +1,411 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef RuleCascadeData_h___ +#define RuleCascadeData_h___ + +#include "mozilla/Attributes.h" +#include "mozilla/EventStates.h" +#include "mozilla/MemoryReporting.h" +#include "mozilla/RefCountType.h" +#include "mozilla/SheetType.h" +#include "mozilla/UniquePtr.h" +#include "mozilla/css/StyleRule.h" +#include "nsContentUtils.h" +#include "nsCSSRules.h" +#include "nsExpirationTracker.h" +#include "nsIMediaList.h" +#include "nsIStyleRuleProcessor.h" +#include "nsRuleWalker.h" +#include "nsTArray.h" +#include "nsRuleProcessorData.h" + +using namespace mozilla; +using namespace mozilla::dom; + +struct nsFontFaceRuleContainer; + +/** + * A struct representing a given CSS rule and a particular selector + * from that rule's selector list. + */ +struct RuleSelectorPair +{ + RuleSelectorPair(css::StyleRule* aRule, nsCSSSelector* aSelector) + : mRule(aRule) + , mSelector(aSelector) + { + } + // If this class ever grows a destructor, deal with + // PerWeightDataListItem appropriately. + + css::StyleRule* mRule; + nsCSSSelector* mSelector; // which of |mRule|'s selectors +}; + +/** + * A struct representing a particular rule in an ordered list of rules + * (the ordering depending on the weight of mSelector and the order of + * our rules to start with). + */ +struct RuleValue : RuleSelectorPair +{ + enum + { + eMaxAncestorHashes = 4 + }; + + RuleValue(const RuleSelectorPair& aRuleSelectorPair, + int32_t aIndex, + bool aQuirksMode) + : RuleSelectorPair(aRuleSelectorPair) + , mIndex(aIndex) + { + CollectAncestorHashes(aQuirksMode); + } + + int32_t mIndex; // High index means high weight/order. + uint32_t mAncestorSelectorHashes[eMaxAncestorHashes]; + +private: + void CollectAncestorHashes(bool aQuirksMode) + { + // Collect up our mAncestorSelectorHashes. It's not clear whether it's + // better to stop once we've found eMaxAncestorHashes of them or to keep + // going and preferentially collect information from selectors higher up the + // chain... Let's do the former for now. + size_t hashIndex = 0; + for (nsCSSSelector* sel = mSelector->mNext; sel; sel = sel->mNext) { + if (!NS_IS_ANCESTOR_OPERATOR(sel->mOperator)) { + // |sel| is going to select something that's not actually one of our + // ancestors, so don't add it to mAncestorSelectorHashes. But keep + // going, because it'll select a sibling of one of our ancestors, so its + // ancestors would be our ancestors too. + continue; + } + + // Now sel is supposed to select one of our ancestors. Grab + // whatever info we can from it into mAncestorSelectorHashes. + // But in qurks mode, don't grab IDs and classes because those + // need to be matched case-insensitively. + if (!aQuirksMode) { + nsAtomList* ids = sel->mIDList; + while (ids) { + mAncestorSelectorHashes[hashIndex++] = ids->mAtom->hash(); + if (hashIndex == eMaxAncestorHashes) { + return; + } + ids = ids->mNext; + } + + nsAtomList* classes = sel->mClassList; + while (classes) { + mAncestorSelectorHashes[hashIndex++] = classes->mAtom->hash(); + if (hashIndex == eMaxAncestorHashes) { + return; + } + classes = classes->mNext; + } + } + + // Only put in the tag name if it's all-lowercase. Otherwise we run into + // trouble because we may test the wrong one of mLowercaseTag and + // mCasedTag against the filter. + if (sel->mLowercaseTag && sel->mCasedTag == sel->mLowercaseTag) { + mAncestorSelectorHashes[hashIndex++] = sel->mLowercaseTag->hash(); + if (hashIndex == eMaxAncestorHashes) { + return; + } + } + } + + while (hashIndex != eMaxAncestorHashes) { + mAncestorSelectorHashes[hashIndex++] = 0; + } + } +}; + +/** + * A struct that stores an nsCSSSelector pointer along side a pointer to + * the rightmost nsCSSSelector in the selector. For example, for + * + * .main p > span + * + * if mSelector points to the |p| nsCSSSelector, mRightmostSelector would + * point to the |span| nsCSSSelector. + * + * Both mSelector and mRightmostSelector are always top-level selectors, + * i.e. they aren't selectors within a :not() or :-moz-any(). + */ +struct SelectorPair +{ + SelectorPair(nsCSSSelector* aSelector, nsCSSSelector* aRightmostSelector) + : mSelector(aSelector) + , mRightmostSelector(aRightmostSelector) + { + MOZ_ASSERT(aSelector); + MOZ_ASSERT(mRightmostSelector); + } + SelectorPair(const SelectorPair& aOther) = default; + nsCSSSelector* const mSelector; + nsCSSSelector* const mRightmostSelector; +}; + +struct StateSelector +{ + StateSelector(mozilla::EventStates aStates, nsCSSSelector* aSelector) + : mStates(aStates) + , mSelector(aSelector) + { + } + + mozilla::EventStates mStates; + nsCSSSelector* mSelector; +}; + +class RuleHash +{ +public: + explicit RuleHash(bool aQuirksMode); + ~RuleHash(); + void AppendRule(const RuleSelectorPair& aRuleInfo); + void EnumerateAllRules(Element* aElement, + ElementDependentRuleProcessorData* aData, + NodeMatchContext& aNodeMatchContext); + + size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const; + size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const; + + static void AppendRuleToTagTable(PLDHashTable* aTable, + nsIAtom* aKey, + const RuleValue& aRuleInfo); + + static const PLDHashTableOps TagTable_Ops; + static const PLDHashTableOps ClassTable_CSOps; + static const PLDHashTableOps ClassTable_CIOps; + static const PLDHashTableOps IdTable_CSOps; + static const PLDHashTableOps IdTable_CIOps; + static const PLDHashTableOps NameSpaceTable_Ops; + +protected: + typedef nsTArray RuleValueList; + void AppendRuleToTable(PLDHashTable* aTable, + const void* aKey, + const RuleSelectorPair& aRuleInfo); + void AppendUniversalRule(const RuleSelectorPair& aRuleInfo); + + int32_t mRuleCount; + + PLDHashTable mIdTable; + PLDHashTable mClassTable; + PLDHashTable mTagTable; + PLDHashTable mNameSpaceTable; + RuleValueList mUniversalRules; + + struct EnumData + { + const RuleValue* mCurValue; + const RuleValue* mEnd; + }; + EnumData* mEnumList; + int32_t mEnumListSize; + + bool mQuirksMode; + + inline EnumData ToEnumData(const RuleValueList& arr) + { + EnumData data = { arr.Elements(), arr.Elements() + arr.Length() }; + return data; + } + +#ifdef RULE_HASH_STATS + uint32_t mUniversalSelectors; + uint32_t mNameSpaceSelectors; + uint32_t mTagSelectors; + uint32_t mClassSelectors; + uint32_t mIdSelectors; + + uint32_t mElementsMatched; + + uint32_t mElementUniversalCalls; + uint32_t mElementNameSpaceCalls; + uint32_t mElementTagCalls; + uint32_t mElementClassCalls; + uint32_t mElementIdCalls; +#endif // RULE_HASH_STATS +}; + +struct AttributeEnumData +{ + AttributeEnumData(AttributeRuleProcessorData* aData, + RestyleHintData& aRestyleHintData) + : data(aData) + , change(nsRestyleHint(0)) + , hintData(aRestyleHintData) + { + } + + AttributeRuleProcessorData* data; + nsRestyleHint change; + RestyleHintData& hintData; +}; + +struct RuleCascadeData +{ + RuleCascadeData(nsIAtom* aMedium, bool aQuirksMode); + ~RuleCascadeData(); + + size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const; + + RuleHash mRuleHash; + RuleHash* mPseudoElementRuleHashes[static_cast( + CSSPseudoElementType::Count)]; + nsTArray mStateSelectors; + EventStates mSelectorDocumentStates; + PLDHashTable mClassSelectors; + PLDHashTable mIdSelectors; + nsTArray mPossiblyNegatedClassSelectors; + nsTArray mPossiblyNegatedIDSelectors; + PLDHashTable mAttributeSelectors; + PLDHashTable mAnonBoxRules; + PLDHashTable mXULTreeRules; + + nsTArray mFontFaceRules; + nsTArray mKeyframesRules; + nsTArray mFontFeatureValuesRules; + nsTArray mPageRules; + nsTArray mCounterStyleRules; + + nsDataHashtable mKeyframesRuleTable; + nsDataHashtable + mCounterStyleRuleTable; + + // Looks up or creates the appropriate list in |mAttributeSelectors|. + // Returns null only on allocation failure. + nsTArray* AttributeListFor(nsIAtom* aAttribute); + + nsMediaQueryResultCacheKey mCacheKey; + + const bool mQuirksMode; + + void RulesMatching(ElementRuleProcessorData* aData); + + void RulesMatching(PseudoElementRuleProcessorData* aData); + + void RulesMatching(AnonBoxRuleProcessorData* aData); + + void RulesMatching(XULTreeRuleProcessorData* aData); + + void HasStateDependentStyle(ElementDependentRuleProcessorData* aData, + Element* aStatefulElement, + CSSPseudoElementType aPseudoType, + EventStates aStateMask, + nsRestyleHint& aHint); + + void HasAttributeDependentStyle( + AttributeRuleProcessorData* aData, + AttributeEnumData* aEnumData, + mozilla::RestyleHintData& aRestyleHintDataResult); + + bool AddSelector( + // The part between combinators at the top level of the selector + nsCSSSelector* aSelectorInTopLevel, + // The part we should look through (might be in :not or :-moz-any()) + nsCSSSelector* aSelectorPart, + // The right-most selector at the top level + nsCSSSelector* aRightmostSelector); + + bool AddRule(RuleSelectorPair* aRuleInfo); + +private: + static const PLDHashTableOps AtomSelector_CSOps; + static const PLDHashTableOps AtomSelector_CIOps; +}; + +struct ResolvedRuleCascades +{ + ResolvedRuleCascades() + : mUnlayered(nullptr) + , mNext(nullptr) + { + } + + ~ResolvedRuleCascades() + { + for (RuleCascadeData* data : mOrderedData) { + delete data; + } + } + + nsTArray mOrderedData; + RuleCascadeData* mUnlayered; + ResolvedRuleCascades* mNext; // for a different medium + + size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const; +}; + +struct CascadeEnumData +{ + CascadeEnumData(nsPresContext* aPresContext, + nsString aName, +#ifdef DEBUG + CascadeEnumData* aParent, +#endif + nsAutoPtr& aContainer, + bool aIsWeak, + nsTArray& aDocumentRules, + nsDocumentRuleResultCacheKey& aDocumentKey, + SheetType aSheetType, + bool aMustGatherDocumentRules); + + CascadeEnumData(nsPresContext* aPresContext, + nsAutoPtr& aContainer, + nsTArray& aDocumentRules, + nsDocumentRuleResultCacheKey& aDocumentKey, + SheetType aSheetType, + bool aMustGatherDocumentRules); + + ~CascadeEnumData(); + + nsPresContext* mPresContext; + nsString mName; + bool mIsAnonymous; + bool mIsWeak; + bool mWasFlattened; + + RuleCascadeData* mData; + + nsTArray mStyleRules; + nsTArray& mDocumentRules; + nsDocumentRuleResultCacheKey& mDocumentCacheKey; + SheetType mSheetType; + bool mMustGatherDocumentRules; + + PLArenaPool mArena; + // Hooray, a manual PLDHashTable since nsClassHashtable doesn't + // provide a getter that gives me a *reference* to the value. + PLDHashTable mRulesByWeight; // of PerWeightDataListItem linked lists + +#ifdef DEBUG + CascadeEnumData* mParent; + bool mIsRoot; +#endif + nsAutoPtr& mContainer; + nsTArray mPreLayers; + nsTArray mPostLayers; + nsDataHashtable mLayers; + + CascadeEnumData* CreateNamedChildLayer(const nsTArray& aPath); + CascadeEnumData* CreateAnonymousChildLayer(); + void Flatten(); + +private: + void Initialize(); + void AddRules(); + + static const PLDHashTableOps sRulesByWeightOps; +}; + +#endif /* RuleCascadeData_h___ */ diff --git a/layout/style/StyleRule.h b/layout/style/StyleRule.h index ba10542c78..1ac19a418d 100644 --- a/layout/style/StyleRule.h +++ b/layout/style/StyleRule.h @@ -22,6 +22,16 @@ #include "nsIStyleRule.h" #include "nsICSSStyleRuleDOMWrapper.h" +// Right now, there are four operators: +// ' ', the descendant combinator, is greedy +// '~', the indirect adjacent sibling combinator, is greedy +// '+' and '>', the direct adjacent sibling and child combinators, are not +#define NS_IS_GREEDY_OPERATOR(ch) \ + ((ch) == char16_t(' ') || (ch) == char16_t('~')) + +#define NS_IS_ANCESTOR_OPERATOR(ch) \ + ((ch) == char16_t(' ') || (ch) == char16_t('>')) + class nsIAtom; struct nsCSSSelectorList; diff --git a/layout/style/moz.build b/layout/style/moz.build index 1212a92d10..61912a7bcb 100644 --- a/layout/style/moz.build +++ b/layout/style/moz.build @@ -45,6 +45,7 @@ EXPORTS += [ 'nsCSSPseudoElementList.h', 'nsCSSPseudoElements.h', 'nsCSSRuleProcessor.h', + 'nsCSSRuleUtils.h', 'nsCSSScanner.h', 'nsCSSValue.h', 'nsDOMCSSAttrDeclaration.h', @@ -70,6 +71,7 @@ EXPORTS += [ 'nsStyleStructInlines.h', 'nsStyleTransformMatrix.h', 'nsStyleUtil.h', + 'RuleCascadeData.h', ] EXPORTS.mozilla += [ @@ -146,7 +148,9 @@ UNIFIED_SOURCES += [ 'nsCSSProps.cpp', 'nsCSSPseudoClasses.cpp', 'nsCSSPseudoElements.cpp', + 'nsCSSRuleProcessor.cpp', 'nsCSSRules.cpp', + 'nsCSSRuleUtils.cpp', 'nsCSSScanner.cpp', 'nsCSSValue.cpp', 'nsDOMCSSAttrDeclaration.cpp', @@ -184,14 +188,14 @@ UNIFIED_SOURCES += [ # includes, via nsStyleCoord.h, , which ends up including # , which fails in much the way described in # . -# - nsCSSRuleProcessor.cpp needs to be built separately because it uses +# - RuleCascadeData.cpp needs to be built separately because it uses # plarena.h. # - nsLayoutStylesheetCache.cpp needs to be built separately because it uses # nsExceptionHandler.h, which includes windows.h. SOURCES += [ 'BindingStyleRule.cpp', - 'nsCSSRuleProcessor.cpp', 'nsLayoutStylesheetCache.cpp', + 'RuleCascadeData.cpp', ] include('/ipc/chromium/chromium-config.mozbuild') diff --git a/layout/style/nsCSSPseudoClasses.cpp b/layout/style/nsCSSPseudoClasses.cpp index 6a6d1959c0..8e12a9648c 100644 --- a/layout/style/nsCSSPseudoClasses.cpp +++ b/layout/style/nsCSSPseudoClasses.cpp @@ -70,6 +70,40 @@ nsCSSPseudoClasses::sPseudoClassEnabled[] = { #undef IS_ENABLED_BY_DEFAULT }; +// Arrays of the states that are relevant for various pseudoclasses. + +/* static */ const mozilla::EventStates +nsCSSPseudoClasses::sPseudoClassStateDependences[] = { +#define CSS_PSEUDO_CLASS(_name, _value, _flags, _pref) EventStates(), +#define CSS_STATE_DEPENDENT_PSEUDO_CLASS( \ + _name, _value, _flags, _pref, _states) \ + _states, +#include "nsCSSPseudoClassList.h" +#undef CSS_STATE_DEPENDENT_PSEUDO_CLASS +#undef CSS_PSEUDO_CLASS + // Add more entries for our fake values to make sure we can't + // index out of bounds into this array no matter what. + EventStates(), + EventStates() + }; + +/* static */ const mozilla::EventStates + nsCSSPseudoClasses::sPseudoClassStates[] = { +#define CSS_PSEUDO_CLASS(_name, _value, _flags, _pref) EventStates(), +#define CSS_STATE_PSEUDO_CLASS(_name, _value, _flags, _pref, _states) _states, +#include "nsCSSPseudoClassList.h" +#undef CSS_STATE_PSEUDO_CLASS +#undef CSS_PSEUDO_CLASS + // Add more entries for our fake values to make sure we can't + // index out of bounds into this array no matter what. + EventStates(), + EventStates() +}; +static_assert(MOZ_ARRAY_LENGTH(nsCSSPseudoClasses::sPseudoClassStates) == + static_cast(CSSPseudoClassType::MAX), + "CSSPseudoClassType::MAX is no longer equal to the length of " + "nsCSSPseudoClasses::sPseudoClassStates"); + void nsCSSPseudoClasses::AddRefAtoms() { NS_RegisterStaticAtoms(CSSPseudoClasses_info); diff --git a/layout/style/nsCSSPseudoClasses.h b/layout/style/nsCSSPseudoClasses.h index e4738f64ba..51eaf61ea4 100644 --- a/layout/style/nsCSSPseudoClasses.h +++ b/layout/style/nsCSSPseudoClasses.h @@ -87,6 +87,9 @@ public: return false; } + static const mozilla::EventStates sPseudoClassStateDependences[]; + static const mozilla::EventStates sPseudoClassStates[]; + private: static const uint32_t kPseudoClassFlags[size_t(Type::Count)]; static bool sPseudoClassEnabled[size_t(Type::Count)]; diff --git a/layout/style/nsCSSRuleProcessor.cpp b/layout/style/nsCSSRuleProcessor.cpp index 81cbd1a244..ac5fdf86dd 100644 --- a/layout/style/nsCSSRuleProcessor.cpp +++ b/layout/style/nsCSSRuleProcessor.cpp @@ -8,13 +8,6 @@ * matching and cascading */ -#define PL_ARENA_CONST_ALIGN_MASK 7 -// We want page-sized arenas so there's no fragmentation involved. -// Including plarena.h must come first to avoid it being included by some -// header file thereby making PL_ARENA_CONST_ALIGN_MASK ineffective. -#define NS_CASCADEENUMDATA_ARENA_BLOCK_SIZE (4096) -#include "plarena.h" - #include "nsAutoPtr.h" #include "nsCSSRuleProcessor.h" #include "nsRuleProcessorData.h" @@ -60,958 +53,12 @@ #include "RuleProcessorCache.h" #include "nsIDOMMutationEvent.h" #include "nsIMozBrowserFrame.h" +#include "RuleCascadeData.h" +#include "nsCSSRuleUtils.h" using namespace mozilla; using namespace mozilla::dom; -#define VISITED_PSEUDO_PREF "layout.css.visited_links_enabled" - -static bool gSupportVisitedPseudo = true; - -static nsTArray< nsCOMPtr >* sSystemMetrics = 0; - -#ifdef XP_WIN -uint8_t nsCSSRuleProcessor::sWinThemeId = LookAndFeel::eWindowsTheme_Generic; -#endif - -/** - * A struct representing a given CSS rule and a particular selector - * from that rule's selector list. - */ -struct RuleSelectorPair { - RuleSelectorPair(css::StyleRule* aRule, nsCSSSelector* aSelector) - : mRule(aRule), mSelector(aSelector) {} - // If this class ever grows a destructor, deal with - // PerWeightDataListItem appropriately. - - css::StyleRule* mRule; - nsCSSSelector* mSelector; // which of |mRule|'s selectors -}; - -#define NS_IS_ANCESTOR_OPERATOR(ch) \ - ((ch) == char16_t(' ') || (ch) == char16_t('>')) - -/** - * A struct representing a particular rule in an ordered list of rules - * (the ordering depending on the weight of mSelector and the order of - * our rules to start with). - */ -struct RuleValue : RuleSelectorPair { - enum { - eMaxAncestorHashes = 4 - }; - - RuleValue(const RuleSelectorPair& aRuleSelectorPair, int32_t aIndex, - bool aQuirksMode) : - RuleSelectorPair(aRuleSelectorPair), - mIndex(aIndex) - { - CollectAncestorHashes(aQuirksMode); - } - - int32_t mIndex; // High index means high weight/order. - uint32_t mAncestorSelectorHashes[eMaxAncestorHashes]; - -private: - void CollectAncestorHashes(bool aQuirksMode) { - // Collect up our mAncestorSelectorHashes. It's not clear whether it's - // better to stop once we've found eMaxAncestorHashes of them or to keep - // going and preferentially collect information from selectors higher up the - // chain... Let's do the former for now. - size_t hashIndex = 0; - for (nsCSSSelector* sel = mSelector->mNext; sel; sel = sel->mNext) { - if (!NS_IS_ANCESTOR_OPERATOR(sel->mOperator)) { - // |sel| is going to select something that's not actually one of our - // ancestors, so don't add it to mAncestorSelectorHashes. But keep - // going, because it'll select a sibling of one of our ancestors, so its - // ancestors would be our ancestors too. - continue; - } - - // Now sel is supposed to select one of our ancestors. Grab - // whatever info we can from it into mAncestorSelectorHashes. - // But in qurks mode, don't grab IDs and classes because those - // need to be matched case-insensitively. - if (!aQuirksMode) { - nsAtomList* ids = sel->mIDList; - while (ids) { - mAncestorSelectorHashes[hashIndex++] = ids->mAtom->hash(); - if (hashIndex == eMaxAncestorHashes) { - return; - } - ids = ids->mNext; - } - - nsAtomList* classes = sel->mClassList; - while (classes) { - mAncestorSelectorHashes[hashIndex++] = classes->mAtom->hash(); - if (hashIndex == eMaxAncestorHashes) { - return; - } - classes = classes->mNext; - } - } - - // Only put in the tag name if it's all-lowercase. Otherwise we run into - // trouble because we may test the wrong one of mLowercaseTag and - // mCasedTag against the filter. - if (sel->mLowercaseTag && sel->mCasedTag == sel->mLowercaseTag) { - mAncestorSelectorHashes[hashIndex++] = sel->mLowercaseTag->hash(); - if (hashIndex == eMaxAncestorHashes) { - return; - } - } - } - - while (hashIndex != eMaxAncestorHashes) { - mAncestorSelectorHashes[hashIndex++] = 0; - } - } -}; - -// ------------------------------ -// Rule hash table -// - -// Uses any of the sets of ops below. -struct RuleHashTableEntry : public PLDHashEntryHdr { - // If you add members that have heap allocated memory be sure to change the - // logic in SizeOfRuleHashTable(). - // Auto length 1, because we always have at least one entry in mRules. - AutoTArray mRules; -}; - -struct RuleHashTagTableEntry : public RuleHashTableEntry { - // If you add members that have heap allocated memory be sure to change the - // logic in RuleHash::SizeOf{In,Ex}cludingThis. - nsCOMPtr mTag; -}; - -static PLDHashNumber -RuleHash_CIHashKey(const void *key) -{ - nsIAtom *atom = const_cast(static_cast(key)); - - nsAutoString str; - atom->ToString(str); - nsContentUtils::ASCIIToLower(str); - return HashString(str); -} - -static inline nsCSSSelector* -SubjectSelectorForRuleHash(const PLDHashEntryHdr *hdr) -{ - auto entry = static_cast(hdr); - nsCSSSelector* selector = entry->mRules[0].mSelector; - if (selector->IsPseudoElement()) { - selector = selector->mNext; - } - return selector; -} - -static inline bool -CIMatchAtoms(const void* key, nsIAtom *entry_atom) -{ - auto match_atom = const_cast(static_cast(key)); - - // Check for case-sensitive match first. - if (match_atom == entry_atom) { - return true; - } - - // Use EqualsIgnoreASCIICase instead of full on unicode case conversion - // in order to save on performance. This is only used in quirks mode - // anyway. - return - nsContentUtils::EqualsIgnoreASCIICase(nsDependentAtomString(entry_atom), - nsDependentAtomString(match_atom)); -} - -static inline bool -CSMatchAtoms(const void* key, nsIAtom *entry_atom) -{ - auto match_atom = const_cast(static_cast(key)); - return match_atom == entry_atom; -} - -static bool -RuleHash_ClassCIMatchEntry(const PLDHashEntryHdr *hdr, const void *key) -{ - return CIMatchAtoms(key, SubjectSelectorForRuleHash(hdr)->mClassList->mAtom); -} - -static bool -RuleHash_IdCIMatchEntry(const PLDHashEntryHdr *hdr, const void *key) -{ - return CIMatchAtoms(key, SubjectSelectorForRuleHash(hdr)->mIDList->mAtom); -} - -static bool -RuleHash_ClassCSMatchEntry(const PLDHashEntryHdr *hdr, const void *key) -{ - return CSMatchAtoms(key, SubjectSelectorForRuleHash(hdr)->mClassList->mAtom); -} - -static bool -RuleHash_IdCSMatchEntry(const PLDHashEntryHdr *hdr, const void *key) -{ - return CSMatchAtoms(key, SubjectSelectorForRuleHash(hdr)->mIDList->mAtom); -} - -static void -RuleHash_InitEntry(PLDHashEntryHdr *hdr, const void *key) -{ - RuleHashTableEntry* entry = static_cast(hdr); - new (KnownNotNull, entry) RuleHashTableEntry(); -} - -static void -RuleHash_ClearEntry(PLDHashTable *table, PLDHashEntryHdr *hdr) -{ - RuleHashTableEntry* entry = static_cast(hdr); - entry->~RuleHashTableEntry(); -} - -static void -RuleHash_MoveEntry(PLDHashTable *table, const PLDHashEntryHdr *from, - PLDHashEntryHdr *to) -{ - NS_PRECONDITION(from != to, "This is not going to work!"); - RuleHashTableEntry *oldEntry = - const_cast( - static_cast(from)); - auto* newEntry = new (KnownNotNull, to) RuleHashTableEntry(); - newEntry->mRules.SwapElements(oldEntry->mRules); - oldEntry->~RuleHashTableEntry(); -} - -static bool -RuleHash_TagTable_MatchEntry(const PLDHashEntryHdr *hdr, const void *key) -{ - nsIAtom *match_atom = const_cast(static_cast(key)); - nsIAtom *entry_atom = static_cast(hdr)->mTag; - - return match_atom == entry_atom; -} - -static void -RuleHash_TagTable_InitEntry(PLDHashEntryHdr *hdr, const void *key) -{ - RuleHashTagTableEntry* entry = static_cast(hdr); - new (KnownNotNull, entry) RuleHashTagTableEntry(); - entry->mTag = const_cast(static_cast(key)); -} - -static void -RuleHash_TagTable_ClearEntry(PLDHashTable *table, PLDHashEntryHdr *hdr) -{ - RuleHashTagTableEntry* entry = static_cast(hdr); - entry->~RuleHashTagTableEntry(); -} - -static void -RuleHash_TagTable_MoveEntry(PLDHashTable *table, const PLDHashEntryHdr *from, - PLDHashEntryHdr *to) -{ - NS_PRECONDITION(from != to, "This is not going to work!"); - RuleHashTagTableEntry *oldEntry = - const_cast( - static_cast(from)); - auto* newEntry = new (KnownNotNull, to) RuleHashTagTableEntry(); - newEntry->mTag.swap(oldEntry->mTag); - newEntry->mRules.SwapElements(oldEntry->mRules); - oldEntry->~RuleHashTagTableEntry(); -} - -static PLDHashNumber -RuleHash_NameSpaceTable_HashKey(const void *key) -{ - return NS_PTR_TO_INT32(key); -} - -static bool -RuleHash_NameSpaceTable_MatchEntry(const PLDHashEntryHdr *hdr, const void *key) -{ - const RuleHashTableEntry *entry = - static_cast(hdr); - - nsCSSSelector* selector = entry->mRules[0].mSelector; - if (selector->IsPseudoElement()) { - selector = selector->mNext; - } - return NS_PTR_TO_INT32(key) == selector->mNameSpace; -} - -static const PLDHashTableOps RuleHash_TagTable_Ops = { - PLDHashTable::HashVoidPtrKeyStub, - RuleHash_TagTable_MatchEntry, - RuleHash_TagTable_MoveEntry, - RuleHash_TagTable_ClearEntry, - RuleHash_TagTable_InitEntry -}; - -// Case-sensitive ops. -static const PLDHashTableOps RuleHash_ClassTable_CSOps = { - PLDHashTable::HashVoidPtrKeyStub, - RuleHash_ClassCSMatchEntry, - RuleHash_MoveEntry, - RuleHash_ClearEntry, - RuleHash_InitEntry -}; - -// Case-insensitive ops. -static const PLDHashTableOps RuleHash_ClassTable_CIOps = { - RuleHash_CIHashKey, - RuleHash_ClassCIMatchEntry, - RuleHash_MoveEntry, - RuleHash_ClearEntry, - RuleHash_InitEntry -}; - -// Case-sensitive ops. -static const PLDHashTableOps RuleHash_IdTable_CSOps = { - PLDHashTable::HashVoidPtrKeyStub, - RuleHash_IdCSMatchEntry, - RuleHash_MoveEntry, - RuleHash_ClearEntry, - RuleHash_InitEntry -}; - -// Case-insensitive ops. -static const PLDHashTableOps RuleHash_IdTable_CIOps = { - RuleHash_CIHashKey, - RuleHash_IdCIMatchEntry, - RuleHash_MoveEntry, - RuleHash_ClearEntry, - RuleHash_InitEntry -}; - -static const PLDHashTableOps RuleHash_NameSpaceTable_Ops = { - RuleHash_NameSpaceTable_HashKey, - RuleHash_NameSpaceTable_MatchEntry, - RuleHash_MoveEntry, - RuleHash_ClearEntry, - RuleHash_InitEntry -}; - -#undef RULE_HASH_STATS -#undef PRINT_UNIVERSAL_RULES - -#ifdef RULE_HASH_STATS -#define RULE_HASH_STAT_INCREMENT(var_) PR_BEGIN_MACRO ++(var_); PR_END_MACRO -#else -#define RULE_HASH_STAT_INCREMENT(var_) PR_BEGIN_MACRO PR_END_MACRO -#endif - -struct NodeMatchContext; - -class RuleHash { -public: - explicit RuleHash(bool aQuirksMode); - ~RuleHash(); - void AppendRule(const RuleSelectorPair &aRuleInfo); - void EnumerateAllRules(Element* aElement, ElementDependentRuleProcessorData* aData, - NodeMatchContext& aNodeMatchContext); - - size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const; - size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const; - -protected: - typedef nsTArray RuleValueList; - void AppendRuleToTable(PLDHashTable* aTable, const void* aKey, - const RuleSelectorPair& aRuleInfo); - void AppendUniversalRule(const RuleSelectorPair& aRuleInfo); - - int32_t mRuleCount; - - PLDHashTable mIdTable; - PLDHashTable mClassTable; - PLDHashTable mTagTable; - PLDHashTable mNameSpaceTable; - RuleValueList mUniversalRules; - - struct EnumData { - const RuleValue* mCurValue; - const RuleValue* mEnd; - }; - EnumData* mEnumList; - int32_t mEnumListSize; - - bool mQuirksMode; - - inline EnumData ToEnumData(const RuleValueList& arr) { - EnumData data = { arr.Elements(), arr.Elements() + arr.Length() }; - return data; - } - -#ifdef RULE_HASH_STATS - uint32_t mUniversalSelectors; - uint32_t mNameSpaceSelectors; - uint32_t mTagSelectors; - uint32_t mClassSelectors; - uint32_t mIdSelectors; - - uint32_t mElementsMatched; - - uint32_t mElementUniversalCalls; - uint32_t mElementNameSpaceCalls; - uint32_t mElementTagCalls; - uint32_t mElementClassCalls; - uint32_t mElementIdCalls; -#endif // RULE_HASH_STATS -}; - -RuleHash::RuleHash(bool aQuirksMode) - : mRuleCount(0), - mIdTable(aQuirksMode ? &RuleHash_IdTable_CIOps - : &RuleHash_IdTable_CSOps, - sizeof(RuleHashTableEntry)), - mClassTable(aQuirksMode ? &RuleHash_ClassTable_CIOps - : &RuleHash_ClassTable_CSOps, - sizeof(RuleHashTableEntry)), - mTagTable(&RuleHash_TagTable_Ops, sizeof(RuleHashTagTableEntry)), - mNameSpaceTable(&RuleHash_NameSpaceTable_Ops, sizeof(RuleHashTableEntry)), - mUniversalRules(0), - mEnumList(nullptr), mEnumListSize(0), - mQuirksMode(aQuirksMode) -#ifdef RULE_HASH_STATS - , - mUniversalSelectors(0), - mNameSpaceSelectors(0), - mTagSelectors(0), - mClassSelectors(0), - mIdSelectors(0), - mElementsMatched(0), - mElementUniversalCalls(0), - mElementNameSpaceCalls(0), - mElementTagCalls(0), - mElementClassCalls(0), - mElementIdCalls(0) -#endif -{ - MOZ_COUNT_CTOR(RuleHash); -} - -RuleHash::~RuleHash() -{ - MOZ_COUNT_DTOR(RuleHash); -#ifdef RULE_HASH_STATS - printf( -"RuleHash(%p):\n" -" Selectors: Universal (%u) NameSpace(%u) Tag(%u) Class(%u) Id(%u)\n" -" Content Nodes: Elements(%u)\n" -" Element Calls: Universal(%u) NameSpace(%u) Tag(%u) Class(%u) Id(%u)\n" - static_cast(this), - mUniversalSelectors, mNameSpaceSelectors, mTagSelectors, - mClassSelectors, mIdSelectors, - mElementsMatched, - mElementUniversalCalls, mElementNameSpaceCalls, mElementTagCalls, - mElementClassCalls, mElementIdCalls); -#ifdef PRINT_UNIVERSAL_RULES - { - if (mUniversalRules.Length() > 0) { - printf(" Universal rules:\n"); - for (uint32_t i = 0; i < mUniversalRules.Length(); ++i) { - RuleValue* value = &(mUniversalRules[i]); - nsAutoString selectorText; - uint32_t lineNumber = value->mRule->GetLineNumber(); - RefPtr cssSheet = value->mRule->GetStyleSheet(); - value->mSelector->ToString(selectorText, cssSheet); - - printf(" line %d, %s\n", - lineNumber, NS_ConvertUTF16toUTF8(selectorText).get()); - } - } - } -#endif // PRINT_UNIVERSAL_RULES -#endif // RULE_HASH_STATS - // Rule Values are arena allocated no need to delete them. Their destructor - // isn't doing any cleanup. So we dont even bother to enumerate through - // the hash tables and call their destructors. - if (nullptr != mEnumList) { - delete [] mEnumList; - } -} - -void RuleHash::AppendRuleToTable(PLDHashTable* aTable, const void* aKey, - const RuleSelectorPair& aRuleInfo) -{ - // Get a new or existing entry. - auto entry = static_cast(aTable->Add(aKey, fallible)); - if (!entry) - return; - entry->mRules.AppendElement(RuleValue(aRuleInfo, mRuleCount++, mQuirksMode)); -} - -static void -AppendRuleToTagTable(PLDHashTable* aTable, nsIAtom* aKey, - const RuleValue& aRuleInfo) -{ - // Get a new or exisiting entry - auto entry = static_cast(aTable->Add(aKey, fallible)); - if (!entry) - return; - - entry->mRules.AppendElement(aRuleInfo); -} - -void RuleHash::AppendUniversalRule(const RuleSelectorPair& aRuleInfo) -{ - mUniversalRules.AppendElement(RuleValue(aRuleInfo, mRuleCount++, mQuirksMode)); -} - -void RuleHash::AppendRule(const RuleSelectorPair& aRuleInfo) -{ - nsCSSSelector *selector = aRuleInfo.mSelector; - if (selector->IsPseudoElement()) { - selector = selector->mNext; - } - if (nullptr != selector->mIDList) { - AppendRuleToTable(&mIdTable, selector->mIDList->mAtom, aRuleInfo); - RULE_HASH_STAT_INCREMENT(mIdSelectors); - } - else if (nullptr != selector->mClassList) { - AppendRuleToTable(&mClassTable, selector->mClassList->mAtom, aRuleInfo); - RULE_HASH_STAT_INCREMENT(mClassSelectors); - } - else if (selector->mLowercaseTag) { - RuleValue ruleValue(aRuleInfo, mRuleCount++, mQuirksMode); - AppendRuleToTagTable(&mTagTable, selector->mLowercaseTag, ruleValue); - RULE_HASH_STAT_INCREMENT(mTagSelectors); - if (selector->mCasedTag && - selector->mCasedTag != selector->mLowercaseTag) { - AppendRuleToTagTable(&mTagTable, selector->mCasedTag, ruleValue); - RULE_HASH_STAT_INCREMENT(mTagSelectors); - } - } - else if (kNameSpaceID_Unknown != selector->mNameSpace) { - AppendRuleToTable(&mNameSpaceTable, - NS_INT32_TO_PTR(selector->mNameSpace), aRuleInfo); - RULE_HASH_STAT_INCREMENT(mNameSpaceSelectors); - } - else { // universal tag selector - AppendUniversalRule(aRuleInfo); - RULE_HASH_STAT_INCREMENT(mUniversalSelectors); - } -} - -// this should cover practically all cases so we don't need to reallocate -#define MIN_ENUM_LIST_SIZE 8 - -#ifdef RULE_HASH_STATS -#define RULE_HASH_STAT_INCREMENT_LIST_COUNT(list_, var_) \ - (var_) += (list_).Length() -#else -#define RULE_HASH_STAT_INCREMENT_LIST_COUNT(list_, var_) \ - PR_BEGIN_MACRO PR_END_MACRO -#endif - -static inline -void ContentEnumFunc(const RuleValue &value, nsCSSSelector* selector, - ElementDependentRuleProcessorData* data, NodeMatchContext& nodeContext, - AncestorFilter *ancestorFilter); - -void RuleHash::EnumerateAllRules(Element* aElement, ElementDependentRuleProcessorData* aData, - NodeMatchContext& aNodeContext) -{ - int32_t nameSpace = aElement->GetNameSpaceID(); - nsIAtom* tag = aElement->NodeInfo()->NameAtom(); - nsIAtom* id = aElement->GetID(); - const nsAttrValue* classList = aElement->GetClasses(); - - MOZ_ASSERT(tag, "How could we not have a tag?"); - - int32_t classCount = classList ? classList->GetAtomCount() : 0; - - // assume 1 universal, tag, id, and namespace, rather than wasting - // time counting - int32_t testCount = classCount + 4; - - if (mEnumListSize < testCount) { - delete [] mEnumList; - mEnumListSize = std::max(testCount, MIN_ENUM_LIST_SIZE); - mEnumList = new EnumData[mEnumListSize]; - } - - int32_t valueCount = 0; - RULE_HASH_STAT_INCREMENT(mElementsMatched); - - if (mUniversalRules.Length() != 0) { // universal rules - mEnumList[valueCount++] = ToEnumData(mUniversalRules); - RULE_HASH_STAT_INCREMENT_LIST_COUNT(mUniversalRules, mElementUniversalCalls); - } - // universal rules within the namespace - if (kNameSpaceID_Unknown != nameSpace && mNameSpaceTable.EntryCount() > 0) { - auto entry = static_cast - (mNameSpaceTable.Search(NS_INT32_TO_PTR(nameSpace))); - if (entry) { - mEnumList[valueCount++] = ToEnumData(entry->mRules); - RULE_HASH_STAT_INCREMENT_LIST_COUNT(entry->mRules, mElementNameSpaceCalls); - } - } - if (mTagTable.EntryCount() > 0) { - auto entry = static_cast(mTagTable.Search(tag)); - if (entry) { - mEnumList[valueCount++] = ToEnumData(entry->mRules); - RULE_HASH_STAT_INCREMENT_LIST_COUNT(entry->mRules, mElementTagCalls); - } - } - if (id && mIdTable.EntryCount() > 0) { - auto entry = static_cast(mIdTable.Search(id)); - if (entry) { - mEnumList[valueCount++] = ToEnumData(entry->mRules); - RULE_HASH_STAT_INCREMENT_LIST_COUNT(entry->mRules, mElementIdCalls); - } - } - if (mClassTable.EntryCount() > 0) { - for (int32_t index = 0; index < classCount; ++index) { - auto entry = static_cast - (mClassTable.Search(classList->AtomAt(index))); - if (entry) { - mEnumList[valueCount++] = ToEnumData(entry->mRules); - RULE_HASH_STAT_INCREMENT_LIST_COUNT(entry->mRules, mElementClassCalls); - } - } - } - NS_ASSERTION(valueCount <= testCount, "values exceeded list size"); - - if (valueCount > 0) { - AncestorFilter *filter = - aData->mTreeMatchContext.mAncestorFilter.HasFilter() ? - &aData->mTreeMatchContext.mAncestorFilter : nullptr; -#ifdef DEBUG - bool isRestricted = (aData->mTreeMatchContext.mShadowHosts.Length() > 0 || - aData->mTreeMatchContext.mRestrictToSlottedPseudo || - aData->mTreeMatchContext.mOnlyMatchHostPseudo || - aData->mTreeMatchContext.mForAssignedSlot); - if (filter && !isRestricted) { - filter->AssertHasAllAncestors(aElement); - } -#endif - bool isForAssignedSlot = aData->mTreeMatchContext.mForAssignedSlot; - // Merge the lists while there are still multiple lists to merge. - while (valueCount > 1) { - int32_t valueIndex = 0; - int32_t lowestRuleIndex = mEnumList[valueIndex].mCurValue->mIndex; - for (int32_t index = 1; index < valueCount; ++index) { - int32_t ruleIndex = mEnumList[index].mCurValue->mIndex; - if (ruleIndex < lowestRuleIndex) { - valueIndex = index; - lowestRuleIndex = ruleIndex; - } - } - const RuleValue *cur = mEnumList[valueIndex].mCurValue; - aData->mTreeMatchContext.mForAssignedSlot = isForAssignedSlot; - ContentEnumFunc(*cur, cur->mSelector, aData, aNodeContext, filter); - cur++; - if (cur == mEnumList[valueIndex].mEnd) { - mEnumList[valueIndex] = mEnumList[--valueCount]; - } else { - mEnumList[valueIndex].mCurValue = cur; - } - } - - // Fast loop over single value. - for (const RuleValue *value = mEnumList[0].mCurValue, - *end = mEnumList[0].mEnd; - value != end; ++value) { - aData->mTreeMatchContext.mForAssignedSlot = isForAssignedSlot; - ContentEnumFunc(*value, value->mSelector, aData, aNodeContext, filter); - } - } -} - -static size_t -SizeOfRuleHashTable(const PLDHashTable& aTable, MallocSizeOf aMallocSizeOf) -{ - size_t n = aTable.ShallowSizeOfExcludingThis(aMallocSizeOf); - for (auto iter = aTable.ConstIter(); !iter.Done(); iter.Next()) { - auto entry = static_cast(iter.Get()); - n += entry->mRules.ShallowSizeOfExcludingThis(aMallocSizeOf); - } - return n; -} - -size_t -RuleHash::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const -{ - size_t n = 0; - - n += SizeOfRuleHashTable(mIdTable, aMallocSizeOf); - - n += SizeOfRuleHashTable(mClassTable, aMallocSizeOf); - - n += SizeOfRuleHashTable(mTagTable, aMallocSizeOf); - - n += SizeOfRuleHashTable(mNameSpaceTable, aMallocSizeOf); - - n += mUniversalRules.ShallowSizeOfExcludingThis(aMallocSizeOf); - - return n; -} - -size_t -RuleHash::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const -{ - return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf); -} - -//-------------------------------- - -/** - * A struct that stores an nsCSSSelector pointer along side a pointer to - * the rightmost nsCSSSelector in the selector. For example, for - * - * .main p > span - * - * if mSelector points to the |p| nsCSSSelector, mRightmostSelector would - * point to the |span| nsCSSSelector. - * - * Both mSelector and mRightmostSelector are always top-level selectors, - * i.e. they aren't selectors within a :not() or :-moz-any(). - */ -struct SelectorPair -{ - SelectorPair(nsCSSSelector* aSelector, nsCSSSelector* aRightmostSelector) - : mSelector(aSelector), mRightmostSelector(aRightmostSelector) - { - MOZ_ASSERT(aSelector); - MOZ_ASSERT(mRightmostSelector); - } - SelectorPair(const SelectorPair& aOther) = default; - nsCSSSelector* const mSelector; - nsCSSSelector* const mRightmostSelector; -}; - -// A hash table mapping atoms to lists of selectors -struct AtomSelectorEntry : public PLDHashEntryHdr { - nsIAtom *mAtom; - // Auto length 2, because a decent fraction of these arrays ends up - // with 2 elements, and each entry is cheap. - AutoTArray mSelectors; -}; - -static void -AtomSelector_ClearEntry(PLDHashTable *table, PLDHashEntryHdr *hdr) -{ - (static_cast(hdr))->~AtomSelectorEntry(); -} - -static void -AtomSelector_InitEntry(PLDHashEntryHdr *hdr, const void *key) -{ - AtomSelectorEntry *entry = static_cast(hdr); - new (KnownNotNull, entry) AtomSelectorEntry(); - entry->mAtom = const_cast(static_cast(key)); -} - -static void -AtomSelector_MoveEntry(PLDHashTable *table, const PLDHashEntryHdr *from, - PLDHashEntryHdr *to) -{ - NS_PRECONDITION(from != to, "This is not going to work!"); - AtomSelectorEntry *oldEntry = - const_cast(static_cast(from)); - auto* newEntry = new (KnownNotNull, to) AtomSelectorEntry(); - newEntry->mAtom = oldEntry->mAtom; - newEntry->mSelectors.SwapElements(oldEntry->mSelectors); - oldEntry->~AtomSelectorEntry(); -} - -static bool -AtomSelector_CIMatchEntry(const PLDHashEntryHdr *hdr, const void *key) -{ - const AtomSelectorEntry *entry = static_cast(hdr); - return CIMatchAtoms(key, entry->mAtom); -} - -// Case-sensitive ops. -static const PLDHashTableOps AtomSelector_CSOps = { - PLDHashTable::HashVoidPtrKeyStub, - PLDHashTable::MatchEntryStub, - AtomSelector_MoveEntry, - AtomSelector_ClearEntry, - AtomSelector_InitEntry -}; - -// Case-insensitive ops. -static const PLDHashTableOps AtomSelector_CIOps = { - RuleHash_CIHashKey, - AtomSelector_CIMatchEntry, - AtomSelector_MoveEntry, - AtomSelector_ClearEntry, - AtomSelector_InitEntry -}; - -//-------------------------------- - -struct RuleCascadeData { - RuleCascadeData(nsIAtom *aMedium, bool aQuirksMode) - : mRuleHash(aQuirksMode), - mStateSelectors(), - mSelectorDocumentStates(0), - mClassSelectors(aQuirksMode ? &AtomSelector_CIOps - : &AtomSelector_CSOps, - sizeof(AtomSelectorEntry)), - mIdSelectors(aQuirksMode ? &AtomSelector_CIOps - : &AtomSelector_CSOps, - sizeof(AtomSelectorEntry)), - // mAttributeSelectors is matching on the attribute _name_, not the - // value, and we case-fold names at parse-time, so this is a - // case-sensitive match. - mAttributeSelectors(&AtomSelector_CSOps, sizeof(AtomSelectorEntry)), - mAnonBoxRules(&RuleHash_TagTable_Ops, sizeof(RuleHashTagTableEntry)), -#ifdef MOZ_XUL - mXULTreeRules(&RuleHash_TagTable_Ops, sizeof(RuleHashTagTableEntry)), -#endif - mKeyframesRuleTable(), - mCounterStyleRuleTable(), - mCacheKey(aMedium), - mQuirksMode(aQuirksMode) - { - memset(mPseudoElementRuleHashes, 0, sizeof(mPseudoElementRuleHashes)); - } - - ~RuleCascadeData() - { - for (uint32_t i = 0; i < ArrayLength(mPseudoElementRuleHashes); ++i) { - delete mPseudoElementRuleHashes[i]; - } - } - - size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const; - - RuleHash mRuleHash; - RuleHash* mPseudoElementRuleHashes[ - static_cast(CSSPseudoElementType::Count)]; - nsTArray mStateSelectors; - EventStates mSelectorDocumentStates; - PLDHashTable mClassSelectors; - PLDHashTable mIdSelectors; - nsTArray mPossiblyNegatedClassSelectors; - nsTArray mPossiblyNegatedIDSelectors; - PLDHashTable mAttributeSelectors; - PLDHashTable mAnonBoxRules; -#ifdef MOZ_XUL - PLDHashTable mXULTreeRules; -#endif - - nsTArray mFontFaceRules; - nsTArray mKeyframesRules; - nsTArray mFontFeatureValuesRules; - nsTArray mPageRules; - nsTArray mCounterStyleRules; - - nsDataHashtable mKeyframesRuleTable; - nsDataHashtable mCounterStyleRuleTable; - - // Looks up or creates the appropriate list in |mAttributeSelectors|. - // Returns null only on allocation failure. - nsTArray* AttributeListFor(nsIAtom* aAttribute); - - nsMediaQueryResultCacheKey mCacheKey; - - const bool mQuirksMode; -}; - -static size_t -SizeOfSelectorsHashTable(const PLDHashTable& aTable, MallocSizeOf aMallocSizeOf) -{ - size_t n = aTable.ShallowSizeOfExcludingThis(aMallocSizeOf); - for (auto iter = aTable.ConstIter(); !iter.Done(); iter.Next()) { - auto entry = static_cast(iter.Get()); - n += entry->mSelectors.ShallowSizeOfExcludingThis(aMallocSizeOf); - } - return n; -} - -size_t -RuleCascadeData::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const -{ - size_t n = aMallocSizeOf(this); - - n += mRuleHash.SizeOfExcludingThis(aMallocSizeOf); - for (uint32_t i = 0; i < ArrayLength(mPseudoElementRuleHashes); ++i) { - if (mPseudoElementRuleHashes[i]) - n += mPseudoElementRuleHashes[i]->SizeOfIncludingThis(aMallocSizeOf); - } - - n += mStateSelectors.ShallowSizeOfExcludingThis(aMallocSizeOf); - - n += SizeOfSelectorsHashTable(mIdSelectors, aMallocSizeOf); - n += SizeOfSelectorsHashTable(mClassSelectors, aMallocSizeOf); - - n += mPossiblyNegatedClassSelectors.ShallowSizeOfExcludingThis(aMallocSizeOf); - n += mPossiblyNegatedIDSelectors.ShallowSizeOfExcludingThis(aMallocSizeOf); - - n += SizeOfSelectorsHashTable(mAttributeSelectors, aMallocSizeOf); - n += SizeOfRuleHashTable(mAnonBoxRules, aMallocSizeOf); -#ifdef MOZ_XUL - n += SizeOfRuleHashTable(mXULTreeRules, aMallocSizeOf); -#endif - - n += mFontFaceRules.ShallowSizeOfExcludingThis(aMallocSizeOf); - n += mKeyframesRules.ShallowSizeOfExcludingThis(aMallocSizeOf); - n += mFontFeatureValuesRules.ShallowSizeOfExcludingThis(aMallocSizeOf); - n += mPageRules.ShallowSizeOfExcludingThis(aMallocSizeOf); - n += mCounterStyleRules.ShallowSizeOfExcludingThis(aMallocSizeOf); - - n += mKeyframesRuleTable.ShallowSizeOfExcludingThis(aMallocSizeOf); - for (auto iter = mKeyframesRuleTable.ConstIter(); !iter.Done(); iter.Next()) { - // We don't own the nsCSSKeyframesRule objects so we don't count them. We - // do care about the size of the keys' nsAString members' buffers though. - // - // Note that we depend on nsStringHashKey::GetKey() returning a reference, - // since otherwise aKey would be a copy of the string key and we would not - // be measuring the right object here. - n += iter.Key().SizeOfExcludingThisIfUnshared(aMallocSizeOf); - } - - return n; -} - -nsTArray* -RuleCascadeData::AttributeListFor(nsIAtom* aAttribute) -{ - auto entry = static_cast - (mAttributeSelectors.Add(aAttribute, fallible)); - if (!entry) - return nullptr; - return &entry->mSelectors; -} - -struct ResolvedRuleCascades { - ResolvedRuleCascades() - : mUnlayered(nullptr) - , mNext(nullptr) - { - } - - ~ResolvedRuleCascades() - { - for (RuleCascadeData* data : mOrderedData) { - delete data; - } - } - - nsTArray mOrderedData; - RuleCascadeData* mUnlayered; - ResolvedRuleCascades* mNext; // for a different medium - - size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const; -}; - -size_t -ResolvedRuleCascades::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const -{ - size_t n = aMallocSizeOf(this); - n += mOrderedData.ShallowSizeOfExcludingThis(aMallocSizeOf); - for (RuleCascadeData* data : mOrderedData) { - n += data->SizeOfIncludingThis(aMallocSizeOf); - } - return n; -} - // ------------------------------- // CSS Style rule processor implementation // @@ -1096,1869 +143,6 @@ nsCSSRuleProcessor::ClearSheets() mSheets.Clear(); } -/* static */ void -nsCSSRuleProcessor::Startup() -{ - Preferences::AddBoolVarCache(&gSupportVisitedPseudo, VISITED_PSEUDO_PREF, - true); -} - -static bool -InitSystemMetrics() -{ - NS_ASSERTION(!sSystemMetrics, "already initialized"); - - sSystemMetrics = new nsTArray< nsCOMPtr >; - NS_ENSURE_TRUE(sSystemMetrics, false); - - /*************************************************************************** - * ANY METRICS ADDED HERE SHOULD ALSO BE ADDED AS MEDIA QUERIES IN * - * nsMediaFeatures.cpp * - ***************************************************************************/ - - int32_t metricResult = - LookAndFeel::GetInt(LookAndFeel::eIntID_ScrollArrowStyle); - if (metricResult & LookAndFeel::eScrollArrow_StartBackward) { - sSystemMetrics->AppendElement(nsGkAtoms::scrollbar_start_backward); - } - if (metricResult & LookAndFeel::eScrollArrow_StartForward) { - sSystemMetrics->AppendElement(nsGkAtoms::scrollbar_start_forward); - } - if (metricResult & LookAndFeel::eScrollArrow_EndBackward) { - sSystemMetrics->AppendElement(nsGkAtoms::scrollbar_end_backward); - } - if (metricResult & LookAndFeel::eScrollArrow_EndForward) { - sSystemMetrics->AppendElement(nsGkAtoms::scrollbar_end_forward); - } - - metricResult = - LookAndFeel::GetInt(LookAndFeel::eIntID_ScrollSliderStyle); - if (metricResult != LookAndFeel::eScrollThumbStyle_Normal) { - sSystemMetrics->AppendElement(nsGkAtoms::scrollbar_thumb_proportional); - } - - metricResult = - LookAndFeel::GetInt(LookAndFeel::eIntID_UseOverlayScrollbars); - if (metricResult) { - sSystemMetrics->AppendElement(nsGkAtoms::overlay_scrollbars); - } - - metricResult = - LookAndFeel::GetInt(LookAndFeel::eIntID_MenuBarDrag); - if (metricResult) { - sSystemMetrics->AppendElement(nsGkAtoms::menubar_drag); - } - - nsresult rv = - LookAndFeel::GetInt(LookAndFeel::eIntID_WindowsDefaultTheme, &metricResult); - if (NS_SUCCEEDED(rv) && metricResult) { - sSystemMetrics->AppendElement(nsGkAtoms::windows_default_theme); - } - - rv = LookAndFeel::GetInt(LookAndFeel::eIntID_MacGraphiteTheme, &metricResult); - if (NS_SUCCEEDED(rv) && metricResult) { - sSystemMetrics->AppendElement(nsGkAtoms::mac_graphite_theme); - } - - rv = LookAndFeel::GetInt(LookAndFeel::eIntID_MacLionTheme, &metricResult); - if (NS_SUCCEEDED(rv) && metricResult) { - sSystemMetrics->AppendElement(nsGkAtoms::mac_lion_theme); - } - - rv = LookAndFeel::GetInt(LookAndFeel::eIntID_MacYosemiteTheme, &metricResult); - if (NS_SUCCEEDED(rv) && metricResult) { - sSystemMetrics->AppendElement(nsGkAtoms::mac_yosemite_theme); - } - - rv = LookAndFeel::GetInt(LookAndFeel::eIntID_WindowsAccentColorApplies, &metricResult); - if (NS_SUCCEEDED(rv) && metricResult) { - sSystemMetrics->AppendElement(nsGkAtoms::windows_accent_color_applies); - } - - rv = LookAndFeel::GetInt(LookAndFeel::eIntID_WindowsAccentColorIsDark, &metricResult); - if (NS_SUCCEEDED(rv) && metricResult) { - sSystemMetrics->AppendElement(nsGkAtoms::windows_accent_color_is_dark); - } - - rv = LookAndFeel::GetInt(LookAndFeel::eIntID_DWMCompositor, &metricResult); - if (NS_SUCCEEDED(rv) && metricResult) { - sSystemMetrics->AppendElement(nsGkAtoms::windows_compositor); - } - - rv = LookAndFeel::GetInt(LookAndFeel::eIntID_WindowsGlass, &metricResult); - if (NS_SUCCEEDED(rv) && metricResult) { - sSystemMetrics->AppendElement(nsGkAtoms::windows_glass); - } - - rv = LookAndFeel::GetInt(LookAndFeel::eIntID_ColorPickerAvailable, &metricResult); - if (NS_SUCCEEDED(rv) && metricResult) { - sSystemMetrics->AppendElement(nsGkAtoms::color_picker_available); - } - - rv = LookAndFeel::GetInt(LookAndFeel::eIntID_WindowsClassic, &metricResult); - if (NS_SUCCEEDED(rv) && metricResult) { - sSystemMetrics->AppendElement(nsGkAtoms::windows_classic); - } - - rv = LookAndFeel::GetInt(LookAndFeel::eIntID_TouchEnabled, &metricResult); - if (NS_SUCCEEDED(rv) && metricResult) { - sSystemMetrics->AppendElement(nsGkAtoms::touch_enabled); - } - - rv = LookAndFeel::GetInt(LookAndFeel::eIntID_SwipeAnimationEnabled, - &metricResult); - if (NS_SUCCEEDED(rv) && metricResult) { - sSystemMetrics->AppendElement(nsGkAtoms::swipe_animation_enabled); - } - - rv = LookAndFeel::GetInt(LookAndFeel::eIntID_PhysicalHomeButton, - &metricResult); - if (NS_SUCCEEDED(rv) && metricResult) { - sSystemMetrics->AppendElement(nsGkAtoms::physical_home_button); - } - -#ifdef XP_WIN - if (NS_SUCCEEDED( - LookAndFeel::GetInt(LookAndFeel::eIntID_WindowsThemeIdentifier, - &metricResult))) { - nsCSSRuleProcessor::SetWindowsThemeIdentifier(static_cast(metricResult)); - switch(metricResult) { - case LookAndFeel::eWindowsTheme_Aero: - sSystemMetrics->AppendElement(nsGkAtoms::windows_theme_aero); - break; - case LookAndFeel::eWindowsTheme_AeroLite: - sSystemMetrics->AppendElement(nsGkAtoms::windows_theme_aero_lite); - break; - case LookAndFeel::eWindowsTheme_LunaBlue: - sSystemMetrics->AppendElement(nsGkAtoms::windows_theme_luna_blue); - break; - case LookAndFeel::eWindowsTheme_LunaOlive: - sSystemMetrics->AppendElement(nsGkAtoms::windows_theme_luna_olive); - break; - case LookAndFeel::eWindowsTheme_LunaSilver: - sSystemMetrics->AppendElement(nsGkAtoms::windows_theme_luna_silver); - break; - case LookAndFeel::eWindowsTheme_Royale: - sSystemMetrics->AppendElement(nsGkAtoms::windows_theme_royale); - break; - case LookAndFeel::eWindowsTheme_Zune: - sSystemMetrics->AppendElement(nsGkAtoms::windows_theme_zune); - break; - case LookAndFeel::eWindowsTheme_Generic: - sSystemMetrics->AppendElement(nsGkAtoms::windows_theme_generic); - break; - } - } -#endif - - return true; -} - -/* static */ void -nsCSSRuleProcessor::FreeSystemMetrics() -{ - delete sSystemMetrics; - sSystemMetrics = nullptr; -} - -/* static */ void -nsCSSRuleProcessor::Shutdown() -{ - FreeSystemMetrics(); -} - -/* static */ bool -nsCSSRuleProcessor::HasSystemMetric(nsIAtom* aMetric) -{ - if (!sSystemMetrics && !InitSystemMetrics()) { - return false; - } - return sSystemMetrics->IndexOf(aMetric) != sSystemMetrics->NoIndex; -} - -#ifdef XP_WIN -/* static */ uint8_t -nsCSSRuleProcessor::GetWindowsThemeIdentifier() -{ - if (!sSystemMetrics) - InitSystemMetrics(); - return sWinThemeId; -} -#endif - -/* static */ -EventStates -nsCSSRuleProcessor::GetContentState(Element* aElement, const TreeMatchContext& aTreeMatchContext) -{ - EventStates state = aElement->StyleState(); - - // If we are not supposed to mark visited links as such, be sure to - // flip the bits appropriately. We want to do this here, rather - // than in GetContentStateForVisitedHandling, so that we don't - // expose that :visited support is disabled to the Web page. - if (state.HasState(NS_EVENT_STATE_VISITED) && - (!gSupportVisitedPseudo || - aElement->OwnerDoc()->IsBeingUsedAsImage() || - aTreeMatchContext.mUsingPrivateBrowsing)) { - state &= ~NS_EVENT_STATE_VISITED; - state |= NS_EVENT_STATE_UNVISITED; - } - return state; -} - -/* static */ -bool -nsCSSRuleProcessor::IsLink(const Element* aElement) -{ - EventStates state = aElement->StyleState(); - return state.HasAtLeastOneOfStates(NS_EVENT_STATE_VISITED | NS_EVENT_STATE_UNVISITED); -} - -/* static */ -EventStates -nsCSSRuleProcessor::GetContentStateForVisitedHandling( - Element* aElement, - const TreeMatchContext& aTreeMatchContext, - nsRuleWalker::VisitedHandlingType aVisitedHandling, - bool aIsRelevantLink) -{ - EventStates contentState = GetContentState(aElement, aTreeMatchContext); - if (contentState.HasAtLeastOneOfStates(NS_EVENT_STATE_VISITED | NS_EVENT_STATE_UNVISITED)) { - MOZ_ASSERT(IsLink(aElement), "IsLink() should match state"); - contentState &= ~(NS_EVENT_STATE_VISITED | NS_EVENT_STATE_UNVISITED); - if (aIsRelevantLink) { - switch (aVisitedHandling) { - case nsRuleWalker::eRelevantLinkUnvisited: - contentState |= NS_EVENT_STATE_UNVISITED; - break; - case nsRuleWalker::eRelevantLinkVisited: - contentState |= NS_EVENT_STATE_VISITED; - break; - case nsRuleWalker::eLinksVisitedOrUnvisited: - contentState |= NS_EVENT_STATE_UNVISITED | NS_EVENT_STATE_VISITED; - break; - } - } else { - contentState |= NS_EVENT_STATE_UNVISITED; - } - } - return contentState; -} - -/** - * A |NodeMatchContext| has data about matching a selector (without - * combinators) against a single node. It contains only input to the - * matching. - * - * Unlike |RuleProcessorData|, which is similar, a |NodeMatchContext| - * can vary depending on the selector matching process. In other words, - * there might be multiple NodeMatchContexts corresponding to a single - * node, but only one possible RuleProcessorData. - */ -struct NodeMatchContext { - // In order to implement nsCSSRuleProcessor::HasStateDependentStyle, - // we need to be able to see if a node might match an - // event-state-dependent selector for any value of that event state. - // So mStateMask contains the states that should NOT be tested. - // - // NOTE: For |mStateMask| to work correctly, it's important that any - // change that changes multiple state bits include all those state - // bits in the notification. Otherwise, if multiple states change but - // we do separate notifications then we might determine the style is - // not state-dependent when it really is (e.g., determining that a - // :hover:active rule no longer matches when both states are unset). - const EventStates mStateMask; - - // Is this link the unique link whose visitedness can affect the style - // of the node being matched? (That link is the nearest link to the - // node being matched that is itself or an ancestor.) - // - // Always false when TreeMatchContext::mForStyling is false. (We - // could figure it out for RestrictedSelectorListMatches, but we're - // starting from the middle of the selector list when doing - // Has{Attribute,State}DependentStyle, so we can't tell. So when - // mForStyling is false, we have to assume we don't know.) - const bool mIsRelevantLink; - - // If the node should be considered featureless (as specified in - // selectors 4), then mIsFeature should be set to true to prevent - // matching unless the selector is a special pseudo class or pseudo - // element that matches featureless elements. - const bool mIsFeatureless; - - NodeMatchContext(EventStates aStateMask, bool aIsRelevantLink, - bool aIsFeatureless = false) - : mStateMask(aStateMask) - , mIsRelevantLink(aIsRelevantLink) - , mIsFeatureless(aIsFeatureless) - { - } -}; - -/** - * Additional information about a selector (without combinators) that is - * being matched. - */ -enum class SelectorMatchesFlags : uint8_t { - NONE = 0, - - // The selector's flags are unknown. This happens when you don't know - // if you're starting from the top of a selector. Only used in cases - // where it's acceptable for matching to return a false positive. - // (It's not OK to return a false negative.) - UNKNOWN = 1 << 0, - - // The selector is part of a compound selector which has been split in - // half, where the other half is a pseudo-element. The current - // selector is not a pseudo-element itself. - HAS_PSEUDO_ELEMENT = 1 << 1, - - // The selector is part of an argument to a functional pseudo-class or - // pseudo-element. - IS_PSEUDO_CLASS_ARGUMENT = 1 << 2, - - // The selector should be blocked from matching because it is called - // from outside the shadow tree. - IS_OUTSIDE_SHADOW_TREE = 1 << 3 -}; -MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(SelectorMatchesFlags) - -// Return whether the selector matches conditions for the :active and -// :hover quirk. -static inline bool ActiveHoverQuirkMatches(nsCSSSelector* aSelector, - SelectorMatchesFlags aSelectorFlags) -{ - if (aSelector->HasTagSelector() || aSelector->mAttrList || - aSelector->mIDList || aSelector->mClassList || - aSelector->IsPseudoElement() || - aSelector->IsHybridPseudoElement() || - // Having this quirk means that some selectors will no longer match, - // so it's better to return false when we aren't sure (i.e., the - // flags are unknown). - aSelectorFlags & (SelectorMatchesFlags::UNKNOWN | - SelectorMatchesFlags::HAS_PSEUDO_ELEMENT | - SelectorMatchesFlags::IS_PSEUDO_CLASS_ARGUMENT | - SelectorMatchesFlags::IS_OUTSIDE_SHADOW_TREE)) { - return false; - } - - // No pseudo-class other than :active and :hover. - for (nsPseudoClassList* pseudoClass = aSelector->mPseudoClassList; - pseudoClass; pseudoClass = pseudoClass->mNext) { - if (pseudoClass->mType != CSSPseudoClassType::hover && - pseudoClass->mType != CSSPseudoClassType::active) { - return false; - } - } - - return true; -} - - -static inline bool -IsSignificantChild(nsIContent* aChild, bool aTextIsSignificant, - bool aWhitespaceIsSignificant) -{ - return nsStyleUtil::IsSignificantChild(aChild, aTextIsSignificant, - aWhitespaceIsSignificant); -} - -// This function is to be called once we have fetched a value for an attribute -// whose namespace and name match those of aAttrSelector. This function -// performs comparisons on the value only, based on aAttrSelector->mFunction. -static bool AttrMatchesValue(const nsAttrSelector* aAttrSelector, - const nsString& aValue, bool isHTML) -{ - NS_PRECONDITION(aAttrSelector, "Must have an attribute selector"); - - // http://lists.w3.org/Archives/Public/www-style/2008Apr/0038.html - // *= (CONTAINSMATCH) ~= (INCLUDES) ^= (BEGINSMATCH) $= (ENDSMATCH) - // all accept the empty string, but match nothing. - if (aAttrSelector->mValue.IsEmpty() && - (aAttrSelector->mFunction == NS_ATTR_FUNC_INCLUDES || - aAttrSelector->mFunction == NS_ATTR_FUNC_ENDSMATCH || - aAttrSelector->mFunction == NS_ATTR_FUNC_BEGINSMATCH || - aAttrSelector->mFunction == NS_ATTR_FUNC_CONTAINSMATCH)) - return false; - - const nsDefaultStringComparator defaultComparator; - const nsASCIICaseInsensitiveStringComparator ciComparator; - const nsStringComparator& comparator = - aAttrSelector->IsValueCaseSensitive(isHTML) - ? static_cast(defaultComparator) - : static_cast(ciComparator); - - switch (aAttrSelector->mFunction) { - case NS_ATTR_FUNC_EQUALS: - return aValue.Equals(aAttrSelector->mValue, comparator); - case NS_ATTR_FUNC_INCLUDES: - return nsStyleUtil::ValueIncludes(aValue, aAttrSelector->mValue, comparator); - case NS_ATTR_FUNC_DASHMATCH: - return nsStyleUtil::DashMatchCompare(aValue, aAttrSelector->mValue, comparator); - case NS_ATTR_FUNC_ENDSMATCH: - return StringEndsWith(aValue, aAttrSelector->mValue, comparator); - case NS_ATTR_FUNC_BEGINSMATCH: - return StringBeginsWith(aValue, aAttrSelector->mValue, comparator); - case NS_ATTR_FUNC_CONTAINSMATCH: - return FindInReadable(aAttrSelector->mValue, aValue, comparator); - default: - NS_NOTREACHED("Shouldn't be ending up here"); - return false; - } -} - -static inline bool -edgeChildMatches(Element* aElement, TreeMatchContext& aTreeMatchContext, - bool checkFirst, bool checkLast) -{ - nsIContent* parent = aElement->GetParent(); - if (parent && aTreeMatchContext.mForStyling) - parent->SetFlags(NODE_HAS_EDGE_CHILD_SELECTOR); - - return (!checkFirst || - aTreeMatchContext.mNthIndexCache. - GetNthIndex(aElement, false, false, true) == 1) && - (!checkLast || - aTreeMatchContext.mNthIndexCache. - GetNthIndex(aElement, false, true, true) == 1); -} - -static inline bool -nthChildGenericMatches(Element* aElement, - TreeMatchContext& aTreeMatchContext, - nsPseudoClassList* pseudoClass, - bool isOfType, bool isFromEnd) -{ - nsIContent* parent = aElement->GetParent(); - if (parent && aTreeMatchContext.mForStyling) { - if (isFromEnd) - parent->SetFlags(NODE_HAS_SLOW_SELECTOR); - else - parent->SetFlags(NODE_HAS_SLOW_SELECTOR_LATER_SIBLINGS); - } - - const int32_t index = aTreeMatchContext.mNthIndexCache. - GetNthIndex(aElement, isOfType, isFromEnd, false); - if (index <= 0) { - // Node is anonymous content (not really a child of its parent). - return false; - } - - const int32_t a = pseudoClass->u.mNumbers[0]; - const int32_t b = pseudoClass->u.mNumbers[1]; - // result should be true if there exists n >= 0 such that - // a * n + b == index. - if (a == 0) { - return b == index; - } - - // Integer division in C does truncation (towards 0). So - // check that the result is nonnegative, and that there was no - // truncation. - const CheckedInt indexMinusB = CheckedInt(index) - b; - const CheckedInt n = indexMinusB / a; - return n.isValid() && - n.value() >= 0 && - a * n == indexMinusB; -} - -static inline bool -edgeOfTypeMatches(Element* aElement, TreeMatchContext& aTreeMatchContext, - bool checkFirst, bool checkLast) -{ - nsIContent *parent = aElement->GetParent(); - if (parent && aTreeMatchContext.mForStyling) { - if (checkLast) - parent->SetFlags(NODE_HAS_SLOW_SELECTOR); - else - parent->SetFlags(NODE_HAS_SLOW_SELECTOR_LATER_SIBLINGS); - } - - return (!checkFirst || - aTreeMatchContext.mNthIndexCache. - GetNthIndex(aElement, true, false, true) == 1) && - (!checkLast || - aTreeMatchContext.mNthIndexCache. - GetNthIndex(aElement, true, true, true) == 1); -} - -static inline bool -checkGenericEmptyMatches(Element* aElement, - TreeMatchContext& aTreeMatchContext, - bool isWhitespaceSignificant) -{ - nsIContent *child = nullptr; - int32_t index = -1; - - if (aTreeMatchContext.mForStyling) - aElement->SetFlags(NODE_HAS_EMPTY_SELECTOR); - - do { - child = aElement->GetChildAt(++index); - // stop at first non-comment (and non-whitespace for - // :-moz-only-whitespace) node - } while (child && !IsSignificantChild(child, true, isWhitespaceSignificant)); - return (child == nullptr); -} - -// Arrays of the states that are relevant for various pseudoclasses. -static const EventStates sPseudoClassStateDependences[] = { -#define CSS_PSEUDO_CLASS(_name, _value, _flags, _pref) \ - EventStates(), -#define CSS_STATE_DEPENDENT_PSEUDO_CLASS(_name, _value, _flags, _pref, _states) \ - _states, -#include "nsCSSPseudoClassList.h" -#undef CSS_STATE_DEPENDENT_PSEUDO_CLASS -#undef CSS_PSEUDO_CLASS - // Add more entries for our fake values to make sure we can't - // index out of bounds into this array no matter what. - EventStates(), - EventStates() -}; - -static const EventStates sPseudoClassStates[] = { -#define CSS_PSEUDO_CLASS(_name, _value, _flags, _pref) \ - EventStates(), -#define CSS_STATE_PSEUDO_CLASS(_name, _value, _flags, _pref, _states) \ - _states, -#include "nsCSSPseudoClassList.h" -#undef CSS_STATE_PSEUDO_CLASS -#undef CSS_PSEUDO_CLASS - // Add more entries for our fake values to make sure we can't - // index out of bounds into this array no matter what. - EventStates(), - EventStates() -}; -static_assert(MOZ_ARRAY_LENGTH(sPseudoClassStates) == - static_cast(CSSPseudoClassType::MAX), - "CSSPseudoClassType::MAX is no longer equal to the length of " - "sPseudoClassStates"); - -static bool -StateSelectorMatches(Element* aElement, - nsCSSSelector* aSelector, - NodeMatchContext& aNodeMatchContext, - TreeMatchContext& aTreeMatchContext, - SelectorMatchesFlags aSelectorFlags, - bool* const aDependence, - EventStates aStatesToCheck) -{ - NS_PRECONDITION(!aStatesToCheck.IsEmpty(), - "should only need to call StateSelectorMatches if " - "aStatesToCheck is not empty"); - - // Bit-based pseudo-classes - if (aStatesToCheck.HasAtLeastOneOfStates(NS_EVENT_STATE_ACTIVE | - NS_EVENT_STATE_HOVER) && - aTreeMatchContext.mCompatMode == eCompatibility_NavQuirks && - ActiveHoverQuirkMatches(aSelector, aSelectorFlags) && - aElement->IsHTMLElement() && !nsCSSRuleProcessor::IsLink(aElement)) { - // In quirks mode, only make links sensitive to selectors ":active" - // and ":hover". - return false; - } - - if (aTreeMatchContext.mForStyling && - aStatesToCheck.HasAtLeastOneOfStates(NS_EVENT_STATE_HOVER)) { - // Mark the element as having :hover-dependent style - aElement->SetHasRelevantHoverRules(); - } - - if (aNodeMatchContext.mStateMask.HasAtLeastOneOfStates(aStatesToCheck)) { - if (aDependence) { - *aDependence = true; - } - } else { - EventStates contentState = - nsCSSRuleProcessor::GetContentStateForVisitedHandling( - aElement, - aTreeMatchContext, - aTreeMatchContext.VisitedHandling(), - aNodeMatchContext.mIsRelevantLink); - if (!contentState.HasAtLeastOneOfStates(aStatesToCheck)) { - return false; - } - } - - return true; -} - -static bool SelectorListMatches(Element* aElement, - nsCSSSelectorList* aList, - NodeMatchContext& aNodeMatchContext, - TreeMatchContext& aTreeMatchContext, - SelectorMatchesFlags aSelectorFlags, - bool aIsForgiving = false, - bool aPreventComplexSelectors = false); - -static bool SelectorListMatches(Element* aElement, - nsPseudoClassList* aList, - NodeMatchContext& aNodeMatchContext, - TreeMatchContext& aTreeMatchContext, - bool aIsForgiving = false, - bool aPreventComplexSelectors = false); - -static bool -StateSelectorMatches(Element* aElement, - nsCSSSelector* aSelector, - NodeMatchContext& aNodeMatchContext, - TreeMatchContext& aTreeMatchContext, - SelectorMatchesFlags aSelectorFlags) -{ - for (nsPseudoClassList* pseudoClass = aSelector->mPseudoClassList; - pseudoClass; pseudoClass = pseudoClass->mNext) { - auto idx = static_cast(pseudoClass->mType); - EventStates statesToCheck = sPseudoClassStates[idx]; - if (!statesToCheck.IsEmpty() && - !StateSelectorMatches(aElement, aSelector, aNodeMatchContext, - aTreeMatchContext, aSelectorFlags, nullptr, - statesToCheck)) { - return false; - } - } - return true; -} - -// Returns whether aSelector can match featureless elements. -static bool CanMatchFeaturelessElement(nsCSSSelector* aSelector) -{ - if (aSelector->HasFeatureSelectors()) { - return false; - } - - for (nsPseudoClassList* pseudoClass = aSelector->mPseudoClassList; - pseudoClass; - pseudoClass = pseudoClass->mNext) { - if (pseudoClass->mType == CSSPseudoClassType::host || - pseudoClass->mType == CSSPseudoClassType::hostContext) { - return true; - } - } - - return false; -} - - -// |aDependence| has two functions: -// * when non-null, it indicates that we're processing a negation, -// which is done only when SelectorMatches calls itself recursively -// * what it points to should be set to true whenever a test is skipped -// because of aNodeMatchContent.mStateMask -static bool SelectorMatches(Element* aElement, - nsCSSSelector* aSelector, - NodeMatchContext& aNodeMatchContext, - TreeMatchContext& aTreeMatchContext, - SelectorMatchesFlags aSelectorFlags, - bool* const aDependence = nullptr) -{ - NS_PRECONDITION(!aSelector->IsPseudoElement(), - "Pseudo-element snuck into SelectorMatches?"); - MOZ_ASSERT(aTreeMatchContext.mForStyling || - !aNodeMatchContext.mIsRelevantLink, - "mIsRelevantLink should be set to false when mForStyling " - "is false since we don't know how to set it correctly in " - "Has(Attribute|State)DependentStyle"); - - if (aNodeMatchContext.mIsFeatureless && - !CanMatchFeaturelessElement(aSelector)) { - return false; - } - - Element* targetElement = aElement; - if (aTreeMatchContext.mForAssignedSlot) { - HTMLSlotElement* slot = aElement->GetAssignedSlot(); - // We're likely testing the slottable's ancestors and it might - // not have an assigned slot, so return early. - if (!slot) { - return false; - } - targetElement = slot->AsElement(); - } - - // namespace/tag match - // optimization : bail out early if we can - if ((kNameSpaceID_Unknown != aSelector->mNameSpace && - targetElement->GetNameSpaceID() != aSelector->mNameSpace)) - return false; - - if (aSelector->mLowercaseTag) { - nsIAtom* selectorTag = - (aTreeMatchContext.mIsHTMLDocument && targetElement->IsHTMLElement()) ? - aSelector->mLowercaseTag : aSelector->mCasedTag; - if (selectorTag != targetElement->NodeInfo()->NameAtom()) { - return false; - } - } - - nsAtomList* IDList = aSelector->mIDList; - if (IDList) { - nsIAtom* id = targetElement->GetID(); - if (id) { - // case sensitivity: bug 93371 - const bool isCaseSensitive = - aTreeMatchContext.mCompatMode != eCompatibility_NavQuirks; - - if (isCaseSensitive) { - do { - if (IDList->mAtom != id) { - return false; - } - IDList = IDList->mNext; - } while (IDList); - } else { - // Use EqualsIgnoreASCIICase instead of full on unicode case conversion - // in order to save on performance. This is only used in quirks mode - // anyway. - nsDependentAtomString id1Str(id); - do { - if (!nsContentUtils::EqualsIgnoreASCIICase(id1Str, - nsDependentAtomString(IDList->mAtom))) { - return false; - } - IDList = IDList->mNext; - } while (IDList); - } - } else { - // Element has no id but we have an id selector - return false; - } - } - - nsAtomList* classList = aSelector->mClassList; - if (classList) { - // test for class match - const nsAttrValue *elementClasses = targetElement->GetClasses(); - if (!elementClasses) { - // Element has no classes but we have a class selector - return false; - } - - // case sensitivity: bug 93371 - const bool isCaseSensitive = - aTreeMatchContext.mCompatMode != eCompatibility_NavQuirks; - - while (classList) { - if (!elementClasses->Contains(classList->mAtom, - isCaseSensitive ? - eCaseMatters : eIgnoreCase)) { - return false; - } - classList = classList->mNext; - } - } - - const bool isOutsideShadowTree = - !!(aSelectorFlags & SelectorMatchesFlags::IS_OUTSIDE_SHADOW_TREE); - const bool isNegated = (aDependence != nullptr); - // The selectors for which we set node bits are, unfortunately, early - // in this function (because they're pseudo-classes, which are - // generally quick to test, and thus earlier). If they were later, - // we'd probably avoid setting those bits in more cases where setting - // them is unnecessary. - NS_ASSERTION(aNodeMatchContext.mStateMask.IsEmpty() || - !aTreeMatchContext.mForStyling, - "mForStyling must be false if we're just testing for " - "state-dependence"); - - // test for pseudo class match - for (nsPseudoClassList* pseudoClass = aSelector->mPseudoClassList; - pseudoClass; pseudoClass = pseudoClass->mNext) { - auto idx = static_cast(pseudoClass->mType); - EventStates statesToCheck = sPseudoClassStates[idx]; - if (statesToCheck.IsEmpty()) { - // keep the cases here in the same order as the list in - // nsCSSPseudoClassList.h - switch (pseudoClass->mType) { - case CSSPseudoClassType::empty: - if (!checkGenericEmptyMatches(aElement, aTreeMatchContext, true)) { - return false; - } - break; - - case CSSPseudoClassType::mozOnlyWhitespace: - if (!checkGenericEmptyMatches(aElement, aTreeMatchContext, false)) { - return false; - } - break; - - case CSSPseudoClassType::mozEmptyExceptChildrenWithLocalname: - { - NS_ASSERTION(pseudoClass->u.mString, "Must have string!"); - nsIContent *child = nullptr; - int32_t index = -1; - - if (aTreeMatchContext.mForStyling) - // FIXME: This isn't sufficient to handle: - // :-moz-empty-except-children-with-localname() + E - // :-moz-empty-except-children-with-localname() ~ E - // because we don't know to restyle the grandparent of the - // inserted/removed element (as in bug 534804 for :empty). - aElement->SetFlags(NODE_HAS_SLOW_SELECTOR); - do { - child = aElement->GetChildAt(++index); - } while (child && - (!IsSignificantChild(child, true, false) || - (child->GetNameSpaceID() == aElement->GetNameSpaceID() && - child->NodeInfo()->NameAtom()->Equals(nsDependentString(pseudoClass->u.mString))))); - if (child != nullptr) { - return false; - } - } - break; - - case CSSPseudoClassType::lang: - { - NS_ASSERTION(nullptr != pseudoClass->u.mString, "null lang parameter"); - if (!pseudoClass->u.mString || !*pseudoClass->u.mString) { - return false; - } - - // We have to determine the language of the current element. Since - // this is currently no property and since the language is inherited - // from the parent we have to be prepared to look at all parent - // nodes. The language itself is encoded in the LANG attribute. - nsAutoString language; - if (aElement->GetLang(language)) { - if (!nsStyleUtil::DashMatchCompare(language, - nsDependentString(pseudoClass->u.mString), - nsASCIICaseInsensitiveStringComparator())) { - return false; - } - // This pseudo-class matched; move on to the next thing - break; - } - - nsIDocument* doc = aTreeMatchContext.mDocument; - if (doc) { - // Try to get the language from the HTTP header or if this - // is missing as well from the preferences. - // The content language can be a comma-separated list of - // language codes. - doc->GetContentLanguage(language); - - nsDependentString langString(pseudoClass->u.mString); - language.StripWhitespace(); - int32_t begin = 0; - int32_t len = language.Length(); - while (begin < len) { - int32_t end = language.FindChar(char16_t(','), begin); - if (end == kNotFound) { - end = len; - } - if (nsStyleUtil::DashMatchCompare(Substring(language, begin, - end-begin), - langString, - nsASCIICaseInsensitiveStringComparator())) { - break; - } - begin = end + 1; - } - if (begin < len) { - // This pseudo-class matched - break; - } - } - } - return false; - - case CSSPseudoClassType::mozBoundElement: - if (aTreeMatchContext.mScopedRoot != aElement) { - return false; - } - break; - - case CSSPseudoClassType::root: - if (aElement != aElement->OwnerDoc()->GetRootElement()) { - return false; - } - break; - - case CSSPseudoClassType::is: - case CSSPseudoClassType::matches: - case CSSPseudoClassType::any: - case CSSPseudoClassType::where: - { - if (!SelectorListMatches(aElement, - pseudoClass, - aNodeMatchContext, - aTreeMatchContext, - true)) { - return false; - } - } - break; - - case CSSPseudoClassType::mozAny: - { - // XXX: For compatibility, we retain :-moz-any()'s original behavior, - // which is to be unforgiving and reject complex selectors in - // its selector list argument. - if (!SelectorListMatches(aElement, - pseudoClass, - aNodeMatchContext, - aTreeMatchContext, - false, - true)) { - return false; - } - } - break; - - case CSSPseudoClassType::mozAnyPrivate: - { - if (!SelectorListMatches(aElement, - pseudoClass, - aNodeMatchContext, - aTreeMatchContext)) { - return false; - } - } - break; - - case CSSPseudoClassType::slotted: - { - if (aTreeMatchContext.mForAssignedSlot) { - aTreeMatchContext.mForAssignedSlot = false; - } - - // Slottables cannot be matched from the outer tree. - if (isOutsideShadowTree) { - return false; - } - - // Slot elements cannot be matched. - if (aElement->IsHTMLElement(nsGkAtoms::slot)) { - return false; - } - - // The current element must have an assigned slot. - if (!aElement->GetAssignedSlot()) { - return false; - } - - if (!SelectorListMatches(aElement, - pseudoClass, - aNodeMatchContext, - aTreeMatchContext)) { - return false; - } - } - break; - - case CSSPseudoClassType::host: - { - ShadowRoot* shadow = aElement->GetShadowRoot(); - // In order to match :host, the element must be a shadow root host, - // we must be matching only against host pseudo selectors, and the - // selector's context must be the shadow root (the selector must be - // featureless, the left-most selector, and be in a shadow root - // style). - if (!shadow || - aSelector->HasFeatureSelectors() || - isOutsideShadowTree) { - return false; - } - - // We're matching :host from inside the shadow root. - if (!aTreeMatchContext.mOnlyMatchHostPseudo) { - // Check if the element has the same shadow root. - if (aTreeMatchContext.mScopedRoot) { - if (shadow != - aTreeMatchContext.mScopedRoot->GetShadowRoot()) { - return false; - } - } - // We were called elsewhere. - } - - // Reject if the next selector is an explicit universal selector. - if (aSelector->mNext && aSelector->mNext->mExplicitUniversal) { - return false; - } - - // The :host selector may also be be functional, with a compound - // selector. If this is the case, then also ensure that the host - // element matches against the compound selector. - if (!pseudoClass->u.mSelectorList) { - break; - } - - // Match if any selector in the argument list matches. - // FIXME: What this effectively does is bypass the "featureless" - // selector check under SelectorMatches. - NodeMatchContext nodeContext(aNodeMatchContext.mStateMask, - aNodeMatchContext.mIsRelevantLink); - if (!SelectorListMatches(aElement, - pseudoClass, - nodeContext, - aTreeMatchContext)) { - return false; - } - } - break; - - case CSSPseudoClassType::hostContext: - { - // In order to match host-context, the element must be a - // shadow root host and the selector's context must be the - // shadow root (aTreeMatchContext.mScopedRoot is set to the - // host of the shadow root where the style is contained, - // thus the element must be mScopedRoot). If the UNKNOWN - // selector flag is set, relax the shadow root host - // requirement because this pseudo class walks through - // ancestors looking for a match, thus the selector can be - // dependant on aElement even though it is not the host. The - // dependency would otherwise be missed because when UNKNOWN - // is set, selector matching may not have started from the top. - if (!((aElement->GetShadowRoot() && - aElement == aTreeMatchContext.mScopedRoot) || - aSelectorFlags & SelectorMatchesFlags::UNKNOWN)) { - return false; - } - - Element* currentElement = aElement; - while (currentElement) { - NodeMatchContext nodeContext(EventStates(), - nsCSSRuleProcessor::IsLink(currentElement)); - if (SelectorListMatches(currentElement, - pseudoClass, - nodeContext, - aTreeMatchContext)) { - break; - } - - nsIContent* flattenedParent = currentElement->GetFlattenedTreeParent(); - currentElement = flattenedParent && flattenedParent->IsElement() ? - flattenedParent->AsElement() : nullptr; - } - if (!currentElement) { - return false; - } - } - break; - - case CSSPseudoClassType::firstChild: - if (!edgeChildMatches(aElement, aTreeMatchContext, true, false)) { - return false; - } - break; - - case CSSPseudoClassType::firstNode: - { - nsIContent *firstNode = nullptr; - nsIContent *parent = aElement->GetParent(); - if (parent) { - if (aTreeMatchContext.mForStyling) - parent->SetFlags(NODE_HAS_EDGE_CHILD_SELECTOR); - - int32_t index = -1; - do { - firstNode = parent->GetChildAt(++index); - // stop at first non-comment and non-whitespace node - } while (firstNode && - !IsSignificantChild(firstNode, true, false)); - } - if (aElement != firstNode) { - return false; - } - } - break; - - case CSSPseudoClassType::lastChild: - if (!edgeChildMatches(aElement, aTreeMatchContext, false, true)) { - return false; - } - break; - - case CSSPseudoClassType::lastNode: - { - nsIContent *lastNode = nullptr; - nsIContent *parent = aElement->GetParent(); - if (parent) { - if (aTreeMatchContext.mForStyling) - parent->SetFlags(NODE_HAS_EDGE_CHILD_SELECTOR); - - uint32_t index = parent->GetChildCount(); - do { - lastNode = parent->GetChildAt(--index); - // stop at first non-comment and non-whitespace node - } while (lastNode && - !IsSignificantChild(lastNode, true, false)); - } - if (aElement != lastNode) { - return false; - } - } - break; - - case CSSPseudoClassType::onlyChild: - if (!edgeChildMatches(aElement, aTreeMatchContext, true, true)) { - return false; - } - break; - - case CSSPseudoClassType::firstOfType: - if (!edgeOfTypeMatches(aElement, aTreeMatchContext, true, false)) { - return false; - } - break; - - case CSSPseudoClassType::lastOfType: - if (!edgeOfTypeMatches(aElement, aTreeMatchContext, false, true)) { - return false; - } - break; - - case CSSPseudoClassType::onlyOfType: - if (!edgeOfTypeMatches(aElement, aTreeMatchContext, true, true)) { - return false; - } - break; - - case CSSPseudoClassType::nthChild: - if (!nthChildGenericMatches(aElement, aTreeMatchContext, pseudoClass, - false, false)) { - return false; - } - break; - - case CSSPseudoClassType::nthLastChild: - if (!nthChildGenericMatches(aElement, aTreeMatchContext, pseudoClass, - false, true)) { - return false; - } - break; - - case CSSPseudoClassType::nthOfType: - if (!nthChildGenericMatches(aElement, aTreeMatchContext, pseudoClass, - true, false)) { - return false; - } - break; - - case CSSPseudoClassType::nthLastOfType: - if (!nthChildGenericMatches(aElement, aTreeMatchContext, pseudoClass, - true, true)) { - return false; - } - break; - - case CSSPseudoClassType::mozIsHTML: - if (!aTreeMatchContext.mIsHTMLDocument || !aElement->IsHTMLElement()) { - return false; - } - break; - - case CSSPseudoClassType::mozNativeAnonymous: - if (!aElement->IsInNativeAnonymousSubtree()) { - return false; - } - break; - - case CSSPseudoClassType::mozSystemMetric: - { - nsCOMPtr metric = NS_Atomize(pseudoClass->u.mString); - if (!nsCSSRuleProcessor::HasSystemMetric(metric)) { - return false; - } - } - break; - - case CSSPseudoClassType::mozLocaleDir: - { - bool docIsRTL = - aTreeMatchContext.mDocument->GetDocumentState(). - HasState(NS_DOCUMENT_STATE_RTL_LOCALE); - - nsDependentString dirString(pseudoClass->u.mString); - - if (dirString.EqualsLiteral("rtl")) { - if (!docIsRTL) { - return false; - } - } else if (dirString.EqualsLiteral("ltr")) { - if (docIsRTL) { - return false; - } - } else { - // Selectors specifying other directions never match. - return false; - } - } - break; - - case CSSPseudoClassType::mozLWTheme: - { - if (aTreeMatchContext.mDocument->GetDocumentLWTheme() <= - nsIDocument::Doc_Theme_None) { - return false; - } - } - break; - - case CSSPseudoClassType::mozLWThemeBrightText: - { - if (aTreeMatchContext.mDocument->GetDocumentLWTheme() != - nsIDocument::Doc_Theme_Bright) { - return false; - } - } - break; - - case CSSPseudoClassType::mozLWThemeDarkText: - { - if (aTreeMatchContext.mDocument->GetDocumentLWTheme() != - nsIDocument::Doc_Theme_Dark) { - return false; - } - } - break; - - case CSSPseudoClassType::mozWindowInactive: - if (!aTreeMatchContext.mDocument->GetDocumentState(). - HasState(NS_DOCUMENT_STATE_WINDOW_INACTIVE)) { - return false; - } - break; - - case CSSPseudoClassType::mozTableBorderNonzero: - { - if (!aElement->IsHTMLElement(nsGkAtoms::table)) { - return false; - } - const nsAttrValue *val = aElement->GetParsedAttr(nsGkAtoms::border); - if (!val || - (val->Type() == nsAttrValue::eInteger && - val->GetIntegerValue() == 0)) { - return false; - } - } - break; - - case CSSPseudoClassType::mozBrowserFrame: - { - nsCOMPtr - browserFrame = do_QueryInterface(aElement); - if (!browserFrame || - !browserFrame->GetReallyIsBrowser()) { - return false; - } - } - break; - - case CSSPseudoClassType::mozDir: - case CSSPseudoClassType::dir: - { - if (aDependence) { - EventStates states = sPseudoClassStateDependences[ - static_cast(pseudoClass->mType)]; - if (aNodeMatchContext.mStateMask.HasAtLeastOneOfStates(states)) { - *aDependence = true; - return false; - } - } - - // If we only had to consider HTML, directionality would be - // exclusively LTR or RTL. - // - // However, in markup languages where there is no direction attribute - // we have to consider the possibility that neither dir(rtl) nor - // dir(ltr) matches. - EventStates state = aElement->StyleState(); - nsDependentString dirString(pseudoClass->u.mString); - - if (dirString.EqualsLiteral("rtl")) { - if (!state.HasState(NS_EVENT_STATE_RTL)) { - return false; - } - } else if (dirString.EqualsLiteral("ltr")) { - if (!state.HasState(NS_EVENT_STATE_LTR)) { - return false; - } - } else { - // Selectors specifying other directions never match. - return false; - } - } - break; - - case CSSPseudoClassType::scope: - if (aTreeMatchContext.mForScopedStyle) { - if (aTreeMatchContext.mCurrentStyleScope) { - // If mCurrentStyleScope is null, aElement must be the style - // scope root. This is because the PopStyleScopeForSelectorMatching - // call in SelectorMatchesTree sets mCurrentStyleScope to null - // as soon as we visit the style scope element, and we won't - // progress further up the tree after this call to - // SelectorMatches. Thus if mCurrentStyleScope is still set, - // we know the selector does not match. - return false; - } - } else if (aTreeMatchContext.HasSpecifiedScope()) { - if (!aTreeMatchContext.IsScopeElement(aElement)) { - return false; - } - } else { - if (aElement != aElement->OwnerDoc()->GetRootElement()) { - return false; - } - } - break; - - default: - MOZ_ASSERT(false, "How did that happen?"); - } - } else { - if (!StateSelectorMatches(aElement, aSelector, aNodeMatchContext, - aTreeMatchContext, aSelectorFlags, aDependence, - statesToCheck)) { - return false; - } - } - } - - bool result = true; - if (aSelector->mAttrList) { - // test for attribute match - if (!targetElement->HasAttrs()) { - // if no attributes on the content, no match - return false; - } else { - result = true; - nsAttrSelector* attr = aSelector->mAttrList; - nsIAtom* matchAttribute; - - do { - bool isHTML = - (aTreeMatchContext.mIsHTMLDocument && targetElement->IsHTMLElement()); - matchAttribute = isHTML ? attr->mLowercaseAttr : attr->mCasedAttr; - if (attr->mNameSpace == kNameSpaceID_Unknown) { - // Attr selector with a wildcard namespace. We have to examine all - // the attributes on our content node.... This sort of selector is - // essentially a boolean OR, over all namespaces, of equivalent attr - // selectors with those namespaces. So to evaluate whether it - // matches, evaluate for each namespace (the only namespaces that - // have a chance at matching, of course, are ones that the element - // actually has attributes in), short-circuiting if we ever match. - result = false; - const nsAttrName* attrName; - for (uint32_t i = 0; (attrName = targetElement->GetAttrNameAt(i)); ++i) { - if (attrName->LocalName() != matchAttribute) { - continue; - } - if (attr->mFunction == NS_ATTR_FUNC_SET) { - result = true; - } else { - nsAutoString value; -#ifdef DEBUG - bool hasAttr = -#endif - targetElement->GetAttr(attrName->NamespaceID(), - attrName->LocalName(), value); - NS_ASSERTION(hasAttr, "GetAttrNameAt lied"); - result = AttrMatchesValue(attr, value, isHTML); - } - - // At this point |result| has been set by us - // explicitly in this loop. If it's false, we may still match - // -- the content may have another attribute with the same name but - // in a different namespace. But if it's true, we are done (we - // can short-circuit the boolean OR described above). - if (result) { - break; - } - } - } - else if (attr->mFunction == NS_ATTR_FUNC_EQUALS) { - result = - targetElement-> - AttrValueIs(attr->mNameSpace, matchAttribute, attr->mValue, - attr->IsValueCaseSensitive(isHTML) ? eCaseMatters - : eIgnoreCase); - } - else if (!targetElement->HasAttr(attr->mNameSpace, matchAttribute)) { - result = false; - } - else if (attr->mFunction != NS_ATTR_FUNC_SET) { - nsAutoString value; -#ifdef DEBUG - bool hasAttr = -#endif - targetElement->GetAttr(attr->mNameSpace, matchAttribute, value); - NS_ASSERTION(hasAttr, "HasAttr lied"); - result = AttrMatchesValue(attr, value, isHTML); - } - - attr = attr->mNext; - } while (attr && result); - } - } - - // apply SelectorMatches to the negated selectors in the chain - if (!isNegated) { - for (nsCSSSelector *negation = aSelector->mNegations; - result && negation; negation = negation->mNegations) { - bool dependence = false; - result = !SelectorMatches(targetElement, negation, aNodeMatchContext, - aTreeMatchContext, - SelectorMatchesFlags::IS_PSEUDO_CLASS_ARGUMENT, - &dependence); - // If the selector does match due to the dependence on - // aNodeMatchContext.mStateMask, then we want to keep result true - // so that the final result of SelectorMatches is true. Doing so - // tells StateEnumFunc that there is a dependence on the state. - result = result || dependence; - } - } - return result; -} - -#undef STATE_CHECK - -#ifdef DEBUG -static bool -HasPseudoClassSelectorArgsWithCombinators(nsCSSSelector* aSelector) -{ - for (nsPseudoClassList* p = aSelector->mPseudoClassList; p; p = p->mNext) { - if (nsCSSPseudoClasses::HasSelectorListArg(p->mType)) { - for (nsCSSSelectorList* l = p->u.mSelectorList; l; l = l->mNext) { - if (l->mSelectors->mNext) { - return true; - } - } - } - } - for (nsCSSSelector* n = aSelector->mNegations; n; n = n->mNegations) { - if (n->mNext) { - return true; - } - } - return false; -} -#endif - -/* static */ bool -nsCSSRuleProcessor::RestrictedSelectorMatches( - Element* aElement, - nsCSSSelector* aSelector, - TreeMatchContext& aTreeMatchContext) -{ - MOZ_ASSERT(aSelector->IsRestrictedSelector(), - "aSelector must not have a pseudo-element"); - - NS_WARNING_ASSERTION( - !HasPseudoClassSelectorArgsWithCombinators(aSelector), - "processing eRestyle_SomeDescendants can be slow if pseudo-classes with " - "selector arguments can now have combinators in them"); - - // We match aSelector as if :visited and :link both match visited and - // unvisited links. - - NodeMatchContext nodeContext(EventStates(), - nsCSSRuleProcessor::IsLink(aElement)); - if (nodeContext.mIsRelevantLink) { - aTreeMatchContext.SetHaveRelevantLink(); - } - aTreeMatchContext.ResetForUnvisitedMatching(); - bool matches = SelectorMatches(aElement, aSelector, nodeContext, - aTreeMatchContext, SelectorMatchesFlags::NONE); - if (nodeContext.mIsRelevantLink) { - aTreeMatchContext.ResetForVisitedMatching(); - if (SelectorMatches(aElement, aSelector, nodeContext, aTreeMatchContext, - SelectorMatchesFlags::NONE)) { - matches = true; - } - } - return matches; -} - -// Right now, there are four operators: -// ' ', the descendant combinator, is greedy -// '~', the indirect adjacent sibling combinator, is greedy -// '+' and '>', the direct adjacent sibling and child combinators, are not -#define NS_IS_GREEDY_OPERATOR(ch) \ - ((ch) == char16_t(' ') || (ch) == char16_t('~')) - -/** - * Flags for SelectorMatchesTree. - */ -enum SelectorMatchesTreeFlags { - // Whether we still have not found the closest ancestor link element and - // thus have to check the current element for it. - eLookForRelevantLink = 0x1, - - // Whether SelectorMatchesTree should check for, and return true upon - // finding, an ancestor element that has an eRestyle_SomeDescendants - // restyle hint pending. - eMatchOnConditionalRestyleAncestor = 0x2, -}; - -static bool -SelectorMatchesTree(Element* aPrevElement, - nsCSSSelector* aSelector, - TreeMatchContext& aTreeMatchContext, - SelectorMatchesTreeFlags aFlags) -{ - MOZ_ASSERT(!aSelector || !aSelector->IsPseudoElement()); - nsCSSSelector* selector = aSelector; - Element* prevElement = aPrevElement; - bool crossedShadowRootBoundary = false; - while (selector) { // check compound selectors - bool contentIsFeatureless = false; - NS_ASSERTION(!selector->mNext || - selector->mNext->mOperator != char16_t(0), - "compound selector without combinator"); - - // If after the previous selector match we are now outside the - // current style scope, we don't need to match any further. - if (aTreeMatchContext.mForScopedStyle && - !aTreeMatchContext.IsWithinStyleScopeForSelectorMatching()) { - return false; - } - - // for adjacent sibling combinators, the content to test against the - // selector is the previous sibling *element* - Element* element = nullptr; - if (char16_t('+') == selector->mOperator || - char16_t('~') == selector->mOperator) { - // The relevant link must be an ancestor of the node being matched. - aFlags = SelectorMatchesTreeFlags(aFlags & ~eLookForRelevantLink); - nsIContent* parent = prevElement->GetParent(); - // Operate on the flattened element tree when matching the - // ::slotted() pseudo-element. - if (aTreeMatchContext.mRestrictToSlottedPseudo) { - parent = prevElement->GetFlattenedTreeParent(); - } - if (parent) { - if (aTreeMatchContext.mForStyling) - parent->SetFlags(NODE_HAS_SLOW_SELECTOR_LATER_SIBLINGS); - - element = prevElement->GetPreviousElementSibling(); - } - } - // for descendant combinators and child combinators, the element - // to test against is the parent - else { - nsIContent* content = prevElement->GetParent(); - // Operate on the flattened element tree when matching the - // ::slotted() pseudo-element. - if (aTreeMatchContext.mRestrictToSlottedPseudo) { - content = prevElement->GetFlattenedTreeParent(); - } - - // In the shadow tree, the shadow host behaves as if it - // is a featureless parent of top-level elements of the shadow - // tree. Only cross shadow root boundary when the selector is the - // left most selector because ancestors of the host are not in - // the selector match list. - ShadowRoot* shadowRoot = content ? - ShadowRoot::FromNode(content) : - nullptr; - if (shadowRoot && !selector->mNext && !crossedShadowRootBoundary) { - content = shadowRoot->GetHost(); - crossedShadowRootBoundary = true; - contentIsFeatureless = true; - } - - // GetParent could return a document fragment; we only want - // element parents. - if (content && content->IsElement()) { - element = content->AsElement(); - if (aTreeMatchContext.mForScopedStyle) { - // We are moving up to the parent element; tell the - // TreeMatchContext, so that in case this element is the - // style scope element, selector matching stops before we - // traverse further up the tree. - aTreeMatchContext.PopStyleScopeForSelectorMatching(element); - } - - // Compatibility hack: First try matching this selector as though the - // element wasn't in the tree to allow old selectors - // were written before participated in CSS selector - // matching to work. - if (selector->mOperator == '>' && element->IsActiveChildrenElement()) { - Element* styleScope = aTreeMatchContext.mCurrentStyleScope; - if (SelectorMatchesTree(element, selector, aTreeMatchContext, - aFlags)) { - // It matched, don't try matching on the element at - // all. - return true; - } - // We want to reset mCurrentStyleScope on aTreeMatchContext - // back to its state before the SelectorMatchesTree call, in - // case that call happens to traverse past the style scope element - // and sets it to null. - aTreeMatchContext.mCurrentStyleScope = styleScope; - } - } - } - if (!element) { - return false; - } - if ((aFlags & eMatchOnConditionalRestyleAncestor) && - element->HasFlag(ELEMENT_IS_CONDITIONAL_RESTYLE_ANCESTOR)) { - // If we're looking at an element that we already generated an - // eRestyle_SomeDescendants restyle hint for, then we should pretend - // that we matched here, because we don't know what the values of - // attributes on |element| were at the time we generated the - // eRestyle_SomeDescendants. This causes AttributeEnumFunc and - // HasStateDependentStyle below to generate a restyle hint for the - // change we're currently looking at, as we don't know whether the LHS - // of the selector we looked up matches or not. (We only pass in aFlags - // to cause us to look for eRestyle_SomeDescendants here under - // AttributeEnumFunc and HasStateDependentStyle.) - return true; - } - const bool isRelevantLink = (aFlags & eLookForRelevantLink) && - nsCSSRuleProcessor::IsLink(element); - - NodeMatchContext nodeContext(EventStates(), isRelevantLink, contentIsFeatureless); - if (isRelevantLink) { - // If we find an ancestor of the matched node that is a link - // during the matching process, then it's the relevant link (see - // constructor call above). - // Since we are still matching against selectors that contain - // :visited (they'll just fail), we will always find such a node - // during the selector matching process if there is a relevant - // link that can influence selector matching. - aFlags = SelectorMatchesTreeFlags(aFlags & ~eLookForRelevantLink); - aTreeMatchContext.SetHaveRelevantLink(); - } - if (SelectorMatches(element, selector, nodeContext, aTreeMatchContext, - SelectorMatchesFlags::NONE)) { - // to avoid greedy matching, we need to recur if this is a - // descendant or general sibling combinator and the next - // combinator is different, but we can make an exception for - // sibling, then parent, since a sibling's parent is always the - // same. - if (NS_IS_GREEDY_OPERATOR(selector->mOperator) && - selector->mNext && - selector->mNext->mOperator != selector->mOperator && - !(selector->mOperator == '~' && - NS_IS_ANCESTOR_OPERATOR(selector->mNext->mOperator))) { - - // pretend the selector didn't match, and step through content - // while testing the same selector - - // This approach is slightly strange in that when it recurs - // it tests from the top of the content tree, down. This - // doesn't matter much for performance since most selectors - // don't match. (If most did, it might be faster...) - Element* styleScope = aTreeMatchContext.mCurrentStyleScope; - if (SelectorMatchesTree(element, selector, aTreeMatchContext, aFlags)) { - return true; - } - // We want to reset mCurrentStyleScope on aTreeMatchContext - // back to its state before the SelectorMatchesTree call, in - // case that call happens to traverse past the style scope element - // and sets it to null. - aTreeMatchContext.mCurrentStyleScope = styleScope; - } - selector = selector->mNext; - if (!selector && - !aTreeMatchContext.mIsTopmostScope && - aTreeMatchContext.mRestrictToSlottedPseudo && - aTreeMatchContext.mScopedRoot != element) { - return false; - } - } - else { - // for adjacent sibling and child combinators, if we didn't find - // a match, we're done - if (!NS_IS_GREEDY_OPERATOR(selector->mOperator)) { - return false; // parent was required to match - } - } - prevElement = element; - } - return true; // all the selectors matched. -} - -static bool SelectorListMatches(Element* aElement, - nsCSSSelectorList* aList, - NodeMatchContext& aNodeMatchContext, - TreeMatchContext& aTreeMatchContext, - SelectorMatchesFlags aSelectorFlags, - bool aIsForgiving, - bool aPreventComplexSelectors) -{ - while (aList) { - nsCSSSelector *selector = aList->mSelectors; - // Forgiving selector lists are allowed to be empty, but they - // don't match anything. - if (!selector && aIsForgiving) { - return false; - } - NS_ASSERTION(selector, "Should have *some* selectors"); - NS_ASSERTION(!selector->IsPseudoElement(), "Shouldn't have been called"); - if (aPreventComplexSelectors) { - NS_ASSERTION(!selector->mNext, "Shouldn't have complex selectors"); - } - if (SelectorMatches(aElement, - selector, - aNodeMatchContext, - aTreeMatchContext, - aSelectorFlags)) { - nsCSSSelector* next = selector->mNext; - SelectorMatchesTreeFlags selectorTreeFlags = SelectorMatchesTreeFlags(0); - // Try to look for the closest ancestor link element if we're processing - // the selector list argument of a pseudo-class, but only if for a new style context (see SelectorMatches). - if (!aNodeMatchContext.mIsRelevantLink && - aTreeMatchContext.mForStyling && - (aSelectorFlags & SelectorMatchesFlags::IS_PSEUDO_CLASS_ARGUMENT)) { - selectorTreeFlags = eLookForRelevantLink; - } - - if (!next || - SelectorMatchesTree(aElement, - next, - aTreeMatchContext, - selectorTreeFlags)) { - return true; - } - } - - aList = aList->mNext; - } - - return false; -} - -static bool SelectorListMatches(Element* aElement, - nsPseudoClassList* aList, - NodeMatchContext& aNodeMatchContext, - TreeMatchContext& aTreeMatchContext, - bool aIsForgiving, - bool aPreventComplexSelectors) -{ - return SelectorListMatches(aElement, - aList->u.mSelectorList, - aNodeMatchContext, - aTreeMatchContext, - SelectorMatchesFlags::IS_PSEUDO_CLASS_ARGUMENT, - aIsForgiving, - aPreventComplexSelectors); -} - -static -inline bool LookForTargetPseudo(nsCSSSelector* aSelector, - TreeMatchContext* aMatchContext, - nsRestyleHint* possibleChange) { - if (aMatchContext->mOnlyMatchHostPseudo) { - while (aSelector && aSelector->mNext != nullptr) { - aSelector = aSelector->mNext; - } - - for (nsPseudoClassList* pseudoClass = aSelector->mPseudoClassList; - pseudoClass; - pseudoClass = pseudoClass->mNext) { - if (pseudoClass->mType == CSSPseudoClassType::host || - pseudoClass->mType == CSSPseudoClassType::hostContext) { - if (possibleChange) { - // :host-context will walk ancestors looking for a match of a - // compound selector, thus any changes to ancestors may require - // restyling the subtree. - *possibleChange |= eRestyle_Subtree; - } - return true; - } - } - return false; - } - else if (aMatchContext->mRestrictToSlottedPseudo) { - for (nsCSSSelector* selector = aSelector; - selector; - selector = selector->mNext) { - if (!selector->mPseudoClassList) { - continue; - } - for (nsPseudoClassList* pseudoClass = selector->mPseudoClassList; - pseudoClass; - pseudoClass = pseudoClass->mNext) { - if (pseudoClass->mType == CSSPseudoClassType::slotted) { - return true; - } - } - } - return false; - } - // We're not restricted to a specific pseudo-class. - return true; -} - -static inline -void ContentEnumFunc(const RuleValue& value, nsCSSSelector* aSelector, - ElementDependentRuleProcessorData* data, NodeMatchContext& nodeContext, - AncestorFilter *ancestorFilter) -{ - if (nodeContext.mIsRelevantLink) { - data->mTreeMatchContext.SetHaveRelevantLink(); - } - // XXX: Ignore the ancestor filter if we're testing the assigned slot. - bool useAncestorFilter = !(data->mTreeMatchContext.mForAssignedSlot); - if (useAncestorFilter && ancestorFilter && - !ancestorFilter->MightHaveMatchingAncestor( - value.mAncestorSelectorHashes)) { - // We won't match; nothing else to do here - return; - } - - if (!LookForTargetPseudo(aSelector, &data->mTreeMatchContext, nullptr)) { - return; - } - - if (!data->mTreeMatchContext.SetStyleScopeForSelectorMatching(data->mElement, - data->mScope)) { - // The selector is for a rule in a scoped style sheet, and the subject - // of the selector matching is not in its scope. - return; - } - nsCSSSelector* selector = aSelector; - if (selector->IsPseudoElement()) { - PseudoElementRuleProcessorData* pdata = - static_cast(data); - if (!pdata->mPseudoElement && selector->mPseudoClassList) { - // We can get here when calling getComputedStyle(aElt, aPseudo) if: - // - // * aPseudo is a pseudo-element that supports a user action - // pseudo-class, like "::placeholder"; - // * there is a style rule that uses a pseudo-class on this - // pseudo-element in the document, like ::placeholder:hover; and - // * aElt does not have such a pseudo-element. - // - // We know that the selector can't match, since there is no element for - // the user action pseudo-class to match against. - return; - } - if (!StateSelectorMatches(pdata->mPseudoElement, aSelector, nodeContext, - data->mTreeMatchContext, - SelectorMatchesFlags::NONE)) { - return; - } - selector = selector->mNext; - } - - SelectorMatchesFlags selectorFlags = SelectorMatchesFlags::NONE; - if (aSelector->IsPseudoElement()) { - selectorFlags |= SelectorMatchesFlags::HAS_PSEUDO_ELEMENT; - } - if (SelectorMatches(data->mElement, selector, nodeContext, - data->mTreeMatchContext, selectorFlags)) { - nsCSSSelector *next = selector->mNext; - if (!next || - SelectorMatchesTree(data->mElement, next, - data->mTreeMatchContext, - nodeContext.mIsRelevantLink ? - SelectorMatchesTreeFlags(0) : - eLookForRelevantLink)) { - css::Declaration* declaration = value.mRule->GetDeclaration(); - declaration->SetImmutable(); - data->mRuleWalker->Forward(declaration); - // nsStyleSet will deal with the !important rule - } - } -} - /* virtual */ void nsCSSRuleProcessor::RulesMatching(ElementRuleProcessorData *aData) { @@ -2968,16 +152,7 @@ nsCSSRuleProcessor::RulesMatching(ElementRuleProcessorData *aData) return; } for (RuleCascadeData* cascade : resolvedCascade->mOrderedData) { - NodeMatchContext nodeContext(EventStates(), - nsCSSRuleProcessor::IsLink(aData->mElement), - aData->mElementIsFeatureless); - // Test against the assigned slot rather than the slottable if we're - // matching the ::slotted() pseudo. - Element* targetElement = aData->mElement; - if (aData->mTreeMatchContext.mForAssignedSlot) { - targetElement = aData->mElement->GetAssignedSlot()->AsElement(); - } - cascade->mRuleHash.EnumerateAllRules(targetElement, aData, nodeContext); + cascade->RulesMatching(aData); } } @@ -2990,14 +165,7 @@ nsCSSRuleProcessor::RulesMatching(PseudoElementRuleProcessorData* aData) return; } for (RuleCascadeData* cascade : resolvedCascade->mOrderedData) { - RuleHash* ruleHash = - cascade->mPseudoElementRuleHashes[static_cast( - aData->mPseudoType)]; - if (ruleHash) { - NodeMatchContext nodeContext( - EventStates(), nsCSSRuleProcessor::IsLink(aData->mElement)); - ruleHash->EnumerateAllRules(aData->mElement, aData, nodeContext); - } + cascade->RulesMatching(aData); } } @@ -3010,22 +178,7 @@ nsCSSRuleProcessor::RulesMatching(AnonBoxRuleProcessorData* aData) return; } for (RuleCascadeData* cascade : resolvedCascade->mOrderedData) { - if (cascade->mAnonBoxRules.EntryCount() == 0) { - continue; - } - auto entry = static_cast( - cascade->mAnonBoxRules.Search(aData->mPseudoTag)); - if (entry) { - nsTArray& rules = entry->mRules; - for (RuleValue *value = rules.Elements(), - *end = value + rules.Length(); - value != end; - ++value) { - css::Declaration* declaration = value->mRule->GetDeclaration(); - declaration->SetImmutable(); - aData->mRuleWalker->Forward(declaration); - } - } + cascade->RulesMatching(aData); } } @@ -3039,41 +192,11 @@ nsCSSRuleProcessor::RulesMatching(XULTreeRuleProcessorData* aData) return; } for (RuleCascadeData* cascade : resolvedCascade->mOrderedData) { - if (cascade->mXULTreeRules.EntryCount() == 0) { - continue; - } - auto entry = static_cast( - cascade->mXULTreeRules.Search(aData->mPseudoTag)); - if (entry) { - NodeMatchContext nodeContext( - EventStates(), nsCSSRuleProcessor::IsLink(aData->mElement)); - nsTArray& rules = entry->mRules; - for (RuleValue *value = rules.Elements(), *end = value + rules.Length(); - value != end; - ++value) { - if (aData->mComparator->PseudoMatches(value->mSelector)) { - ContentEnumFunc( - *value, value->mSelector->mNext, aData, nodeContext, nullptr); - } - } - } + cascade->RulesMatching(aData); } } #endif -static inline nsRestyleHint RestyleHintForOp(char16_t oper) -{ - if (oper == char16_t('+') || oper == char16_t('~')) { - return eRestyle_LaterSiblings; - } - - if (oper != char16_t(0)) { - return eRestyle_Subtree; - } - - return eRestyle_Self; -} - nsRestyleHint nsCSSRuleProcessor::HasStateDependentStyle(ElementDependentRuleProcessorData* aData, Element* aStatefulElement, @@ -3084,89 +207,13 @@ nsCSSRuleProcessor::HasStateDependentStyle(ElementDependentRuleProcessorData* aD "mCurrentStyleScope will need to be saved and restored after the " "SelectorMatchesTree call"); - bool isPseudoElement = - aPseudoType != CSSPseudoElementType::NotPseudo; - ResolvedRuleCascades* resolvedCascade = GetRuleCascade(aData->mPresContext); - // Look up the content node in the state rule list, which points to - // any (CSS2 definition) simple selector (whether or not it is the - // subject) that has a state pseudo-class on it. This means that this - // code will be matching selectors that aren't real selectors in any - // stylesheet (e.g., if there is a selector "body > p:hover > a", then - // "body > p:hover" will be in |cascade->mStateSelectors|). Note that - // |ComputeSelectorStateDependence| below determines which selectors are in - // |cascade->mStateSelectors|. nsRestyleHint hint = nsRestyleHint(0); if (resolvedCascade) { for (RuleCascadeData* cascade : resolvedCascade->mOrderedData) { - StateSelector *iter = cascade->mStateSelectors.Elements(), - *end = iter + cascade->mStateSelectors.Length(); - NodeMatchContext nodeContext(aStateMask, false); - for (; iter != end; ++iter) { - nsCSSSelector* selector = iter->mSelector; - EventStates states = iter->mStates; - - if (selector->IsPseudoElement() != isPseudoElement) { - continue; - } - - nsCSSSelector* selectorForPseudo; - if (isPseudoElement) { - if (selector->PseudoType() != aPseudoType) { - continue; - } - selectorForPseudo = selector; - selector = selector->mNext; - } - - nsRestyleHint possibleChange = RestyleHintForOp(selector->mOperator); - SelectorMatchesFlags selectorFlags = SelectorMatchesFlags::UNKNOWN; - - // If hint already includes all the bits of possibleChange, - // don't bother calling SelectorMatches, since even if it returns false - // hint won't change. - // Also don't bother calling SelectorMatches if none of the - // states passed in are relevant here. - if ( - (possibleChange & ~hint) && - states.HasAtLeastOneOfStates(aStateMask) && - // We can optimize away testing selectors that only involve :hover, a - // namespace, and a tag name against nodes that don't have the - // NodeHasRelevantHoverRules flag: such a selector didn't match - // the tag name or namespace the first time around (since the :hover - // didn't set the NodeHasRelevantHoverRules flag), so it won't - // match it now. Check for our selector only having :hover states, or - // the element having the hover rules flag, or the selector having - // some sort of non-namespace, non-tagname data in it. - (states != NS_EVENT_STATE_HOVER || - aStatefulElement->HasRelevantHoverRules() || selector->mIDList || - selector->mClassList || - // We generally expect an mPseudoClassList, since we have a :hover. - // The question is whether we have anything else in there. - (selector->mPseudoClassList && - (selector->mPseudoClassList->mNext || - selector->mPseudoClassList->mType != CSSPseudoClassType::hover)) || - selector->mAttrList || selector->mNegations) && - (!isPseudoElement || StateSelectorMatches(aStatefulElement, - selectorForPseudo, - nodeContext, - aData->mTreeMatchContext, - selectorFlags, - nullptr, - aStateMask)) && - SelectorMatches(aData->mElement, - selector, - nodeContext, - aData->mTreeMatchContext, - selectorFlags) && - SelectorMatchesTree(aData->mElement, - selector->mNext, - aData->mTreeMatchContext, - eMatchOnConditionalRestyleAncestor)) { - hint = nsRestyleHint(hint | possibleChange); - } - } + cascade->HasStateDependentStyle( + aData, aStatefulElement, aPseudoType, aStateMask, hint); } } return hint; @@ -3207,140 +254,6 @@ nsCSSRuleProcessor::HasDocumentStateDependentStyle(StateRuleProcessorData* aData return false; } -struct AttributeEnumData { - AttributeEnumData(AttributeRuleProcessorData *aData, - RestyleHintData& aRestyleHintData) - : data(aData), change(nsRestyleHint(0)), hintData(aRestyleHintData) {} - - AttributeRuleProcessorData *data; - nsRestyleHint change; - RestyleHintData& hintData; -}; - - -static inline nsRestyleHint -RestyleHintForSelectorWithAttributeChange(nsRestyleHint aCurrentHint, - nsCSSSelector* aSelector, - nsCSSSelector* aRightmostSelector) -{ - MOZ_ASSERT(aSelector); - - char16_t oper = aSelector->mOperator; - - if (oper == char16_t('+') || oper == char16_t('~')) { - return eRestyle_LaterSiblings; - } - - if (oper == char16_t(':')) { - return eRestyle_Subtree; - } - - if (oper != char16_t(0)) { - // Check whether the selector is in a form that supports - // eRestyle_SomeDescendants. If it isn't, return eRestyle_Subtree. - - if (aCurrentHint & eRestyle_Subtree) { - // No point checking, since we'll end up restyling the whole - // subtree anyway. - return eRestyle_Subtree; - } - - if (!aRightmostSelector) { - // aSelector wasn't a top-level selector, which means we were inside - // a :not() or :-moz-any(). We don't support that. - return eRestyle_Subtree; - } - - MOZ_ASSERT(aSelector != aRightmostSelector, - "if aSelector == aRightmostSelector then we should have " - "no operator"); - - // Check that aRightmostSelector can be passed to RestrictedSelectorMatches. - if (!aRightmostSelector->IsRestrictedSelector()) { - return eRestyle_Subtree; - } - - // We also don't support pseudo-elements on any of the selectors - // between aRightmostSelector and aSelector. - // XXX Can we lift this restriction, so that we don't have to loop - // over all the selectors? - for (nsCSSSelector* sel = aRightmostSelector->mNext; - sel != aSelector; - sel = sel->mNext) { - MOZ_ASSERT(sel, "aSelector must be reachable from aRightmostSelector"); - if (sel->PseudoType() != CSSPseudoElementType::NotPseudo) { - return eRestyle_Subtree; - } - } - - return eRestyle_SomeDescendants; - } - - return eRestyle_Self; -} - -static void -AttributeEnumFunc(nsCSSSelector* aSelector, - nsCSSSelector* aRightmostSelector, - AttributeEnumData* aData) -{ - AttributeRuleProcessorData *data = aData->data; - - if (!data->mTreeMatchContext.SetStyleScopeForSelectorMatching(data->mElement, - data->mScope)) { - // The selector is for a rule in a scoped style sheet, and the subject - // of the selector matching is not in its scope. - return; - } - - nsRestyleHint possibleChange = - RestyleHintForSelectorWithAttributeChange(aData->change, - aSelector, aRightmostSelector); - - if (!LookForTargetPseudo(aSelector, &data->mTreeMatchContext, &possibleChange)) { - return; - } - - // If, ignoring eRestyle_SomeDescendants, enumData->change already includes - // all the bits of possibleChange, don't bother calling SelectorMatches, since - // even if it returns false enumData->change won't change. If possibleChange - // has eRestyle_SomeDescendants, we need to call SelectorMatches(Tree) - // regardless as it might give us new selectors to append to - // mSelectorsForDescendants. - NodeMatchContext nodeContext(EventStates(), false); - if (((possibleChange & (~(aData->change) | eRestyle_SomeDescendants))) && - SelectorMatches(data->mElement, aSelector, nodeContext, - data->mTreeMatchContext, SelectorMatchesFlags::UNKNOWN) && - SelectorMatchesTree(data->mElement, aSelector->mNext, - data->mTreeMatchContext, - eMatchOnConditionalRestyleAncestor)) { - aData->change = nsRestyleHint(aData->change | possibleChange); - if (possibleChange & eRestyle_SomeDescendants) { - aData->hintData.mSelectorsForDescendants.AppendElement(aRightmostSelector); - } - } -} - -static MOZ_ALWAYS_INLINE void -EnumerateSelectors(nsTArray& aSelectors, AttributeEnumData* aData) -{ - SelectorPair *iter = aSelectors.Elements(), - *end = iter + aSelectors.Length(); - for (; iter != end; ++iter) { - AttributeEnumFunc(iter->mSelector, iter->mRightmostSelector, aData); - } -} - -static MOZ_ALWAYS_INLINE void -EnumerateSelectors(nsTArray& aSelectors, AttributeEnumData* aData) -{ - nsCSSSelector **iter = aSelectors.Elements(), - **end = iter + aSelectors.Length(); - for (; iter != end; ++iter) { - AttributeEnumFunc(*iter, nullptr, aData); - } -} - nsRestyleHint nsCSSRuleProcessor::HasAttributeDependentStyle( AttributeRuleProcessorData* aData, @@ -3382,64 +295,8 @@ nsCSSRuleProcessor::HasAttributeDependentStyle( // ones we might have started matching. if (resolvedCascade) { for (RuleCascadeData* cascade : resolvedCascade->mOrderedData) { - if (aData->mAttribute == nsGkAtoms::id) { - nsIAtom* id = aData->mElement->GetID(); - if (id) { - auto entry = - static_cast(cascade->mIdSelectors.Search(id)); - if (entry) { - EnumerateSelectors(entry->mSelectors, &data); - } - } - - EnumerateSelectors(cascade->mPossiblyNegatedIDSelectors, &data); - } - - if (aData->mAttribute == nsGkAtoms::_class && - aData->mNameSpaceID == kNameSpaceID_None) { - const nsAttrValue* otherClasses = aData->mOtherValue; - NS_ASSERTION(otherClasses || - aData->mModType == nsIDOMMutationEvent::REMOVAL, - "All class values should be StoresOwnData and parsed" - "via Element::BeforeSetAttr, so available here"); - // For WillChange, enumerate classes that will be removed to see which - // rules apply before the change. - // For Changed, enumerate classes that have been added to see which rules - // apply after the change. - // In both cases we're interested in the classes that are currently on - // the element but not in mOtherValue. - const nsAttrValue* elementClasses = aData->mElement->GetClasses(); - if (elementClasses) { - int32_t atomCount = elementClasses->GetAtomCount(); - if (atomCount > 0) { - nsTHashtable> otherClassesTable; - if (otherClasses) { - int32_t otherClassesCount = otherClasses->GetAtomCount(); - for (int32_t i = 0; i < otherClassesCount; ++i) { - otherClassesTable.PutEntry(otherClasses->AtomAt(i)); - } - } - for (int32_t i = 0; i < atomCount; ++i) { - nsIAtom* curClass = elementClasses->AtomAt(i); - if (!otherClassesTable.Contains(curClass)) { - auto entry = static_cast( - cascade->mClassSelectors.Search(curClass)); - if (entry) { - EnumerateSelectors(entry->mSelectors, &data); - } - } - } - } - } - - EnumerateSelectors(cascade->mPossiblyNegatedClassSelectors, &data); - } - - auto entry = static_cast( - cascade->mAttributeSelectors.Search(aData->mAttribute)); - if (entry) { - EnumerateSelectors(entry->mSelectors, &data); - } + cascade->HasAttributeDependentStyle( + aData, &data, aRestyleHintDataResult); } } @@ -3665,561 +522,6 @@ nsCSSRuleProcessor::ClearRuleCascades() } -// This function should return the set of states that this selector -// depends on; this is used to implement HasStateDependentStyle. It -// does NOT recur down into things like :not and :-moz-any. -inline -EventStates ComputeSelectorStateDependence(nsCSSSelector& aSelector) -{ - EventStates states; - for (nsPseudoClassList* pseudoClass = aSelector.mPseudoClassList; - pseudoClass; pseudoClass = pseudoClass->mNext) { - // Tree pseudo-elements overload mPseudoClassList for things that - // aren't pseudo-classes. - if (pseudoClass->mType >= CSSPseudoClassType::Count) { - continue; - } - - auto idx = static_cast(pseudoClass->mType); - states |= sPseudoClassStateDependences[idx]; - } - return states; -} - -static bool -AddSelector(RuleCascadeData* aCascade, - // The part between combinators at the top level of the selector - nsCSSSelector* aSelectorInTopLevel, - // The part we should look through (might be in :not or :-moz-any()) - nsCSSSelector* aSelectorPart, - // The right-most selector at the top level - nsCSSSelector* aRightmostSelector) -{ - // It's worth noting that this loop over negations isn't quite - // optimal for two reasons. One, we could add something to one of - // these lists twice, which means we'll check it twice, but I don't - // think that's worth worrying about. (We do the same for multiple - // attribute selectors on the same attribute.) Two, we don't really - // need to check negations past the first in the current - // implementation (and they're rare as well), but that might change - // in the future if :not() is extended. - for (nsCSSSelector* negation = aSelectorPart; negation; - negation = negation->mNegations) { - // Track both document states and attribute dependence in pseudo-classes. - for (nsPseudoClassList* pseudoClass = negation->mPseudoClassList; - pseudoClass; pseudoClass = pseudoClass->mNext) { - switch (pseudoClass->mType) { - case CSSPseudoClassType::mozLocaleDir: { - aCascade->mSelectorDocumentStates |= NS_DOCUMENT_STATE_RTL_LOCALE; - break; - } - case CSSPseudoClassType::mozWindowInactive: { - aCascade->mSelectorDocumentStates |= NS_DOCUMENT_STATE_WINDOW_INACTIVE; - break; - } - case CSSPseudoClassType::mozTableBorderNonzero: { - nsTArray *array = - aCascade->AttributeListFor(nsGkAtoms::border); - if (!array) { - return false; - } - array->AppendElement(SelectorPair(aSelectorInTopLevel, - aRightmostSelector)); - break; - } - default: { - break; - } - } - } - - // Build mStateSelectors. - EventStates dependentStates = ComputeSelectorStateDependence(*negation); - if (!dependentStates.IsEmpty()) { - aCascade->mStateSelectors.AppendElement( - nsCSSRuleProcessor::StateSelector(dependentStates, - aSelectorInTopLevel)); - } - - // Build mIDSelectors - if (negation == aSelectorInTopLevel) { - for (nsAtomList* curID = negation->mIDList; curID; - curID = curID->mNext) { - auto entry = static_cast - (aCascade->mIdSelectors.Add(curID->mAtom, fallible)); - if (entry) { - entry->mSelectors.AppendElement(SelectorPair(aSelectorInTopLevel, - aRightmostSelector)); - } - } - } else if (negation->mIDList) { - aCascade->mPossiblyNegatedIDSelectors.AppendElement(aSelectorInTopLevel); - } - - // Build mClassSelectors - if (negation == aSelectorInTopLevel) { - for (nsAtomList* curClass = negation->mClassList; curClass; - curClass = curClass->mNext) { - auto entry = static_cast - (aCascade->mClassSelectors.Add(curClass->mAtom, fallible)); - if (entry) { - entry->mSelectors.AppendElement(SelectorPair(aSelectorInTopLevel, - aRightmostSelector)); - } - } - } else if (negation->mClassList) { - aCascade->mPossiblyNegatedClassSelectors.AppendElement(aSelectorInTopLevel); - } - - // Build mAttributeSelectors. - for (nsAttrSelector *attr = negation->mAttrList; attr; - attr = attr->mNext) { - nsTArray *array = - aCascade->AttributeListFor(attr->mCasedAttr); - if (!array) { - return false; - } - array->AppendElement(SelectorPair(aSelectorInTopLevel, - aRightmostSelector)); - if (attr->mLowercaseAttr != attr->mCasedAttr) { - array = aCascade->AttributeListFor(attr->mLowercaseAttr); - if (!array) { - return false; - } - array->AppendElement(SelectorPair(aSelectorInTopLevel, - aRightmostSelector)); - } - } - - // Recur through any pseudo-class that has a selector list argument. - for (nsPseudoClassList* pseudoClass = negation->mPseudoClassList; - pseudoClass; pseudoClass = pseudoClass->mNext) { - if (nsCSSPseudoClasses::HasSelectorListArg(pseudoClass->mType)) { - for (nsCSSSelectorList *l = pseudoClass->u.mSelectorList; l; l = l->mNext) { - nsCSSSelector *s = l->mSelectors; - if (!AddSelector(aCascade, aSelectorInTopLevel, s, - aRightmostSelector)) { - return false; - } - } - } - } - } - - return true; -} - -static bool -AddRule(RuleSelectorPair* aRuleInfo, RuleCascadeData* aCascade) -{ - RuleCascadeData * const cascade = aCascade; - - // Build the rule hash. - CSSPseudoElementType pseudoType = aRuleInfo->mSelector->PseudoType(); - if (MOZ_LIKELY(pseudoType == CSSPseudoElementType::NotPseudo)) { - cascade->mRuleHash.AppendRule(*aRuleInfo); - } else if (pseudoType < CSSPseudoElementType::Count) { - RuleHash*& ruleHash = cascade->mPseudoElementRuleHashes[ - static_cast(pseudoType)]; - if (!ruleHash) { - ruleHash = new RuleHash(cascade->mQuirksMode); - if (!ruleHash) { - // Out of memory; give up - return false; - } - } - NS_ASSERTION(aRuleInfo->mSelector->mNext, - "Must have mNext; parser screwed up"); - NS_ASSERTION(aRuleInfo->mSelector->mNext->mOperator == ':', - "Unexpected mNext combinator"); - ruleHash->AppendRule(*aRuleInfo); - } else if (pseudoType == CSSPseudoElementType::AnonBox) { - NS_ASSERTION(!aRuleInfo->mSelector->mCasedTag && - !aRuleInfo->mSelector->mIDList && - !aRuleInfo->mSelector->mClassList && - !aRuleInfo->mSelector->mPseudoClassList && - !aRuleInfo->mSelector->mAttrList && - !aRuleInfo->mSelector->mNegations && - !aRuleInfo->mSelector->mNext && - aRuleInfo->mSelector->mNameSpace == kNameSpaceID_Unknown, - "Parser messed up with anon box selector"); - - // Index doesn't matter here, since we'll just be walking these - // rules in order; just pass 0. - AppendRuleToTagTable(&cascade->mAnonBoxRules, - aRuleInfo->mSelector->mLowercaseTag, - RuleValue(*aRuleInfo, 0, aCascade->mQuirksMode)); - } else { -#ifdef MOZ_XUL - NS_ASSERTION(pseudoType == CSSPseudoElementType::XULTree, - "Unexpected pseudo type"); - // Index doesn't matter here, since we'll just be walking these - // rules in order; just pass 0. - AppendRuleToTagTable(&cascade->mXULTreeRules, - aRuleInfo->mSelector->mLowercaseTag, - RuleValue(*aRuleInfo, 0, aCascade->mQuirksMode)); -#else - NS_NOTREACHED("Unexpected pseudo type"); -#endif - } - - for (nsCSSSelector* selector = aRuleInfo->mSelector; - selector; selector = selector->mNext) { - if (selector->IsPseudoElement()) { - CSSPseudoElementType pseudo = selector->PseudoType(); - if (pseudo >= CSSPseudoElementType::Count || - !nsCSSPseudoElements::PseudoElementSupportsUserActionState(pseudo)) { - NS_ASSERTION(!selector->mNegations, "Shouldn't have negations"); - // We do store selectors ending with pseudo-elements that allow :hover - // and :active after them in the hashtables corresponding to that - // selector's mNext (i.e. the thing that matches against the element), - // but we want to make sure that selectors for any other kinds of - // pseudo-elements don't end up in the hashtables. In particular, tree - // pseudos store strange things in mPseudoClassList that we don't want - // to try to match elements against. - continue; - } - } - if (!AddSelector(cascade, selector, selector, aRuleInfo->mSelector)) { - return false; - } - } - - return true; -} - -struct PerWeightDataListItem : public RuleSelectorPair { - PerWeightDataListItem(css::StyleRule* aRule, nsCSSSelector* aSelector) - : RuleSelectorPair(aRule, aSelector) - , mNext(nullptr) - {} - // No destructor; these are arena-allocated - - - // Placement new to arena allocate the PerWeightDataListItem - void *operator new(size_t aSize, PLArenaPool &aArena) CPP_THROW_NEW { - void *mem; - PL_ARENA_ALLOCATE(mem, &aArena, aSize); - return mem; - } - - PerWeightDataListItem *mNext; -}; - -struct PerWeightData { - PerWeightData() - : mRuleSelectorPairs(nullptr) - , mTail(&mRuleSelectorPairs) - {} - - int32_t mWeight; - PerWeightDataListItem *mRuleSelectorPairs; - PerWeightDataListItem **mTail; -}; - -struct RuleByWeightEntry : public PLDHashEntryHdr { - PerWeightData data; // mWeight is key, mRuleSelectorPairs are value -}; - -static PLDHashNumber -HashIntKey(const void *key) -{ - return PLDHashNumber(NS_PTR_TO_INT32(key)); -} - -static bool -MatchWeightEntry(const PLDHashEntryHdr *hdr, const void *key) -{ - const RuleByWeightEntry *entry = (const RuleByWeightEntry *)hdr; - return entry->data.mWeight == NS_PTR_TO_INT32(key); -} - -static void -InitWeightEntry(PLDHashEntryHdr *hdr, const void *key) -{ - RuleByWeightEntry* entry = static_cast(hdr); - new (KnownNotNull, entry) RuleByWeightEntry(); -} - -static const PLDHashTableOps gRulesByWeightOps = { - HashIntKey, - MatchWeightEntry, - PLDHashTable::MoveEntryStub, - PLDHashTable::ClearEntryStub, - InitWeightEntry -}; - -static int -CompareWeightData(const void* aArg1, const void* aArg2, void* closure) -{ - const PerWeightData* arg1 = static_cast(aArg1); - const PerWeightData* arg2 = static_cast(aArg2); - return arg1->mWeight - arg2->mWeight; // put lower weight first -} - -struct CascadeEnumData -{ - CascadeEnumData(nsPresContext* aPresContext, - nsString aName, -#ifdef DEBUG - CascadeEnumData* aParent, -#endif - nsAutoPtr& aContainer, - bool aIsWeak, - nsTArray& aDocumentRules, - nsDocumentRuleResultCacheKey& aDocumentKey, - SheetType aSheetType, - bool aMustGatherDocumentRules) - : mPresContext(aPresContext) - , mName(aName) - , mIsAnonymous(mName.IsEmpty()) - , mIsWeak(aIsWeak) - , mWasFlattened(false) -#ifdef DEBUG - , mParent(aParent) - , mIsRoot(false) -#endif - , mContainer(aContainer) - , mDocumentRules(aDocumentRules) - , mDocumentCacheKey(aDocumentKey) - , mSheetType(aSheetType) - , mMustGatherDocumentRules(aMustGatherDocumentRules) - , mRulesByWeight(&gRulesByWeightOps, sizeof(RuleByWeightEntry), 32) - { - Initialize(); - } - - CascadeEnumData(nsPresContext* aPresContext, - nsAutoPtr& aContainer, - nsTArray& aDocumentRules, - nsDocumentRuleResultCacheKey& aDocumentKey, - SheetType aSheetType, - bool aMustGatherDocumentRules) - : mPresContext(aPresContext) - , mContainer(aContainer) - , mIsAnonymous(false) - , mIsWeak(false) - , mWasFlattened(false) -#ifdef DEBUG - , mParent(nullptr) - , mIsRoot(true) -#endif - , mDocumentRules(aDocumentRules) - , mDocumentCacheKey(aDocumentKey) - , mSheetType(aSheetType) - , mMustGatherDocumentRules(aMustGatherDocumentRules) - , mRulesByWeight(&gRulesByWeightOps, sizeof(RuleByWeightEntry), 32) - { - Initialize(); - } - - ~CascadeEnumData() - { - PL_FinishArenaPool(&mArena); - } - - nsPresContext* mPresContext; - nsString mName; - bool mIsAnonymous; - bool mIsWeak; - bool mWasFlattened; - - RuleCascadeData* mData; - - nsTArray mStyleRules; - nsTArray& mDocumentRules; - nsDocumentRuleResultCacheKey& mDocumentCacheKey; - SheetType mSheetType; - bool mMustGatherDocumentRules; - - PLArenaPool mArena; - // Hooray, a manual PLDHashTable since nsClassHashtable doesn't - // provide a getter that gives me a *reference* to the value. - PLDHashTable mRulesByWeight; // of PerWeightDataListItem linked lists - -#ifdef DEBUG - CascadeEnumData* mParent; - bool mIsRoot; -#endif - nsAutoPtr& mContainer; - nsTArray mPreLayers; - nsTArray mPostLayers; - nsDataHashtable mLayers; - - CascadeEnumData* CreateNamedChildLayer(const nsTArray& aPath); - CascadeEnumData* CreateAnonymousChildLayer(); - void Flatten(); - -private: - void Initialize(); - void AddRules(); -}; - -CascadeEnumData* -CascadeEnumData::CreateNamedChildLayer(const nsTArray& aPath) -{ - if (aPath.IsEmpty()) { - return this; - } - - const nsString& name = aPath[0]; - CascadeEnumData* childLayer = nullptr; - - // Create new layer if it doesn't exist. - if (!mLayers.Get(name, &childLayer)) { - childLayer = new CascadeEnumData(mPresContext, - name, -#ifdef DEBUG - this, -#endif - mContainer, - false, - mDocumentRules, - mDocumentCacheKey, - mSheetType, - mMustGatherDocumentRules); - mPreLayers.AppendElement(childLayer); - mLayers.Put(name, childLayer); - } - - // Final layer in the path. - if (aPath.Length() == 1) { - return childLayer; - } - - // Continue with the tail of the path. - nsTArray tail; - tail.AppendElements(aPath.Elements() + 1, aPath.Length() - 1); - return childLayer->CreateNamedChildLayer(tail); -} - -CascadeEnumData* -CascadeEnumData::CreateAnonymousChildLayer() -{ - nsString name; - CascadeEnumData* childLayer = new CascadeEnumData(mPresContext, - name, -#ifdef DEBUG - this, -#endif - mContainer, - false, - mDocumentRules, - mDocumentCacheKey, - mSheetType, - mMustGatherDocumentRules); - mPreLayers.AppendElement(childLayer); - return childLayer; -} - -void -CascadeEnumData::AddRules() -{ - for (css::StyleRule* styleRule : mStyleRules) { - for (nsCSSSelectorList* sel = styleRule->Selector(); sel; - sel = sel->mNext) { - int32_t weight = sel->mWeight; - auto entry = static_cast( - mRulesByWeight.Add(NS_INT32_TO_PTR(weight), fallible)); - if (!entry) { - return; - } - entry->data.mWeight = weight; - // entry->data.mRuleSelectorPairs should be linked in forward order; - // entry->data.mTail is the slot to write to. - auto* newItem = - new (mArena) PerWeightDataListItem(styleRule, sel->mSelectors); - if (newItem) { - *(entry->data.mTail) = newItem; - entry->data.mTail = &newItem->mNext; - } - } - } - - // Sort the hash table of per-weight linked lists by weight. - uint32_t weightCount = mRulesByWeight.EntryCount(); - auto weightArray = MakeUnique(weightCount); - int32_t j = 0; - for (auto iter = mRulesByWeight.Iter(); !iter.Done(); iter.Next()) { - auto entry = static_cast(iter.Get()); - weightArray[j++] = entry->data; - } - NS_QuickSort(weightArray.get(), - weightCount, - sizeof(PerWeightData), - CompareWeightData, - nullptr); - - // Put things into the rule hash. - // The primary sort is by weight... - for (uint32_t i = 0; i < weightCount; ++i) { - // and the secondary sort is by order. mRuleSelectorPairs is already in - // the right order.. - for (PerWeightDataListItem* cur = weightArray[i].mRuleSelectorPairs; cur; - cur = cur->mNext) { - if (!AddRule(cur, mData)) - return; /* out of memory */ - } - } - - // Build mKeyframesRuleTable. - for (nsTArray::size_type - i = 0, - iEnd = mData->mKeyframesRules.Length(); - i < iEnd; - ++i) { - nsCSSKeyframesRule* rule = mData->mKeyframesRules[i]; - mData->mKeyframesRuleTable.Put(rule->GetName(), rule); - } - - // Build mCounterStyleRuleTable - for (nsTArray::size_type - i = 0, - iEnd = mData->mCounterStyleRules.Length(); - i < iEnd; - ++i) { - nsCSSCounterStyleRule* rule = mData->mCounterStyleRules[i]; - mData->mCounterStyleRuleTable.Put(rule->GetName(), rule); - } -} - -void -CascadeEnumData::Flatten() -{ - if (mWasFlattened) { - return; - } - - if (mPreLayers.Length() > 0) { - for (CascadeEnumData* pre : mPreLayers) { - pre->Flatten(); - } - } - - mContainer->mOrderedData.AppendElement(mData); - AddRules(); - - if (mPostLayers.Length() > 0) { - for (CascadeEnumData* post : mPostLayers) { - post->Flatten(); - } - } - - mWasFlattened = true; -} - -void -CascadeEnumData::Initialize() -{ - mData = new RuleCascadeData( - mPresContext->Medium(), - eCompatibility_NavQuirks == mPresContext->CompatibilityMode()); - - // Initialize our arena - PL_INIT_ARENA_POOL( - &mArena, "CascadeEnumDataArena", NS_CASCADEENUMDATA_ARENA_BLOCK_SIZE); -} - /** * Recursively traverses rules in order to: * (1) add any @-moz-document rules into data->mDocumentRules. @@ -4240,15 +542,13 @@ GatherDocRuleEnumFunc(css::Rule* aRule, void* aData) "should only call GatherDocRuleEnumFunc if " "mMustGatherDocumentRules is true"); - if (css::Rule::MEDIA_RULE == type || - css::Rule::SUPPORTS_RULE == type || + if (css::Rule::MEDIA_RULE == type || css::Rule::SUPPORTS_RULE == type || css::Rule::LAYER_BLOCK_RULE == type) { css::GroupRule* groupRule = static_cast(aRule); if (!groupRule->EnumerateRulesForwards(GatherDocRuleEnumFunc, aData)) { return false; } - } - else if (css::Rule::DOCUMENT_RULE == type) { + } else if (css::Rule::DOCUMENT_RULE == type) { css::DocumentRule* docRule = static_cast(aRule); if (!layer->mDocumentRules.AppendElement(docRule)) { return false; @@ -4304,48 +604,43 @@ CascadeRuleEnumFunc(css::Rule* aRule, void* aData) if (!layer->mStyleRules.AppendElement(styleRule)) { return false; } - } - else if (css::Rule::MEDIA_RULE == type || - css::Rule::SUPPORTS_RULE == type) { + } else if (css::Rule::MEDIA_RULE == type || + css::Rule::SUPPORTS_RULE == type) { css::GroupRule* groupRule = static_cast(aRule); - const bool use = - groupRule->UseForPresentation(layer->mPresContext, layer->mData->mCacheKey); + const bool use = groupRule->UseForPresentation(layer->mPresContext, + layer->mData->mCacheKey); if (use || layer->mMustGatherDocumentRules) { - if (!groupRule->EnumerateRulesForwards(use ? CascadeRuleEnumFunc : - GatherDocRuleEnumFunc, - aData)) { + if (!groupRule->EnumerateRulesForwards( + use ? CascadeRuleEnumFunc : GatherDocRuleEnumFunc, aData)) { return false; } } - } - else if (css::Rule::LAYER_STATEMENT_RULE == type) { - CSSLayerStatementRule* layerRule = static_cast(aRule); + } else if (css::Rule::LAYER_STATEMENT_RULE == type) { + CSSLayerStatementRule* layerRule = + static_cast(aRule); nsTArray> pathList; layerRule->GetPathList(pathList); for (const nsTArray& path : pathList) { layer->CreateNamedChildLayer(path); } - } - else if (css::Rule::LAYER_BLOCK_RULE == type) { + } else if (css::Rule::LAYER_BLOCK_RULE == type) { CSSLayerBlockRule* layerRule = static_cast(aRule); nsTArray path; layerRule->GetPath(path); nsString name; layerRule->GetName(name); CascadeEnumData* targetLayer = name.IsEmpty() - ? layer->CreateAnonymousChildLayer() - : layer->CreateNamedChildLayer(path); - const bool use = - layerRule->UseForPresentation(layer->mPresContext, layer->mData->mCacheKey); + ? layer->CreateAnonymousChildLayer() + : layer->CreateNamedChildLayer(path); + const bool use = layerRule->UseForPresentation(layer->mPresContext, + layer->mData->mCacheKey); if (use || layer->mMustGatherDocumentRules) { - if (!layerRule->EnumerateRulesForwards(use ? CascadeRuleEnumFunc : - GatherDocRuleEnumFunc, - targetLayer)) { + if (!layerRule->EnumerateRulesForwards( + use ? CascadeRuleEnumFunc : GatherDocRuleEnumFunc, targetLayer)) { return false; } } - } - else if (css::Rule::DOCUMENT_RULE == type) { + } else if (css::Rule::DOCUMENT_RULE == type) { css::DocumentRule* docRule = static_cast(aRule); if (layer->mMustGatherDocumentRules) { if (!layer->mDocumentRules.AppendElement(docRule)) { @@ -4359,42 +654,36 @@ CascadeRuleEnumFunc(css::Rule* aRule, void* aData) } } if (use || layer->mMustGatherDocumentRules) { - if (!docRule->EnumerateRulesForwards(use ? CascadeRuleEnumFunc - : GatherDocRuleEnumFunc, - aData)) { + if (!docRule->EnumerateRulesForwards( + use ? CascadeRuleEnumFunc : GatherDocRuleEnumFunc, aData)) { return false; } } - } - else if (css::Rule::FONT_FACE_RULE == type) { - nsCSSFontFaceRule *fontFaceRule = static_cast(aRule); - nsFontFaceRuleContainer *ptr = layer->mData->mFontFaceRules.AppendElement(); + } else if (css::Rule::FONT_FACE_RULE == type) { + nsCSSFontFaceRule* fontFaceRule = static_cast(aRule); + nsFontFaceRuleContainer* ptr = layer->mData->mFontFaceRules.AppendElement(); if (!ptr) return false; ptr->mRule = fontFaceRule; ptr->mSheetType = layer->mSheetType; - } - else if (css::Rule::KEYFRAMES_RULE == type) { - nsCSSKeyframesRule *keyframesRule = - static_cast(aRule); + } else if (css::Rule::KEYFRAMES_RULE == type) { + nsCSSKeyframesRule* keyframesRule = static_cast(aRule); if (!layer->mData->mKeyframesRules.AppendElement(keyframesRule)) { return false; } - } - else if (css::Rule::FONT_FEATURE_VALUES_RULE == type) { - nsCSSFontFeatureValuesRule *fontFeatureValuesRule = + } else if (css::Rule::FONT_FEATURE_VALUES_RULE == type) { + nsCSSFontFeatureValuesRule* fontFeatureValuesRule = static_cast(aRule); - if (!layer->mData->mFontFeatureValuesRules.AppendElement(fontFeatureValuesRule)) { + if (!layer->mData->mFontFeatureValuesRules.AppendElement( + fontFeatureValuesRule)) { return false; } - } - else if (css::Rule::PAGE_RULE == type) { + } else if (css::Rule::PAGE_RULE == type) { nsCSSPageRule* pageRule = static_cast(aRule); if (!layer->mData->mPageRules.AppendElement(pageRule)) { return false; } - } - else if (css::Rule::COUNTER_STYLE_RULE == type) { + } else if (css::Rule::COUNTER_STYLE_RULE == type) { nsCSSCounterStyleRule* counterStyleRule = static_cast(aRule); if (!layer->mData->mCounterStyleRules.AppendElement(counterStyleRule)) { @@ -4531,26 +820,6 @@ nsCSSRuleProcessor::RefreshRuleCascade(nsPresContext* aPresContext) return; } -/* static */ bool -nsCSSRuleProcessor::RestrictedSelectorListMatches(Element* aElement, - TreeMatchContext& aTreeMatchContext, - nsCSSSelectorList* aSelectorList) -{ - MOZ_ASSERT(!aTreeMatchContext.mForScopedStyle, - "mCurrentStyleScope will need to be saved and restored after the " - "SelectorMatchesTree call"); - - NodeMatchContext nodeContext(EventStates(), false); - SelectorMatchesFlags flags = aElement->IsInShadowTree() ? - SelectorMatchesFlags::NONE : - SelectorMatchesFlags::IS_OUTSIDE_SHADOW_TREE; - return SelectorListMatches(aElement, - aSelectorList, - nodeContext, - aTreeMatchContext, - flags); -} - void nsCSSRuleProcessor::TakeDocumentRulesAndCacheKey( nsPresContext* aPresContext, @@ -4589,137 +858,3 @@ nsCSSRuleProcessor::ReleaseStyleSetRef() RuleProcessorCache::StartTracking(this); } } - -// TreeMatchContext and AncestorFilter out of line methods -void -TreeMatchContext::InitAncestors(Element *aElement) -{ - MOZ_ASSERT(!mAncestorFilter.mFilter); - MOZ_ASSERT(mAncestorFilter.mHashes.IsEmpty()); - MOZ_ASSERT(mStyleScopes.IsEmpty()); - - mAncestorFilter.mFilter = new AncestorFilter::Filter(); - - if (MOZ_LIKELY(aElement)) { - MOZ_ASSERT(aElement->GetUncomposedDoc() || - aElement->HasFlag(NODE_IS_IN_SHADOW_TREE), - "aElement must be in the document or in shadow tree " - "for the assumption that GetParentNode() is non-null " - "on all element ancestors of aElement to be true"); - // Collect up the ancestors - AutoTArray ancestors; - Element* cur = aElement; - do { - ancestors.AppendElement(cur); - cur = cur->GetParentElementCrossingShadowRoot(); - } while (cur); - - // Now push them in reverse order. - for (uint32_t i = ancestors.Length(); i-- != 0; ) { - mAncestorFilter.PushAncestor(ancestors[i]); - PushStyleScope(ancestors[i]); - PushShadowHost(ancestors[i]); - } - } -} - -void -TreeMatchContext::InitStyleScopes(Element* aElement) -{ - MOZ_ASSERT(mStyleScopes.IsEmpty()); - - if (MOZ_LIKELY(aElement)) { - // Collect up the ancestors - AutoTArray ancestors; - Element* cur = aElement; - do { - ancestors.AppendElement(cur); - cur = cur->GetParentElementCrossingShadowRoot(); - } while (cur); - - // Now push them in reverse order. - for (uint32_t i = ancestors.Length(); i-- != 0; ) { - PushStyleScope(ancestors[i]); - } - } -} - -void -AncestorFilter::PushAncestor(Element *aElement) -{ - MOZ_ASSERT(mFilter); - - uint32_t oldLength = mHashes.Length(); - - mPopTargets.AppendElement(oldLength); -#ifdef DEBUG - mElements.AppendElement(aElement); -#endif - mHashes.AppendElement(aElement->NodeInfo()->NameAtom()->hash()); - nsIAtom *id = aElement->GetID(); - if (id) { - mHashes.AppendElement(id->hash()); - } - const nsAttrValue *classes = aElement->GetClasses(); - if (classes) { - uint32_t classCount = classes->GetAtomCount(); - for (uint32_t i = 0; i < classCount; ++i) { - mHashes.AppendElement(classes->AtomAt(i)->hash()); - } - } - - uint32_t newLength = mHashes.Length(); - for (uint32_t i = oldLength; i < newLength; ++i) { - mFilter->add(mHashes[i]); - } -} - -void -AncestorFilter::PopAncestor() -{ - MOZ_ASSERT(!mPopTargets.IsEmpty()); - MOZ_ASSERT(mPopTargets.Length() == mElements.Length()); - - uint32_t popTargetLength = mPopTargets.Length(); - uint32_t newLength = mPopTargets[popTargetLength-1]; - - mPopTargets.TruncateLength(popTargetLength-1); -#ifdef DEBUG - mElements.TruncateLength(popTargetLength-1); -#endif - - uint32_t oldLength = mHashes.Length(); - for (uint32_t i = newLength; i < oldLength; ++i) { - mFilter->remove(mHashes[i]); - } - mHashes.TruncateLength(newLength); -} - -#ifdef DEBUG -void -AncestorFilter::AssertHasAllAncestors(Element *aElement) const -{ - Element* cur = aElement->GetParentElementCrossingShadowRoot(); - while (cur) { - MOZ_ASSERT(mElements.Contains(cur)); - cur = cur->GetParentElementCrossingShadowRoot(); - } -} - -void -TreeMatchContext::AssertHasAllStyleScopes(Element* aElement) const -{ - if (aElement->IsInNativeAnonymousSubtree()) { - // Document style sheets are never applied to native anonymous content, - // so it's not possible for them to be in a