summaryrefslogtreecommitdiffstats
path: root/layout/style/nsCSSRuleProcessor.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'layout/style/nsCSSRuleProcessor.cpp')
-rw-r--r--layout/style/nsCSSRuleProcessor.cpp4061
1 files changed, 4061 insertions, 0 deletions
diff --git a/layout/style/nsCSSRuleProcessor.cpp b/layout/style/nsCSSRuleProcessor.cpp
new file mode 100644
index 000000000..07a4ef57b
--- /dev/null
+++ b/layout/style/nsCSSRuleProcessor.cpp
@@ -0,0 +1,4061 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+// vim:cindent:tabstop=2:expandtab:shiftwidth=2:
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * style rule processor for CSS style sheets, responsible for selector
+ * matching and cascading
+ */
+
+#define PL_ARENA_CONST_ALIGN_MASK 7
+// We want page-sized arenas so there's no fragmentation involved.
+// Including plarena.h must come first to avoid it being included by some
+// header file thereby making PL_ARENA_CONST_ALIGN_MASK ineffective.
+#define NS_CASCADEENUMDATA_ARENA_BLOCK_SIZE (4096)
+#include "plarena.h"
+
+#include "nsAutoPtr.h"
+#include "nsCSSRuleProcessor.h"
+#include "nsRuleProcessorData.h"
+#include <algorithm>
+#include "nsIAtom.h"
+#include "PLDHashTable.h"
+#include "nsICSSPseudoComparator.h"
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/css/StyleRule.h"
+#include "mozilla/css/GroupRule.h"
+#include "nsIDocument.h"
+#include "nsPresContext.h"
+#include "nsGkAtoms.h"
+#include "nsUnicharUtils.h"
+#include "nsError.h"
+#include "nsRuleWalker.h"
+#include "nsCSSPseudoClasses.h"
+#include "nsCSSPseudoElements.h"
+#include "nsIContent.h"
+#include "nsCOMPtr.h"
+#include "nsHashKeys.h"
+#include "nsStyleUtil.h"
+#include "nsQuickSort.h"
+#include "nsAttrValue.h"
+#include "nsAttrValueInlines.h"
+#include "nsAttrName.h"
+#include "nsTArray.h"
+#include "nsContentUtils.h"
+#include "nsIMediaList.h"
+#include "nsCSSRules.h"
+#include "nsStyleSet.h"
+#include "mozilla/dom/Element.h"
+#include "nsNthIndexCache.h"
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/EventStates.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/LookAndFeel.h"
+#include "mozilla/Likely.h"
+#include "mozilla/OperatorNewExtensions.h"
+#include "mozilla/TypedEnumBits.h"
+#include "RuleProcessorCache.h"
+#include "nsIDOMMutationEvent.h"
+#include "nsIMozBrowserFrame.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+#define VISITED_PSEUDO_PREF "layout.css.visited_links_enabled"
+
+static bool gSupportVisitedPseudo = true;
+
+static nsTArray< nsCOMPtr<nsIAtom> >* 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<RuleValue, 1> 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<nsIAtom> mTag;
+};
+
+static PLDHashNumber
+RuleHash_CIHashKey(const void *key)
+{
+ nsIAtom *atom = const_cast<nsIAtom*>(static_cast<const nsIAtom*>(key));
+
+ nsAutoString str;
+ atom->ToString(str);
+ nsContentUtils::ASCIIToLower(str);
+ return HashString(str);
+}
+
+static inline nsCSSSelector*
+SubjectSelectorForRuleHash(const PLDHashEntryHdr *hdr)
+{
+ auto entry = static_cast<const RuleHashTableEntry*>(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<nsIAtom*>(static_cast<const nsIAtom*>(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<nsIAtom*>(static_cast<const nsIAtom*>(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<RuleHashTableEntry*>(hdr);
+ new (KnownNotNull, entry) RuleHashTableEntry();
+}
+
+static void
+RuleHash_ClearEntry(PLDHashTable *table, PLDHashEntryHdr *hdr)
+{
+ RuleHashTableEntry* entry = static_cast<RuleHashTableEntry*>(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<RuleHashTableEntry*>(
+ static_cast<const RuleHashTableEntry*>(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<nsIAtom*>(static_cast<const nsIAtom*>(key));
+ nsIAtom *entry_atom = static_cast<const RuleHashTagTableEntry*>(hdr)->mTag;
+
+ return match_atom == entry_atom;
+}
+
+static void
+RuleHash_TagTable_InitEntry(PLDHashEntryHdr *hdr, const void *key)
+{
+ RuleHashTagTableEntry* entry = static_cast<RuleHashTagTableEntry*>(hdr);
+ new (KnownNotNull, entry) RuleHashTagTableEntry();
+ entry->mTag = const_cast<nsIAtom*>(static_cast<const nsIAtom*>(key));
+}
+
+static void
+RuleHash_TagTable_ClearEntry(PLDHashTable *table, PLDHashEntryHdr *hdr)
+{
+ RuleHashTagTableEntry* entry = static_cast<RuleHashTagTableEntry*>(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<RuleHashTagTableEntry*>(
+ static_cast<const RuleHashTagTableEntry*>(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<const RuleHashTableEntry*>(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<RuleValue> 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<void*>(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<CSSStyleSheet> 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<RuleHashTableEntry*>(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<RuleHashTagTableEntry*>(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<RuleHashTableEntry*>
+ (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<RuleHashTableEntry*>(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<RuleHashTableEntry*>(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<RuleHashTableEntry*>
+ (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
+ if (filter) {
+ filter->AssertHasAllAncestors(aElement);
+ }
+#endif
+ // Merge the lists while there are still multiple lists to merge.
+ while (valueCount > 1) {
+ int32_t valueIndex = 0;
+ int32_t lowestRuleIndex = mEnumList[valueIndex].mCurValue->mIndex;
+ for (int32_t index = 1; index < valueCount; ++index) {
+ int32_t ruleIndex = mEnumList[index].mCurValue->mIndex;
+ if (ruleIndex < lowestRuleIndex) {
+ valueIndex = index;
+ lowestRuleIndex = ruleIndex;
+ }
+ }
+ const RuleValue *cur = mEnumList[valueIndex].mCurValue;
+ ContentEnumFunc(*cur, cur->mSelector, aData, aNodeContext, filter);
+ cur++;
+ if (cur == mEnumList[valueIndex].mEnd) {
+ mEnumList[valueIndex] = mEnumList[--valueCount];
+ } else {
+ mEnumList[valueIndex].mCurValue = cur;
+ }
+ }
+
+ // Fast loop over single value.
+ for (const RuleValue *value = mEnumList[0].mCurValue,
+ *end = mEnumList[0].mEnd;
+ value != end; ++value) {
+ ContentEnumFunc(*value, value->mSelector, aData, aNodeContext, filter);
+ }
+ }
+}
+
+static size_t
+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<RuleHashTableEntry*>(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<SelectorPair, 2> mSelectors;
+};
+
+static void
+AtomSelector_ClearEntry(PLDHashTable *table, PLDHashEntryHdr *hdr)
+{
+ (static_cast<AtomSelectorEntry*>(hdr))->~AtomSelectorEntry();
+}
+
+static void
+AtomSelector_InitEntry(PLDHashEntryHdr *hdr, const void *key)
+{
+ AtomSelectorEntry *entry = static_cast<AtomSelectorEntry*>(hdr);
+ new (KnownNotNull, entry) AtomSelectorEntry();
+ entry->mAtom = const_cast<nsIAtom*>(static_cast<const nsIAtom*>(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<AtomSelectorEntry*>(static_cast<const AtomSelectorEntry*>(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<const AtomSelectorEntry*>(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),
+ mNext(nullptr),
+ 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<CSSPseudoElementTypeBase>(CSSPseudoElementType::Count)];
+ nsTArray<nsCSSRuleProcessor::StateSelector> mStateSelectors;
+ EventStates mSelectorDocumentStates;
+ PLDHashTable mClassSelectors;
+ PLDHashTable mIdSelectors;
+ nsTArray<nsCSSSelector*> mPossiblyNegatedClassSelectors;
+ nsTArray<nsCSSSelector*> mPossiblyNegatedIDSelectors;
+ PLDHashTable mAttributeSelectors;
+ PLDHashTable mAnonBoxRules;
+#ifdef MOZ_XUL
+ PLDHashTable mXULTreeRules;
+#endif
+
+ nsTArray<nsFontFaceRuleContainer> mFontFaceRules;
+ nsTArray<nsCSSKeyframesRule*> mKeyframesRules;
+ nsTArray<nsCSSFontFeatureValuesRule*> mFontFeatureValuesRules;
+ nsTArray<nsCSSPageRule*> mPageRules;
+ nsTArray<nsCSSCounterStyleRule*> mCounterStyleRules;
+
+ nsDataHashtable<nsStringHashKey, nsCSSKeyframesRule*> mKeyframesRuleTable;
+ nsDataHashtable<nsStringHashKey, nsCSSCounterStyleRule*> mCounterStyleRuleTable;
+
+ // Looks up or creates the appropriate list in |mAttributeSelectors|.
+ // Returns null only on allocation failure.
+ nsTArray<SelectorPair>* AttributeListFor(nsIAtom* aAttribute);
+
+ nsMediaQueryResultCacheKey mCacheKey;
+ RuleCascadeData* mNext; // for a different medium
+
+ 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<AtomSelectorEntry*>(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<SelectorPair>*
+RuleCascadeData::AttributeListFor(nsIAtom* aAttribute)
+{
+ auto entry = static_cast<AtomSelectorEntry*>
+ (mAttributeSelectors.Add(aAttribute, fallible));
+ if (!entry)
+ return nullptr;
+ return &entry->mSelectors;
+}
+
+// -------------------------------
+// CSS Style rule processor implementation
+//
+
+nsCSSRuleProcessor::nsCSSRuleProcessor(const sheet_array_type& aSheets,
+ SheetType aSheetType,
+ Element* aScopeElement,
+ nsCSSRuleProcessor*
+ aPreviousCSSRuleProcessor,
+ bool aIsShared)
+ : nsCSSRuleProcessor(sheet_array_type(aSheets), aSheetType, aScopeElement,
+ aPreviousCSSRuleProcessor, aIsShared)
+{
+}
+
+nsCSSRuleProcessor::nsCSSRuleProcessor(sheet_array_type&& aSheets,
+ SheetType aSheetType,
+ Element* aScopeElement,
+ nsCSSRuleProcessor*
+ aPreviousCSSRuleProcessor,
+ bool aIsShared)
+ : mSheets(aSheets)
+ , mRuleCascades(nullptr)
+ , mPreviousCacheKey(aPreviousCSSRuleProcessor
+ ? aPreviousCSSRuleProcessor->CloneMQCacheKey()
+ : UniquePtr<nsMediaQueryResultCacheKey>())
+ , mLastPresContext(nullptr)
+ , mScopeElement(aScopeElement)
+ , mStyleSetRefCnt(0)
+ , mSheetType(aSheetType)
+ , mIsShared(aIsShared)
+ , mMustGatherDocumentRules(aIsShared)
+ , mInRuleProcessorCache(false)
+#ifdef DEBUG
+ , mDocumentRulesAndCacheKeyValid(false)
+#endif
+{
+ NS_ASSERTION(!!mScopeElement == (aSheetType == SheetType::ScopedDoc),
+ "aScopeElement must be specified iff aSheetType is "
+ "eScopedDocSheet");
+ for (sheet_array_type::size_type i = mSheets.Length(); i-- != 0; ) {
+ mSheets[i]->AddRuleProcessor(this);
+ }
+}
+
+nsCSSRuleProcessor::~nsCSSRuleProcessor()
+{
+ if (mInRuleProcessorCache) {
+ RuleProcessorCache::RemoveRuleProcessor(this);
+ }
+ MOZ_ASSERT(!mExpirationState.IsTracked());
+ MOZ_ASSERT(mStyleSetRefCnt == 0);
+ ClearSheets();
+ ClearRuleCascades();
+}
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsCSSRuleProcessor)
+ NS_INTERFACE_MAP_ENTRY(nsIStyleRuleProcessor)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(nsCSSRuleProcessor)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(nsCSSRuleProcessor)
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(nsCSSRuleProcessor)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsCSSRuleProcessor)
+ tmp->ClearSheets();
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mScopeElement)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsCSSRuleProcessor)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSheets)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mScopeElement)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+void
+nsCSSRuleProcessor::ClearSheets()
+{
+ for (sheet_array_type::size_type i = mSheets.Length(); i-- != 0; ) {
+ mSheets[i]->DropRuleProcessor(this);
+ }
+ 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<nsIAtom> >;
+ 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_MacYosemiteTheme, &metricResult);
+ if (NS_SUCCEEDED(rv) && metricResult) {
+ sSystemMetrics->AppendElement(nsGkAtoms::mac_yosemite_theme);
+ }
+
+ rv = LookAndFeel::GetInt(LookAndFeel::eIntID_DWMCompositor, &metricResult);
+ if (NS_SUCCEEDED(rv) && metricResult) {
+ sSystemMetrics->AppendElement(nsGkAtoms::windows_compositor);
+ }
+
+ rv = LookAndFeel::GetInt(LookAndFeel::eIntID_WindowsGlass, &metricResult);
+ if (NS_SUCCEEDED(rv) && metricResult) {
+ sSystemMetrics->AppendElement(nsGkAtoms::windows_glass);
+ }
+
+ rv = LookAndFeel::GetInt(LookAndFeel::eIntID_ColorPickerAvailable, &metricResult);
+ if (NS_SUCCEEDED(rv) && metricResult) {
+ sSystemMetrics->AppendElement(nsGkAtoms::color_picker_available);
+ }
+
+ rv = LookAndFeel::GetInt(LookAndFeel::eIntID_WindowsClassic, &metricResult);
+ if (NS_SUCCEEDED(rv) && metricResult) {
+ sSystemMetrics->AppendElement(nsGkAtoms::windows_classic);
+ }
+
+ rv = LookAndFeel::GetInt(LookAndFeel::eIntID_TouchEnabled, &metricResult);
+ if (NS_SUCCEEDED(rv) && metricResult) {
+ sSystemMetrics->AppendElement(nsGkAtoms::touch_enabled);
+ }
+
+ rv = LookAndFeel::GetInt(LookAndFeel::eIntID_SwipeAnimationEnabled,
+ &metricResult);
+ if (NS_SUCCEEDED(rv) && metricResult) {
+ sSystemMetrics->AppendElement(nsGkAtoms::swipe_animation_enabled);
+ }
+
+ rv = LookAndFeel::GetInt(LookAndFeel::eIntID_PhysicalHomeButton,
+ &metricResult);
+ if (NS_SUCCEEDED(rv) && metricResult) {
+ sSystemMetrics->AppendElement(nsGkAtoms::physical_home_button);
+ }
+
+#ifdef XP_WIN
+ if (NS_SUCCEEDED(
+ LookAndFeel::GetInt(LookAndFeel::eIntID_WindowsThemeIdentifier,
+ &metricResult))) {
+ nsCSSRuleProcessor::SetWindowsThemeIdentifier(static_cast<uint8_t>(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 SelectorListMatches, but we're starting
+ // from the middle of the selector list when doing
+ // Has{Attribute,State}DependentStyle, so we can't tell. So when
+ // mForStyling is false, we have to assume we don't know.)
+ const bool mIsRelevantLink;
+
+ NodeMatchContext(EventStates aStateMask, bool aIsRelevantLink)
+ : mStateMask(aStateMask)
+ , mIsRelevantLink(aIsRelevantLink)
+ {
+ }
+};
+
+/**
+ * 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
+};
+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() ||
+ // 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)) {
+ 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<const nsStringComparator&>(defaultComparator)
+ : static_cast<const nsStringComparator&>(ciComparator);
+
+ switch (aAttrSelector->mFunction) {
+ case NS_ATTR_FUNC_EQUALS:
+ return aValue.Equals(aAttrSelector->mValue, comparator);
+ case NS_ATTR_FUNC_INCLUDES:
+ return 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<int32_t> indexMinusB = CheckedInt<int32_t>(index) - b;
+ const CheckedInt<int32_t> 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<size_t>(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
+StateSelectorMatches(Element* aElement,
+ nsCSSSelector* aSelector,
+ NodeMatchContext& aNodeMatchContext,
+ TreeMatchContext& aTreeMatchContext,
+ SelectorMatchesFlags aSelectorFlags)
+{
+ for (nsPseudoClassList* pseudoClass = aSelector->mPseudoClassList;
+ pseudoClass; pseudoClass = pseudoClass->mNext) {
+ auto idx = static_cast<CSSPseudoClassTypeBase>(pseudoClass->mType);
+ EventStates statesToCheck = sPseudoClassStates[idx];
+ if (!statesToCheck.IsEmpty() &&
+ !StateSelectorMatches(aElement, aSelector, aNodeMatchContext,
+ aTreeMatchContext, aSelectorFlags, nullptr,
+ statesToCheck)) {
+ return false;
+ }
+ }
+ return true;
+}
+
+// |aDependence| has two functions:
+// * when non-null, it indicates that we're processing a negation,
+// which is done only when SelectorMatches calls itself recursively
+// * what it points to should be set to true whenever a test is skipped
+// because of aNodeMatchContent.mStateMask
+static bool SelectorMatches(Element* aElement,
+ nsCSSSelector* aSelector,
+ NodeMatchContext& aNodeMatchContext,
+ TreeMatchContext& aTreeMatchContext,
+ 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");
+
+ // namespace/tag match
+ // optimization : bail out early if we can
+ if ((kNameSpaceID_Unknown != aSelector->mNameSpace &&
+ aElement->GetNameSpaceID() != aSelector->mNameSpace))
+ return false;
+
+ if (aSelector->mLowercaseTag) {
+ nsIAtom* selectorTag =
+ (aTreeMatchContext.mIsHTMLDocument && aElement->IsHTMLElement()) ?
+ aSelector->mLowercaseTag : aSelector->mCasedTag;
+ if (selectorTag != aElement->NodeInfo()->NameAtom()) {
+ return false;
+ }
+ }
+
+ nsAtomList* IDList = aSelector->mIDList;
+ if (IDList) {
+ nsIAtom* id = aElement->GetID();
+ if (id) {
+ // case sensitivity: bug 93371
+ const bool isCaseSensitive =
+ aTreeMatchContext.mCompatMode != eCompatibility_NavQuirks;
+
+ if (isCaseSensitive) {
+ do {
+ if (IDList->mAtom != id) {
+ return false;
+ }
+ IDList = IDList->mNext;
+ } while (IDList);
+ } else {
+ // Use EqualsIgnoreASCIICase instead of full on unicode case conversion
+ // in order to save on performance. This is only used in quirks mode
+ // anyway.
+ nsDependentAtomString id1Str(id);
+ do {
+ if (!nsContentUtils::EqualsIgnoreASCIICase(id1Str,
+ nsDependentAtomString(IDList->mAtom))) {
+ return false;
+ }
+ IDList = IDList->mNext;
+ } while (IDList);
+ }
+ } else {
+ // Element has no id but we have an id selector
+ return false;
+ }
+ }
+
+ nsAtomList* classList = aSelector->mClassList;
+ if (classList) {
+ // test for class match
+ const nsAttrValue *elementClasses = aElement->GetClasses();
+ if (!elementClasses) {
+ // Element has no classes but we have a class selector
+ return false;
+ }
+
+ // case sensitivity: bug 93371
+ const bool isCaseSensitive =
+ aTreeMatchContext.mCompatMode != eCompatibility_NavQuirks;
+
+ while (classList) {
+ if (!elementClasses->Contains(classList->mAtom,
+ isCaseSensitive ?
+ eCaseMatters : eIgnoreCase)) {
+ return false;
+ }
+ classList = classList->mNext;
+ }
+ }
+
+ const bool isNegated = (aDependence != nullptr);
+ // The selectors for which we set node bits are, unfortunately, early
+ // in this function (because they're pseudo-classes, which are
+ // generally quick to test, and thus earlier). If they were later,
+ // we'd probably avoid setting those bits in more cases where setting
+ // them is unnecessary.
+ NS_ASSERTION(aNodeMatchContext.mStateMask.IsEmpty() ||
+ !aTreeMatchContext.mForStyling,
+ "mForStyling must be false if we're just testing for "
+ "state-dependence");
+
+ // test for pseudo class match
+ for (nsPseudoClassList* pseudoClass = aSelector->mPseudoClassList;
+ pseudoClass; pseudoClass = pseudoClass->mNext) {
+ auto idx = static_cast<CSSPseudoClassTypeBase>(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::any:
+ {
+ nsCSSSelectorList *l;
+ for (l = pseudoClass->u.mSelectors; l; l = l->mNext) {
+ nsCSSSelector *s = l->mSelectors;
+ MOZ_ASSERT(!s->mNext && !s->IsPseudoElement(),
+ "parser failed");
+ if (SelectorMatches(
+ aElement, s, aNodeMatchContext, aTreeMatchContext,
+ SelectorMatchesFlags::IS_PSEUDO_CLASS_ARGUMENT)) {
+ break;
+ }
+ }
+ if (!l) {
+ 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<nsIAtom> 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<nsIMozBrowserFrame>
+ browserFrame = do_QueryInterface(aElement);
+ if (!browserFrame ||
+ !browserFrame->GetReallyIsBrowserOrApp()) {
+ return false;
+ }
+ }
+ break;
+
+ case CSSPseudoClassType::mozDir:
+ case CSSPseudoClassType::dir:
+ {
+ if (aDependence) {
+ EventStates states = sPseudoClassStateDependences[
+ static_cast<CSSPseudoClassTypeBase>(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 (!aElement->HasAttrs()) {
+ // if no attributes on the content, no match
+ return false;
+ } else {
+ result = true;
+ nsAttrSelector* attr = aSelector->mAttrList;
+ nsIAtom* matchAttribute;
+
+ do {
+ bool isHTML =
+ (aTreeMatchContext.mIsHTMLDocument && aElement->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 = aElement->GetAttrNameAt(i)); ++i) {
+ if (attrName->LocalName() != matchAttribute) {
+ continue;
+ }
+ if (attr->mFunction == NS_ATTR_FUNC_SET) {
+ result = true;
+ } else {
+ nsAutoString value;
+#ifdef DEBUG
+ bool hasAttr =
+#endif
+ aElement->GetAttr(attrName->NamespaceID(),
+ attrName->LocalName(), value);
+ NS_ASSERTION(hasAttr, "GetAttrNameAt lied");
+ result = AttrMatchesValue(attr, value, isHTML);
+ }
+
+ // At this point |result| has been set by us
+ // explicitly in this loop. If it's false, we may still match
+ // -- the content may have another attribute with the same name but
+ // in a different namespace. But if it's true, we are done (we
+ // can short-circuit the boolean OR described above).
+ if (result) {
+ break;
+ }
+ }
+ }
+ else if (attr->mFunction == NS_ATTR_FUNC_EQUALS) {
+ result =
+ aElement->
+ AttrValueIs(attr->mNameSpace, matchAttribute, attr->mValue,
+ attr->IsValueCaseSensitive(isHTML) ? eCaseMatters
+ : eIgnoreCase);
+ }
+ else if (!aElement->HasAttr(attr->mNameSpace, matchAttribute)) {
+ result = false;
+ }
+ else if (attr->mFunction != NS_ATTR_FUNC_SET) {
+ nsAutoString value;
+#ifdef DEBUG
+ bool hasAttr =
+#endif
+ aElement->GetAttr(attr->mNameSpace, matchAttribute, value);
+ NS_ASSERTION(hasAttr, "HasAttr lied");
+ result = AttrMatchesValue(attr, value, isHTML);
+ }
+
+ attr = attr->mNext;
+ } while (attr && result);
+ }
+ }
+
+ // apply SelectorMatches to the negated selectors in the chain
+ if (!isNegated) {
+ for (nsCSSSelector *negation = aSelector->mNegations;
+ result && negation; negation = negation->mNegations) {
+ bool dependence = false;
+ result = !SelectorMatches(aElement, negation, aNodeMatchContext,
+ aTreeMatchContext,
+ 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.mSelectors; 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;
+ while (selector) { // check compound selectors
+ NS_ASSERTION(!selector->mNext ||
+ selector->mNext->mOperator != char16_t(0),
+ "compound selector without combinator");
+
+ // If after the previous selector match we are now outside the
+ // current style scope, we don't need to match any further.
+ if (aTreeMatchContext.mForScopedStyle &&
+ !aTreeMatchContext.IsWithinStyleScopeForSelectorMatching()) {
+ return false;
+ }
+
+ // for adjacent sibling combinators, the content to test against the
+ // selector is the previous sibling *element*
+ Element* element = nullptr;
+ if (char16_t('+') == selector->mOperator ||
+ char16_t('~') == selector->mOperator) {
+ // The relevant link must be an ancestor of the node being matched.
+ aFlags = SelectorMatchesTreeFlags(aFlags & ~eLookForRelevantLink);
+ nsIContent* parent = prevElement->GetParent();
+ if (parent) {
+ if (aTreeMatchContext.mForStyling)
+ parent->SetFlags(NODE_HAS_SLOW_SELECTOR_LATER_SIBLINGS);
+
+ element = prevElement->GetPreviousElementSibling();
+ }
+ }
+ // for descendant combinators and child combinators, the element
+ // to test against is the parent
+ else {
+ nsIContent *content = prevElement->GetParent();
+ // GetParent could return a document fragment; we only want
+ // element parents.
+ if (content && content->IsElement()) {
+ element = content->AsElement();
+ if (aTreeMatchContext.mForScopedStyle) {
+ // We are moving up to the parent element; tell the
+ // TreeMatchContext, so that in case this element is the
+ // style scope element, selector matching stops before we
+ // traverse further up the tree.
+ aTreeMatchContext.PopStyleScopeForSelectorMatching(element);
+ }
+
+ // Compatibility hack: First try matching this selector as though the
+ // <xbl:children> element wasn't in the tree to allow old selectors
+ // were written before <xbl:children> 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 <xbl:children> 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);
+ 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;
+ }
+ else {
+ // for adjacent sibling and child combinators, if we didn't find
+ // a match, we're done
+ if (!NS_IS_GREEDY_OPERATOR(selector->mOperator)) {
+ return false; // parent was required to match
+ }
+ }
+ prevElement = element;
+ }
+ return true; // all the selectors matched.
+}
+
+static inline
+void ContentEnumFunc(const RuleValue& value, nsCSSSelector* aSelector,
+ ElementDependentRuleProcessorData* data, NodeMatchContext& nodeContext,
+ AncestorFilter *ancestorFilter)
+{
+ if (nodeContext.mIsRelevantLink) {
+ data->mTreeMatchContext.SetHaveRelevantLink();
+ }
+ if (ancestorFilter &&
+ !ancestorFilter->MightHaveMatchingAncestor<RuleValue::eMaxAncestorHashes>(
+ value.mAncestorSelectorHashes)) {
+ // We won't match; nothing else to do here
+ return;
+ }
+ if (!data->mTreeMatchContext.SetStyleScopeForSelectorMatching(data->mElement,
+ data->mScope)) {
+ // The selector is for a rule in a scoped style sheet, and the subject
+ // of the selector matching is not in its scope.
+ return;
+ }
+ nsCSSSelector* selector = aSelector;
+ if (selector->IsPseudoElement()) {
+ PseudoElementRuleProcessorData* pdata =
+ static_cast<PseudoElementRuleProcessorData*>(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)
+{
+ RuleCascadeData* cascade = GetRuleCascade(aData->mPresContext);
+
+ if (cascade) {
+ NodeMatchContext nodeContext(EventStates(),
+ nsCSSRuleProcessor::IsLink(aData->mElement));
+ cascade->mRuleHash.EnumerateAllRules(aData->mElement, aData, nodeContext);
+ }
+}
+
+/* virtual */ void
+nsCSSRuleProcessor::RulesMatching(PseudoElementRuleProcessorData* aData)
+{
+ RuleCascadeData* cascade = GetRuleCascade(aData->mPresContext);
+
+ if (cascade) {
+ RuleHash* ruleHash = cascade->mPseudoElementRuleHashes[
+ static_cast<CSSPseudoElementTypeBase>(aData->mPseudoType)];
+ if (ruleHash) {
+ NodeMatchContext nodeContext(EventStates(),
+ nsCSSRuleProcessor::IsLink(aData->mElement));
+ ruleHash->EnumerateAllRules(aData->mElement, aData, nodeContext);
+ }
+ }
+}
+
+/* virtual */ void
+nsCSSRuleProcessor::RulesMatching(AnonBoxRuleProcessorData* aData)
+{
+ RuleCascadeData* cascade = GetRuleCascade(aData->mPresContext);
+
+ if (cascade && cascade->mAnonBoxRules.EntryCount()) {
+ auto entry = static_cast<RuleHashTagTableEntry*>
+ (cascade->mAnonBoxRules.Search(aData->mPseudoTag));
+ if (entry) {
+ nsTArray<RuleValue>& 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);
+ }
+ }
+ }
+}
+
+#ifdef MOZ_XUL
+/* virtual */ void
+nsCSSRuleProcessor::RulesMatching(XULTreeRuleProcessorData* aData)
+{
+ RuleCascadeData* cascade = GetRuleCascade(aData->mPresContext);
+
+ if (cascade && cascade->mXULTreeRules.EntryCount()) {
+ auto entry = static_cast<RuleHashTagTableEntry*>
+ (cascade->mXULTreeRules.Search(aData->mPseudoTag));
+ if (entry) {
+ NodeMatchContext nodeContext(EventStates(),
+ nsCSSRuleProcessor::IsLink(aData->mElement));
+ nsTArray<RuleValue>& rules = entry->mRules;
+ for (RuleValue *value = rules.Elements(), *end = value + rules.Length();
+ value != end; ++value) {
+ if (aData->mComparator->PseudoMatches(value->mSelector)) {
+ ContentEnumFunc(*value, value->mSelector->mNext, aData, nodeContext,
+ nullptr);
+ }
+ }
+ }
+ }
+}
+#endif
+
+static inline nsRestyleHint RestyleHintForOp(char16_t oper)
+{
+ if (oper == char16_t('+') || oper == char16_t('~')) {
+ return eRestyle_LaterSiblings;
+ }
+
+ if (oper != char16_t(0)) {
+ return eRestyle_Subtree;
+ }
+
+ return eRestyle_Self;
+}
+
+nsRestyleHint
+nsCSSRuleProcessor::HasStateDependentStyle(ElementDependentRuleProcessorData* aData,
+ Element* aStatefulElement,
+ CSSPseudoElementType aPseudoType,
+ EventStates aStateMask)
+{
+ MOZ_ASSERT(!aData->mTreeMatchContext.mForScopedStyle,
+ "mCurrentStyleScope will need to be saved and restored after the "
+ "SelectorMatchesTree call");
+
+ bool isPseudoElement =
+ aPseudoType != CSSPseudoElementType::NotPseudo;
+
+ RuleCascadeData* cascade = GetRuleCascade(aData->mPresContext);
+
+ // Look up the content node in the state rule list, which points to
+ // any (CSS2 definition) simple selector (whether or not it is the
+ // subject) that has a state pseudo-class on it. This means that this
+ // code will be matching selectors that aren't real selectors in any
+ // stylesheet (e.g., if there is a selector "body > p:hover > a", then
+ // "body > p:hover" will be in |cascade->mStateSelectors|). Note that
+ // |ComputeSelectorStateDependence| below determines which selectors are in
+ // |cascade->mStateSelectors|.
+ nsRestyleHint hint = nsRestyleHint(0);
+ if (cascade) {
+ StateSelector *iter = cascade->mStateSelectors.Elements(),
+ *end = iter + cascade->mStateSelectors.Length();
+ NodeMatchContext nodeContext(aStateMask, false);
+ for(; iter != end; ++iter) {
+ nsCSSSelector* selector = iter->mSelector;
+ EventStates states = iter->mStates;
+
+ if (selector->IsPseudoElement() != isPseudoElement) {
+ continue;
+ }
+
+ nsCSSSelector* selectorForPseudo;
+ if (isPseudoElement) {
+ if (selector->PseudoType() != aPseudoType) {
+ continue;
+ }
+ selectorForPseudo = selector;
+ selector = selector->mNext;
+ }
+
+ nsRestyleHint possibleChange = RestyleHintForOp(selector->mOperator);
+ 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);
+ }
+ }
+ }
+ return hint;
+}
+
+nsRestyleHint
+nsCSSRuleProcessor::HasStateDependentStyle(StateRuleProcessorData* aData)
+{
+ return HasStateDependentStyle(aData,
+ aData->mElement,
+ CSSPseudoElementType::NotPseudo,
+ aData->mStateMask);
+}
+
+nsRestyleHint
+nsCSSRuleProcessor::HasStateDependentStyle(PseudoElementStateRuleProcessorData* aData)
+{
+ return HasStateDependentStyle(aData,
+ aData->mPseudoElement,
+ aData->mPseudoType,
+ aData->mStateMask);
+}
+
+bool
+nsCSSRuleProcessor::HasDocumentStateDependentStyle(StateRuleProcessorData* aData)
+{
+ RuleCascadeData* cascade = GetRuleCascade(aData->mPresContext);
+
+ return cascade && cascade->mSelectorDocumentStates.HasAtLeastOneOfStates(aData->mStateMask);
+}
+
+struct AttributeEnumData {
+ AttributeEnumData(AttributeRuleProcessorData *aData,
+ 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, 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<SelectorPair>& 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<nsCSSSelector*>& aSelectors, AttributeEnumData* aData)
+{
+ nsCSSSelector **iter = aSelectors.Elements(),
+ **end = iter + aSelectors.Length();
+ for (; iter != end; ++iter) {
+ AttributeEnumFunc(*iter, nullptr, aData);
+ }
+}
+
+nsRestyleHint
+nsCSSRuleProcessor::HasAttributeDependentStyle(
+ AttributeRuleProcessorData* aData,
+ RestyleHintData& aRestyleHintDataResult)
+{
+ // We could try making use of aData->mModType, but :not rules make it a bit
+ // of a pain to do so... So just ignore it for now.
+
+ AttributeEnumData data(aData, aRestyleHintDataResult);
+
+ // Don't do our special handling of certain attributes if the attr
+ // hasn't changed yet.
+ if (aData->mAttrHasChanged) {
+ // check for the lwtheme and lwthemetextcolor attribute on root XUL elements
+ if ((aData->mAttribute == nsGkAtoms::lwtheme ||
+ aData->mAttribute == nsGkAtoms::lwthemetextcolor) &&
+ aData->mElement->GetNameSpaceID() == kNameSpaceID_XUL &&
+ aData->mElement == aData->mElement->OwnerDoc()->GetRootElement())
+ {
+ data.change = nsRestyleHint(data.change | eRestyle_Subtree);
+ }
+
+ // We don't know the namespace of the attribute, and xml:lang applies to
+ // all elements. If the lang attribute changes, we need to restyle our
+ // whole subtree, since the :lang selector on our descendants can examine
+ // our lang attribute.
+ if (aData->mAttribute == nsGkAtoms::lang) {
+ data.change = nsRestyleHint(data.change | eRestyle_Subtree);
+ }
+ }
+
+ RuleCascadeData* cascade = GetRuleCascade(aData->mPresContext);
+
+ // Since we get both before and after notifications for attributes, we
+ // don't have to ignore aData->mAttribute while matching. Just check
+ // whether we have selectors relevant to aData->mAttribute that we
+ // match. If this is the before change notification, that will catch
+ // rules we might stop matching; if the after change notification, the
+ // ones we might have started matching.
+ if (cascade) {
+ if (aData->mAttribute == nsGkAtoms::id) {
+ nsIAtom* id = aData->mElement->GetID();
+ if (id) {
+ auto entry =
+ static_cast<AtomSelectorEntry*>(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<nsPtrHashKey<nsIAtom>> 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<AtomSelectorEntry*>
+ (cascade->mClassSelectors.Search(curClass));
+ if (entry) {
+ EnumerateSelectors(entry->mSelectors, &data);
+ }
+ }
+ }
+ }
+ }
+
+ EnumerateSelectors(cascade->mPossiblyNegatedClassSelectors, &data);
+ }
+
+ auto entry =
+ static_cast<AtomSelectorEntry*>
+ (cascade->mAttributeSelectors.Search(aData->mAttribute));
+ if (entry) {
+ EnumerateSelectors(entry->mSelectors, &data);
+ }
+ }
+
+ return data.change;
+}
+
+/* virtual */ bool
+nsCSSRuleProcessor::MediumFeaturesChanged(nsPresContext* aPresContext)
+{
+ // We don't want to do anything if there aren't any sets of rules
+ // cached yet, since we should not build the rule cascade too early
+ // (e.g., before we know whether the quirk style sheet should be
+ // enabled). And if there's nothing cached, it doesn't matter if
+ // anything changed. But in the cases where it does matter, we've
+ // cached a previous cache key to test against, instead of our current
+ // rule cascades. See bug 448281 and bug 1089417.
+ MOZ_ASSERT(!(mRuleCascades && mPreviousCacheKey));
+ RuleCascadeData *old = mRuleCascades;
+ if (old) {
+ RefreshRuleCascade(aPresContext);
+ return (old != mRuleCascades);
+ }
+
+ if (mPreviousCacheKey) {
+ // RefreshRuleCascade will get rid of mPreviousCacheKey anyway to
+ // maintain the invariant that we can't have both an mRuleCascades
+ // and an mPreviousCacheKey. But we need to hold it a little
+ // longer.
+ UniquePtr<nsMediaQueryResultCacheKey> previousCacheKey(
+ Move(mPreviousCacheKey));
+ RefreshRuleCascade(aPresContext);
+
+ // This test is a bit pessimistic since the cache key's operator==
+ // just does list comparison rather than set comparison, but it
+ // should catch all the cases we care about (i.e., where the cascade
+ // order hasn't changed). Other cases will do a restyle anyway, so
+ // we shouldn't need to worry about posting a second.
+ return !mRuleCascades || // all sheets gone, but we had sheets before
+ mRuleCascades->mCacheKey != *previousCacheKey;
+ }
+
+ return false;
+}
+
+UniquePtr<nsMediaQueryResultCacheKey>
+nsCSSRuleProcessor::CloneMQCacheKey()
+{
+ MOZ_ASSERT(!(mRuleCascades && mPreviousCacheKey));
+
+ RuleCascadeData* c = mRuleCascades;
+ if (!c) {
+ // We might have an mPreviousCacheKey. It already comes from a call
+ // to CloneMQCacheKey, so don't bother checking
+ // HasFeatureConditions().
+ if (mPreviousCacheKey) {
+ NS_ASSERTION(mPreviousCacheKey->HasFeatureConditions(),
+ "we shouldn't have a previous cache key unless it has "
+ "feature conditions");
+ return MakeUnique<nsMediaQueryResultCacheKey>(*mPreviousCacheKey);
+ }
+
+ return UniquePtr<nsMediaQueryResultCacheKey>();
+ }
+
+ if (!c->mCacheKey.HasFeatureConditions()) {
+ return UniquePtr<nsMediaQueryResultCacheKey>();
+ }
+
+ return MakeUnique<nsMediaQueryResultCacheKey>(c->mCacheKey);
+}
+
+/* virtual */ size_t
+nsCSSRuleProcessor::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
+{
+ size_t n = 0;
+ n += mSheets.ShallowSizeOfExcludingThis(aMallocSizeOf);
+ for (RuleCascadeData* cascade = mRuleCascades; cascade;
+ cascade = cascade->mNext) {
+ n += cascade->SizeOfIncludingThis(aMallocSizeOf);
+ }
+
+ return n;
+}
+
+/* virtual */ size_t
+nsCSSRuleProcessor::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const
+{
+ return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+}
+
+// Append all the currently-active font face rules to aArray. Return
+// true for success and false for failure.
+bool
+nsCSSRuleProcessor::AppendFontFaceRules(
+ nsPresContext *aPresContext,
+ nsTArray<nsFontFaceRuleContainer>& aArray)
+{
+ RuleCascadeData* cascade = GetRuleCascade(aPresContext);
+
+ if (cascade) {
+ if (!aArray.AppendElements(cascade->mFontFaceRules))
+ return false;
+ }
+
+ return true;
+}
+
+nsCSSKeyframesRule*
+nsCSSRuleProcessor::KeyframesRuleForName(nsPresContext* aPresContext,
+ const nsString& aName)
+{
+ RuleCascadeData* cascade = GetRuleCascade(aPresContext);
+
+ if (cascade) {
+ return cascade->mKeyframesRuleTable.Get(aName);
+ }
+
+ return nullptr;
+}
+
+nsCSSCounterStyleRule*
+nsCSSRuleProcessor::CounterStyleRuleForName(nsPresContext* aPresContext,
+ const nsAString& aName)
+{
+ RuleCascadeData* cascade = GetRuleCascade(aPresContext);
+
+ if (cascade) {
+ return cascade->mCounterStyleRuleTable.Get(aName);
+ }
+
+ return nullptr;
+}
+
+// Append all the currently-active page rules to aArray. Return
+// true for success and false for failure.
+bool
+nsCSSRuleProcessor::AppendPageRules(
+ nsPresContext* aPresContext,
+ nsTArray<nsCSSPageRule*>& aArray)
+{
+ RuleCascadeData* cascade = GetRuleCascade(aPresContext);
+
+ if (cascade) {
+ if (!aArray.AppendElements(cascade->mPageRules)) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+bool
+nsCSSRuleProcessor::AppendFontFeatureValuesRules(
+ nsPresContext *aPresContext,
+ nsTArray<nsCSSFontFeatureValuesRule*>& aArray)
+{
+ RuleCascadeData* cascade = GetRuleCascade(aPresContext);
+
+ if (cascade) {
+ if (!aArray.AppendElements(cascade->mFontFeatureValuesRules))
+ return false;
+ }
+
+ return true;
+}
+
+nsresult
+nsCSSRuleProcessor::ClearRuleCascades()
+{
+ if (!mPreviousCacheKey) {
+ mPreviousCacheKey = CloneMQCacheKey();
+ }
+
+ // No need to remove the rule processor from the RuleProcessorCache here,
+ // since CSSStyleSheet::ClearRuleCascades will have called
+ // RuleProcessorCache::RemoveSheet() passing itself, which will catch
+ // this rule processor (and any others for different @-moz-document
+ // cache key results).
+ MOZ_ASSERT(!RuleProcessorCache::HasRuleProcessor(this));
+
+#ifdef DEBUG
+ // For shared rule processors, if we've already gathered document
+ // rules, then they will now be out of date. We don't actually need
+ // them to be up-to-date (see the comment in RefreshRuleCascade), so
+ // record their invalidity so we can assert if we try to use them.
+ if (!mMustGatherDocumentRules) {
+ mDocumentRulesAndCacheKeyValid = false;
+ }
+#endif
+
+ // We rely on our caller (perhaps indirectly) to do something that
+ // will rebuild style data and the user font set (either
+ // nsIPresShell::RestyleForCSSRuleChanges or
+ // nsPresContext::RebuildAllStyleData).
+ RuleCascadeData *data = mRuleCascades;
+ mRuleCascades = nullptr;
+ while (data) {
+ RuleCascadeData *next = data->mNext;
+ delete data;
+ data = next;
+ }
+ return NS_OK;
+}
+
+
+// This function should return the set of states that this selector
+// depends on; this is used to implement HasStateDependentStyle. It
+// does NOT recur down into things like :not and :-moz-any.
+inline
+EventStates ComputeSelectorStateDependence(nsCSSSelector& aSelector)
+{
+ EventStates states;
+ for (nsPseudoClassList* pseudoClass = aSelector.mPseudoClassList;
+ pseudoClass; pseudoClass = pseudoClass->mNext) {
+ // Tree pseudo-elements overload mPseudoClassList for things that
+ // aren't pseudo-classes.
+ if (pseudoClass->mType >= CSSPseudoClassType::Count) {
+ continue;
+ }
+
+ auto idx = static_cast<CSSPseudoClassTypeBase>(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<SelectorPair> *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<AtomSelectorEntry*>
+ (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<AtomSelectorEntry*>
+ (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<SelectorPair> *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 :-moz-any selectors
+ for (nsPseudoClassList* pseudoClass = negation->mPseudoClassList;
+ pseudoClass; pseudoClass = pseudoClass->mNext) {
+ if (pseudoClass->mType == CSSPseudoClassType::any) {
+ for (nsCSSSelectorList *l = pseudoClass->u.mSelectors; 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<CSSPseudoElementTypeBase>(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<RuleByWeightEntry*>(hdr);
+ new (KnownNotNull, entry) RuleByWeightEntry();
+}
+
+static const PLDHashTableOps gRulesByWeightOps = {
+ HashIntKey,
+ MatchWeightEntry,
+ PLDHashTable::MoveEntryStub,
+ PLDHashTable::ClearEntryStub,
+ InitWeightEntry
+};
+
+struct CascadeEnumData {
+ CascadeEnumData(nsPresContext* aPresContext,
+ nsTArray<nsFontFaceRuleContainer>& aFontFaceRules,
+ nsTArray<nsCSSKeyframesRule*>& aKeyframesRules,
+ nsTArray<nsCSSFontFeatureValuesRule*>& aFontFeatureValuesRules,
+ nsTArray<nsCSSPageRule*>& aPageRules,
+ nsTArray<nsCSSCounterStyleRule*>& aCounterStyleRules,
+ nsTArray<css::DocumentRule*>& aDocumentRules,
+ nsMediaQueryResultCacheKey& aKey,
+ nsDocumentRuleResultCacheKey& aDocumentKey,
+ SheetType aSheetType,
+ bool aMustGatherDocumentRules)
+ : mPresContext(aPresContext),
+ mFontFaceRules(aFontFaceRules),
+ mKeyframesRules(aKeyframesRules),
+ mFontFeatureValuesRules(aFontFeatureValuesRules),
+ mPageRules(aPageRules),
+ mCounterStyleRules(aCounterStyleRules),
+ mDocumentRules(aDocumentRules),
+ mCacheKey(aKey),
+ mDocumentCacheKey(aDocumentKey),
+ mRulesByWeight(&gRulesByWeightOps, sizeof(RuleByWeightEntry), 32),
+ mSheetType(aSheetType),
+ mMustGatherDocumentRules(aMustGatherDocumentRules)
+ {
+ // Initialize our arena
+ PL_INIT_ARENA_POOL(&mArena, "CascadeEnumDataArena",
+ NS_CASCADEENUMDATA_ARENA_BLOCK_SIZE);
+ }
+
+ ~CascadeEnumData()
+ {
+ PL_FinishArenaPool(&mArena);
+ }
+
+ nsPresContext* mPresContext;
+ nsTArray<nsFontFaceRuleContainer>& mFontFaceRules;
+ nsTArray<nsCSSKeyframesRule*>& mKeyframesRules;
+ nsTArray<nsCSSFontFeatureValuesRule*>& mFontFeatureValuesRules;
+ nsTArray<nsCSSPageRule*>& mPageRules;
+ nsTArray<nsCSSCounterStyleRule*>& mCounterStyleRules;
+ nsTArray<css::DocumentRule*>& mDocumentRules;
+ nsMediaQueryResultCacheKey& mCacheKey;
+ nsDocumentRuleResultCacheKey& mDocumentCacheKey;
+ 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
+ SheetType mSheetType;
+ bool mMustGatherDocumentRules;
+};
+
+/**
+ * Recursively traverses rules in order to:
+ * (1) add any @-moz-document rules into data->mDocumentRules.
+ * (2) record any @-moz-document rules whose conditions evaluate to true
+ * on data->mDocumentCacheKey.
+ *
+ * See also CascadeRuleEnumFunc below, which calls us via
+ * EnumerateRulesForwards. If modifying this function you may need to
+ * update CascadeRuleEnumFunc too.
+ */
+static bool
+GatherDocRuleEnumFunc(css::Rule* aRule, void* aData)
+{
+ CascadeEnumData* data = (CascadeEnumData*)aData;
+ int32_t type = aRule->GetType();
+
+ MOZ_ASSERT(data->mMustGatherDocumentRules,
+ "should only call GatherDocRuleEnumFunc if "
+ "mMustGatherDocumentRules is true");
+
+ if (css::Rule::MEDIA_RULE == type ||
+ css::Rule::SUPPORTS_RULE == type) {
+ css::GroupRule* groupRule = static_cast<css::GroupRule*>(aRule);
+ if (!groupRule->EnumerateRulesForwards(GatherDocRuleEnumFunc, aData)) {
+ return false;
+ }
+ }
+ else if (css::Rule::DOCUMENT_RULE == type) {
+ css::DocumentRule* docRule = static_cast<css::DocumentRule*>(aRule);
+ if (!data->mDocumentRules.AppendElement(docRule)) {
+ return false;
+ }
+ if (docRule->UseForPresentation(data->mPresContext)) {
+ if (!data->mDocumentCacheKey.AddMatchingRule(docRule)) {
+ return false;
+ }
+ }
+ if (!docRule->EnumerateRulesForwards(GatherDocRuleEnumFunc, aData)) {
+ return false;
+ }
+ }
+ return true;
+}
+
+/*
+ * This enumerates style rules in a sheet (and recursively into any
+ * grouping rules) in order to:
+ * (1) add any style rules, in order, into data->mRulesByWeight (for
+ * the primary CSS cascade), where they are separated by weight
+ * but kept in order per-weight, and
+ * (2) add any @font-face rules, in order, into data->mFontFaceRules.
+ * (3) add any @keyframes rules, in order, into data->mKeyframesRules.
+ * (4) add any @font-feature-value rules, in order,
+ * into data->mFontFeatureValuesRules.
+ * (5) add any @page rules, in order, into data->mPageRules.
+ * (6) add any @counter-style rules, in order, into data->mCounterStyleRules.
+ * (7) add any @-moz-document rules into data->mDocumentRules.
+ * (8) record any @-moz-document rules whose conditions evaluate to true
+ * on data->mDocumentCacheKey.
+ *
+ * See also GatherDocRuleEnumFunc above, which we call to traverse into
+ * @-moz-document rules even if their (or an ancestor's) condition
+ * fails. This means we might look at the result of some @-moz-document
+ * rules that don't actually affect whether a RuleProcessorCache lookup
+ * is a hit or a miss. The presence of @-moz-document rules inside
+ * @media etc. rules should be rare, and looking at all of them in the
+ * sheets lets us avoid the complication of having different document
+ * cache key results for different media.
+ *
+ * If modifying this function you may need to update
+ * GatherDocRuleEnumFunc too.
+ */
+static bool
+CascadeRuleEnumFunc(css::Rule* aRule, void* aData)
+{
+ CascadeEnumData* data = (CascadeEnumData*)aData;
+ int32_t type = aRule->GetType();
+
+ if (css::Rule::STYLE_RULE == type) {
+ css::StyleRule* styleRule = static_cast<css::StyleRule*>(aRule);
+
+ for (nsCSSSelectorList *sel = styleRule->Selector();
+ sel; sel = sel->mNext) {
+ int32_t weight = sel->mWeight;
+ auto entry = static_cast<RuleByWeightEntry*>
+ (data->mRulesByWeight.Add(NS_INT32_TO_PTR(weight), fallible));
+ if (!entry)
+ return false;
+ entry->data.mWeight = weight;
+ // entry->data.mRuleSelectorPairs should be linked in forward order;
+ // entry->data.mTail is the slot to write to.
+ auto* newItem =
+ new (data->mArena) PerWeightDataListItem(styleRule, sel->mSelectors);
+ if (newItem) {
+ *(entry->data.mTail) = newItem;
+ entry->data.mTail = &newItem->mNext;
+ }
+ }
+ }
+ else if (css::Rule::MEDIA_RULE == type ||
+ css::Rule::SUPPORTS_RULE == type) {
+ css::GroupRule* groupRule = static_cast<css::GroupRule*>(aRule);
+ const bool use =
+ groupRule->UseForPresentation(data->mPresContext, data->mCacheKey);
+ if (use || data->mMustGatherDocumentRules) {
+ if (!groupRule->EnumerateRulesForwards(use ? CascadeRuleEnumFunc :
+ GatherDocRuleEnumFunc,
+ aData)) {
+ return false;
+ }
+ }
+ }
+ else if (css::Rule::DOCUMENT_RULE == type) {
+ css::DocumentRule* docRule = static_cast<css::DocumentRule*>(aRule);
+ if (data->mMustGatherDocumentRules) {
+ if (!data->mDocumentRules.AppendElement(docRule)) {
+ return false;
+ }
+ }
+ const bool use = docRule->UseForPresentation(data->mPresContext);
+ if (use && data->mMustGatherDocumentRules) {
+ if (!data->mDocumentCacheKey.AddMatchingRule(docRule)) {
+ return false;
+ }
+ }
+ if (use || data->mMustGatherDocumentRules) {
+ if (!docRule->EnumerateRulesForwards(use ? CascadeRuleEnumFunc
+ : GatherDocRuleEnumFunc,
+ aData)) {
+ return false;
+ }
+ }
+ }
+ else if (css::Rule::FONT_FACE_RULE == type) {
+ nsCSSFontFaceRule *fontFaceRule = static_cast<nsCSSFontFaceRule*>(aRule);
+ nsFontFaceRuleContainer *ptr = data->mFontFaceRules.AppendElement();
+ if (!ptr)
+ return false;
+ ptr->mRule = fontFaceRule;
+ ptr->mSheetType = data->mSheetType;
+ }
+ else if (css::Rule::KEYFRAMES_RULE == type) {
+ nsCSSKeyframesRule *keyframesRule =
+ static_cast<nsCSSKeyframesRule*>(aRule);
+ if (!data->mKeyframesRules.AppendElement(keyframesRule)) {
+ return false;
+ }
+ }
+ else if (css::Rule::FONT_FEATURE_VALUES_RULE == type) {
+ nsCSSFontFeatureValuesRule *fontFeatureValuesRule =
+ static_cast<nsCSSFontFeatureValuesRule*>(aRule);
+ if (!data->mFontFeatureValuesRules.AppendElement(fontFeatureValuesRule)) {
+ return false;
+ }
+ }
+ else if (css::Rule::PAGE_RULE == type) {
+ nsCSSPageRule* pageRule = static_cast<nsCSSPageRule*>(aRule);
+ if (!data->mPageRules.AppendElement(pageRule)) {
+ return false;
+ }
+ }
+ else if (css::Rule::COUNTER_STYLE_RULE == type) {
+ nsCSSCounterStyleRule* counterStyleRule =
+ static_cast<nsCSSCounterStyleRule*>(aRule);
+ if (!data->mCounterStyleRules.AppendElement(counterStyleRule)) {
+ return false;
+ }
+ }
+ return true;
+}
+
+/* static */ bool
+nsCSSRuleProcessor::CascadeSheet(CSSStyleSheet* aSheet, CascadeEnumData* aData)
+{
+ if (aSheet->IsApplicable() &&
+ aSheet->UseForPresentation(aData->mPresContext, aData->mCacheKey) &&
+ aSheet->mInner) {
+ CSSStyleSheet* child = aSheet->mInner->mFirstChild;
+ while (child) {
+ CascadeSheet(child, aData);
+ child = child->mNext;
+ }
+
+ if (!aSheet->mInner->mOrderedRules.EnumerateForwards(CascadeRuleEnumFunc,
+ aData))
+ return false;
+ }
+ return true;
+}
+
+static int CompareWeightData(const void* aArg1, const void* aArg2,
+ void* closure)
+{
+ const PerWeightData* arg1 = static_cast<const PerWeightData*>(aArg1);
+ const PerWeightData* arg2 = static_cast<const PerWeightData*>(aArg2);
+ return arg1->mWeight - arg2->mWeight; // put lower weight first
+}
+
+RuleCascadeData*
+nsCSSRuleProcessor::GetRuleCascade(nsPresContext* aPresContext)
+{
+ // FIXME: Make this infallible!
+
+ // If anything changes about the presentation context, we will be
+ // notified. Otherwise, our cache is valid if mLastPresContext
+ // matches aPresContext. (The only rule processors used for multiple
+ // pres contexts are for XBL. These rule processors are probably less
+ // likely to have @media rules, and thus the cache is pretty likely to
+ // hit instantly even when we're switching between pres contexts.)
+
+ if (!mRuleCascades || aPresContext != mLastPresContext) {
+ RefreshRuleCascade(aPresContext);
+ }
+ mLastPresContext = aPresContext;
+
+ return mRuleCascades;
+}
+
+void
+nsCSSRuleProcessor::RefreshRuleCascade(nsPresContext* aPresContext)
+{
+ // Having RuleCascadeData objects be per-medium (over all variation
+ // caused by media queries, handled through mCacheKey) works for now
+ // since nsCSSRuleProcessor objects are per-document. (For a given
+ // set of stylesheets they can vary based on medium (@media) or
+ // document (@-moz-document).)
+
+ for (RuleCascadeData **cascadep = &mRuleCascades, *cascade;
+ (cascade = *cascadep); cascadep = &cascade->mNext) {
+ if (cascade->mCacheKey.Matches(aPresContext)) {
+ // Ensure that the current one is always mRuleCascades.
+ *cascadep = cascade->mNext;
+ cascade->mNext = mRuleCascades;
+ mRuleCascades = cascade;
+
+ return;
+ }
+ }
+
+ // We're going to make a new rule cascade; this means that we should
+ // now stop using the previous cache key that we're holding on to from
+ // the last time we had rule cascades.
+ mPreviousCacheKey = nullptr;
+
+ if (mSheets.Length() != 0) {
+ nsAutoPtr<RuleCascadeData> newCascade(
+ new RuleCascadeData(aPresContext->Medium(),
+ eCompatibility_NavQuirks == aPresContext->CompatibilityMode()));
+ if (newCascade) {
+ CascadeEnumData data(aPresContext, newCascade->mFontFaceRules,
+ newCascade->mKeyframesRules,
+ newCascade->mFontFeatureValuesRules,
+ newCascade->mPageRules,
+ newCascade->mCounterStyleRules,
+ mDocumentRules,
+ newCascade->mCacheKey,
+ mDocumentCacheKey,
+ mSheetType,
+ mMustGatherDocumentRules);
+
+ for (uint32_t i = 0; i < mSheets.Length(); ++i) {
+ if (!CascadeSheet(mSheets.ElementAt(i), &data))
+ return; /* out of memory */
+ }
+
+ // Sort the hash table of per-weight linked lists by weight.
+ uint32_t weightCount = data.mRulesByWeight.EntryCount();
+ auto weightArray = MakeUnique<PerWeightData[]>(weightCount);
+ int32_t j = 0;
+ for (auto iter = data.mRulesByWeight.Iter(); !iter.Done(); iter.Next()) {
+ auto entry = static_cast<const RuleByWeightEntry*>(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, newCascade))
+ return; /* out of memory */
+ }
+ }
+
+ // Build mKeyframesRuleTable.
+ for (nsTArray<nsCSSKeyframesRule*>::size_type i = 0,
+ iEnd = newCascade->mKeyframesRules.Length(); i < iEnd; ++i) {
+ nsCSSKeyframesRule* rule = newCascade->mKeyframesRules[i];
+ newCascade->mKeyframesRuleTable.Put(rule->GetName(), rule);
+ }
+
+ // Build mCounterStyleRuleTable
+ for (nsTArray<nsCSSCounterStyleRule*>::size_type i = 0,
+ iEnd = newCascade->mCounterStyleRules.Length(); i < iEnd; ++i) {
+ nsCSSCounterStyleRule* rule = newCascade->mCounterStyleRules[i];
+ newCascade->mCounterStyleRuleTable.Put(rule->GetName(), rule);
+ }
+
+ // mMustGatherDocumentRules controls whether we build mDocumentRules
+ // and mDocumentCacheKey so that they can be used as keys by the
+ // RuleProcessorCache, as obtained by TakeDocumentRulesAndCacheKey
+ // later. We set it to false just below so that we only do this
+ // the first time we build a RuleProcessorCache for a shared rule
+ // processor.
+ //
+ // An up-to-date mDocumentCacheKey is only needed if we
+ // are still in the RuleProcessorCache (as we store a copy of the
+ // cache key in the RuleProcessorCache), and an up-to-date
+ // mDocumentRules is only needed at the time TakeDocumentRulesAndCacheKey
+ // is called, which is immediately after the rule processor is created
+ // (by nsStyleSet).
+ //
+ // Note that when nsCSSRuleProcessor::ClearRuleCascades is called,
+ // by CSSStyleSheet::ClearRuleCascades, we will have called
+ // RuleProcessorCache::RemoveSheet, which will remove the rule
+ // processor from the cache. (This is because the list of document
+ // rules now may not match the one used as they key in the
+ // RuleProcessorCache.)
+ //
+ // Thus, as we'll no longer be in the RuleProcessorCache, and we won't
+ // have TakeDocumentRulesAndCacheKey called on us, we don't need to ensure
+ // mDocumentCacheKey and mDocumentRules are up-to-date after the
+ // first time GetRuleCascade is called.
+ if (mMustGatherDocumentRules) {
+ mDocumentRules.Sort();
+ mDocumentCacheKey.Finalize();
+ mMustGatherDocumentRules = false;
+#ifdef DEBUG
+ mDocumentRulesAndCacheKeyValid = true;
+#endif
+ }
+
+ // Ensure that the current one is always mRuleCascades.
+ newCascade->mNext = mRuleCascades;
+ mRuleCascades = newCascade.forget();
+ }
+ }
+ return;
+}
+
+/* static */ bool
+nsCSSRuleProcessor::SelectorListMatches(Element* aElement,
+ TreeMatchContext& aTreeMatchContext,
+ nsCSSSelectorList* aSelectorList)
+{
+ MOZ_ASSERT(!aTreeMatchContext.mForScopedStyle,
+ "mCurrentStyleScope will need to be saved and restored after the "
+ "SelectorMatchesTree call");
+
+ while (aSelectorList) {
+ nsCSSSelector* sel = aSelectorList->mSelectors;
+ NS_ASSERTION(sel, "Should have *some* selectors");
+ NS_ASSERTION(!sel->IsPseudoElement(), "Shouldn't have been called");
+ NodeMatchContext nodeContext(EventStates(), false);
+ if (SelectorMatches(aElement, sel, nodeContext, aTreeMatchContext,
+ SelectorMatchesFlags::NONE)) {
+ nsCSSSelector* next = sel->mNext;
+ if (!next ||
+ SelectorMatchesTree(aElement, next, aTreeMatchContext,
+ SelectorMatchesTreeFlags(0))) {
+ return true;
+ }
+ }
+
+ aSelectorList = aSelectorList->mNext;
+ }
+
+ return false;
+}
+
+void
+nsCSSRuleProcessor::TakeDocumentRulesAndCacheKey(
+ nsPresContext* aPresContext,
+ nsTArray<css::DocumentRule*>& aDocumentRules,
+ nsDocumentRuleResultCacheKey& aCacheKey)
+{
+ MOZ_ASSERT(mIsShared);
+
+ GetRuleCascade(aPresContext);
+ MOZ_ASSERT(mDocumentRulesAndCacheKeyValid);
+
+ aDocumentRules.Clear();
+ aDocumentRules.SwapElements(mDocumentRules);
+ aCacheKey.Swap(mDocumentCacheKey);
+
+#ifdef DEBUG
+ mDocumentRulesAndCacheKeyValid = false;
+#endif
+}
+
+void
+nsCSSRuleProcessor::AddStyleSetRef()
+{
+ MOZ_ASSERT(mIsShared);
+ if (++mStyleSetRefCnt == 1) {
+ RuleProcessorCache::StopTracking(this);
+ }
+}
+
+void
+nsCSSRuleProcessor::ReleaseStyleSetRef()
+{
+ MOZ_ASSERT(mIsShared);
+ MOZ_ASSERT(mStyleSetRefCnt > 0);
+ if (--mStyleSetRefCnt == 0 && mInRuleProcessorCache) {
+ 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<Element*, 50> 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]);
+ }
+ }
+}
+
+void
+TreeMatchContext::InitStyleScopes(Element* aElement)
+{
+ MOZ_ASSERT(mStyleScopes.IsEmpty());
+
+ if (MOZ_LIKELY(aElement)) {
+ // Collect up the ancestors
+ AutoTArray<Element*, 50> 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 <style scoped> scope.
+ return;
+ }
+ Element* cur = aElement->GetParentElementCrossingShadowRoot();
+ while (cur) {
+ if (cur->IsScopedStyleRoot()) {
+ MOZ_ASSERT(mStyleScopes.Contains(cur));
+ }
+ cur = cur->GetParentElementCrossingShadowRoot();
+ }
+}
+#endif