Wed, 31 Dec 2014 07:16:47 +0100
Revert simplistic fix pending revisit of Mozilla integration attempt.
michael@0 | 1 | /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
michael@0 | 2 | // vim:cindent:tabstop=2:expandtab:shiftwidth=2: |
michael@0 | 3 | /* This Source Code Form is subject to the terms of the Mozilla Public |
michael@0 | 4 | * License, v. 2.0. If a copy of the MPL was not distributed with this |
michael@0 | 5 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
michael@0 | 6 | |
michael@0 | 7 | /* |
michael@0 | 8 | * style rule processor for CSS style sheets, responsible for selector |
michael@0 | 9 | * matching and cascading |
michael@0 | 10 | */ |
michael@0 | 11 | |
michael@0 | 12 | #define PL_ARENA_CONST_ALIGN_MASK 7 |
michael@0 | 13 | // We want page-sized arenas so there's no fragmentation involved. |
michael@0 | 14 | // Including plarena.h must come first to avoid it being included by some |
michael@0 | 15 | // header file thereby making PL_ARENA_CONST_ALIGN_MASK ineffective. |
michael@0 | 16 | #define NS_CASCADEENUMDATA_ARENA_BLOCK_SIZE (4096) |
michael@0 | 17 | #include "plarena.h" |
michael@0 | 18 | |
michael@0 | 19 | #include "nsCSSRuleProcessor.h" |
michael@0 | 20 | #include "nsRuleProcessorData.h" |
michael@0 | 21 | #include <algorithm> |
michael@0 | 22 | #include "nsIAtom.h" |
michael@0 | 23 | #include "pldhash.h" |
michael@0 | 24 | #include "nsICSSPseudoComparator.h" |
michael@0 | 25 | #include "mozilla/MemoryReporting.h" |
michael@0 | 26 | #include "mozilla/css/StyleRule.h" |
michael@0 | 27 | #include "mozilla/css/GroupRule.h" |
michael@0 | 28 | #include "nsIDocument.h" |
michael@0 | 29 | #include "nsPresContext.h" |
michael@0 | 30 | #include "nsGkAtoms.h" |
michael@0 | 31 | #include "nsUnicharUtils.h" |
michael@0 | 32 | #include "nsError.h" |
michael@0 | 33 | #include "nsRuleWalker.h" |
michael@0 | 34 | #include "nsCSSPseudoClasses.h" |
michael@0 | 35 | #include "nsCSSPseudoElements.h" |
michael@0 | 36 | #include "nsIContent.h" |
michael@0 | 37 | #include "nsCOMPtr.h" |
michael@0 | 38 | #include "nsHashKeys.h" |
michael@0 | 39 | #include "nsStyleUtil.h" |
michael@0 | 40 | #include "nsQuickSort.h" |
michael@0 | 41 | #include "nsAttrValue.h" |
michael@0 | 42 | #include "nsAttrValueInlines.h" |
michael@0 | 43 | #include "nsAttrName.h" |
michael@0 | 44 | #include "nsTArray.h" |
michael@0 | 45 | #include "nsContentUtils.h" |
michael@0 | 46 | #include "nsIMediaList.h" |
michael@0 | 47 | #include "nsCSSRules.h" |
michael@0 | 48 | #include "nsStyleSet.h" |
michael@0 | 49 | #include "mozilla/dom/Element.h" |
michael@0 | 50 | #include "nsNthIndexCache.h" |
michael@0 | 51 | #include "mozilla/ArrayUtils.h" |
michael@0 | 52 | #include "mozilla/EventStates.h" |
michael@0 | 53 | #include "mozilla/Preferences.h" |
michael@0 | 54 | #include "mozilla/LookAndFeel.h" |
michael@0 | 55 | #include "mozilla/Likely.h" |
michael@0 | 56 | |
michael@0 | 57 | using namespace mozilla; |
michael@0 | 58 | using namespace mozilla::dom; |
michael@0 | 59 | |
michael@0 | 60 | #define VISITED_PSEUDO_PREF "layout.css.visited_links_enabled" |
michael@0 | 61 | |
michael@0 | 62 | static bool gSupportVisitedPseudo = true; |
michael@0 | 63 | |
michael@0 | 64 | static nsTArray< nsCOMPtr<nsIAtom> >* sSystemMetrics = 0; |
michael@0 | 65 | |
michael@0 | 66 | #ifdef XP_WIN |
michael@0 | 67 | uint8_t nsCSSRuleProcessor::sWinThemeId = LookAndFeel::eWindowsTheme_Generic; |
michael@0 | 68 | #endif |
michael@0 | 69 | |
michael@0 | 70 | /** |
michael@0 | 71 | * A struct representing a given CSS rule and a particular selector |
michael@0 | 72 | * from that rule's selector list. |
michael@0 | 73 | */ |
michael@0 | 74 | struct RuleSelectorPair { |
michael@0 | 75 | RuleSelectorPair(css::StyleRule* aRule, nsCSSSelector* aSelector) |
michael@0 | 76 | : mRule(aRule), mSelector(aSelector) {} |
michael@0 | 77 | // If this class ever grows a destructor, deal with |
michael@0 | 78 | // PerWeightDataListItem appropriately. |
michael@0 | 79 | |
michael@0 | 80 | css::StyleRule* mRule; |
michael@0 | 81 | nsCSSSelector* mSelector; // which of |mRule|'s selectors |
michael@0 | 82 | }; |
michael@0 | 83 | |
michael@0 | 84 | #define NS_IS_ANCESTOR_OPERATOR(ch) \ |
michael@0 | 85 | ((ch) == char16_t(' ') || (ch) == char16_t('>')) |
michael@0 | 86 | |
michael@0 | 87 | /** |
michael@0 | 88 | * A struct representing a particular rule in an ordered list of rules |
michael@0 | 89 | * (the ordering depending on the weight of mSelector and the order of |
michael@0 | 90 | * our rules to start with). |
michael@0 | 91 | */ |
michael@0 | 92 | struct RuleValue : RuleSelectorPair { |
michael@0 | 93 | enum { |
michael@0 | 94 | eMaxAncestorHashes = 4 |
michael@0 | 95 | }; |
michael@0 | 96 | |
michael@0 | 97 | RuleValue(const RuleSelectorPair& aRuleSelectorPair, int32_t aIndex, |
michael@0 | 98 | bool aQuirksMode) : |
michael@0 | 99 | RuleSelectorPair(aRuleSelectorPair), |
michael@0 | 100 | mIndex(aIndex) |
michael@0 | 101 | { |
michael@0 | 102 | CollectAncestorHashes(aQuirksMode); |
michael@0 | 103 | } |
michael@0 | 104 | |
michael@0 | 105 | int32_t mIndex; // High index means high weight/order. |
michael@0 | 106 | uint32_t mAncestorSelectorHashes[eMaxAncestorHashes]; |
michael@0 | 107 | |
michael@0 | 108 | private: |
michael@0 | 109 | void CollectAncestorHashes(bool aQuirksMode) { |
michael@0 | 110 | // Collect up our mAncestorSelectorHashes. It's not clear whether it's |
michael@0 | 111 | // better to stop once we've found eMaxAncestorHashes of them or to keep |
michael@0 | 112 | // going and preferentially collect information from selectors higher up the |
michael@0 | 113 | // chain... Let's do the former for now. |
michael@0 | 114 | size_t hashIndex = 0; |
michael@0 | 115 | for (nsCSSSelector* sel = mSelector->mNext; sel; sel = sel->mNext) { |
michael@0 | 116 | if (!NS_IS_ANCESTOR_OPERATOR(sel->mOperator)) { |
michael@0 | 117 | // |sel| is going to select something that's not actually one of our |
michael@0 | 118 | // ancestors, so don't add it to mAncestorSelectorHashes. But keep |
michael@0 | 119 | // going, because it'll select a sibling of one of our ancestors, so its |
michael@0 | 120 | // ancestors would be our ancestors too. |
michael@0 | 121 | continue; |
michael@0 | 122 | } |
michael@0 | 123 | |
michael@0 | 124 | // Now sel is supposed to select one of our ancestors. Grab |
michael@0 | 125 | // whatever info we can from it into mAncestorSelectorHashes. |
michael@0 | 126 | // But in qurks mode, don't grab IDs and classes because those |
michael@0 | 127 | // need to be matched case-insensitively. |
michael@0 | 128 | if (!aQuirksMode) { |
michael@0 | 129 | nsAtomList* ids = sel->mIDList; |
michael@0 | 130 | while (ids) { |
michael@0 | 131 | mAncestorSelectorHashes[hashIndex++] = ids->mAtom->hash(); |
michael@0 | 132 | if (hashIndex == eMaxAncestorHashes) { |
michael@0 | 133 | return; |
michael@0 | 134 | } |
michael@0 | 135 | ids = ids->mNext; |
michael@0 | 136 | } |
michael@0 | 137 | |
michael@0 | 138 | nsAtomList* classes = sel->mClassList; |
michael@0 | 139 | while (classes) { |
michael@0 | 140 | mAncestorSelectorHashes[hashIndex++] = classes->mAtom->hash(); |
michael@0 | 141 | if (hashIndex == eMaxAncestorHashes) { |
michael@0 | 142 | return; |
michael@0 | 143 | } |
michael@0 | 144 | classes = classes->mNext; |
michael@0 | 145 | } |
michael@0 | 146 | } |
michael@0 | 147 | |
michael@0 | 148 | // Only put in the tag name if it's all-lowercase. Otherwise we run into |
michael@0 | 149 | // trouble because we may test the wrong one of mLowercaseTag and |
michael@0 | 150 | // mCasedTag against the filter. |
michael@0 | 151 | if (sel->mLowercaseTag && sel->mCasedTag == sel->mLowercaseTag) { |
michael@0 | 152 | mAncestorSelectorHashes[hashIndex++] = sel->mLowercaseTag->hash(); |
michael@0 | 153 | if (hashIndex == eMaxAncestorHashes) { |
michael@0 | 154 | return; |
michael@0 | 155 | } |
michael@0 | 156 | } |
michael@0 | 157 | } |
michael@0 | 158 | |
michael@0 | 159 | while (hashIndex != eMaxAncestorHashes) { |
michael@0 | 160 | mAncestorSelectorHashes[hashIndex++] = 0; |
michael@0 | 161 | } |
michael@0 | 162 | } |
michael@0 | 163 | }; |
michael@0 | 164 | |
michael@0 | 165 | // ------------------------------ |
michael@0 | 166 | // Rule hash table |
michael@0 | 167 | // |
michael@0 | 168 | |
michael@0 | 169 | // Uses any of the sets of ops below. |
michael@0 | 170 | struct RuleHashTableEntry : public PLDHashEntryHdr { |
michael@0 | 171 | // If you add members that have heap allocated memory be sure to change the |
michael@0 | 172 | // logic in SizeOfRuleHashTableEntry(). |
michael@0 | 173 | // Auto length 1, because we always have at least one entry in mRules. |
michael@0 | 174 | nsAutoTArray<RuleValue, 1> mRules; |
michael@0 | 175 | }; |
michael@0 | 176 | |
michael@0 | 177 | struct RuleHashTagTableEntry : public RuleHashTableEntry { |
michael@0 | 178 | // If you add members that have heap allocated memory be sure to change the |
michael@0 | 179 | // logic in RuleHash::SizeOf{In,Ex}cludingThis. |
michael@0 | 180 | nsCOMPtr<nsIAtom> mTag; |
michael@0 | 181 | }; |
michael@0 | 182 | |
michael@0 | 183 | static PLDHashNumber |
michael@0 | 184 | RuleHash_CIHashKey(PLDHashTable *table, const void *key) |
michael@0 | 185 | { |
michael@0 | 186 | nsIAtom *atom = const_cast<nsIAtom*>(static_cast<const nsIAtom*>(key)); |
michael@0 | 187 | |
michael@0 | 188 | nsAutoString str; |
michael@0 | 189 | atom->ToString(str); |
michael@0 | 190 | nsContentUtils::ASCIIToLower(str); |
michael@0 | 191 | return HashString(str); |
michael@0 | 192 | } |
michael@0 | 193 | |
michael@0 | 194 | typedef nsIAtom* |
michael@0 | 195 | (* RuleHashGetKey) (PLDHashTable *table, const PLDHashEntryHdr *entry); |
michael@0 | 196 | |
michael@0 | 197 | struct RuleHashTableOps { |
michael@0 | 198 | const PLDHashTableOps ops; |
michael@0 | 199 | // Extra callback to avoid duplicating the matchEntry callback for |
michael@0 | 200 | // each table. (There used to be a getKey callback in |
michael@0 | 201 | // PLDHashTableOps.) |
michael@0 | 202 | RuleHashGetKey getKey; |
michael@0 | 203 | }; |
michael@0 | 204 | |
michael@0 | 205 | inline const RuleHashTableOps* |
michael@0 | 206 | ToLocalOps(const PLDHashTableOps *aOps) |
michael@0 | 207 | { |
michael@0 | 208 | return (const RuleHashTableOps*) |
michael@0 | 209 | (((const char*) aOps) - offsetof(RuleHashTableOps, ops)); |
michael@0 | 210 | } |
michael@0 | 211 | |
michael@0 | 212 | static bool |
michael@0 | 213 | RuleHash_CIMatchEntry(PLDHashTable *table, const PLDHashEntryHdr *hdr, |
michael@0 | 214 | const void *key) |
michael@0 | 215 | { |
michael@0 | 216 | nsIAtom *match_atom = const_cast<nsIAtom*>(static_cast<const nsIAtom*> |
michael@0 | 217 | (key)); |
michael@0 | 218 | // Use our extra |getKey| callback to avoid code duplication. |
michael@0 | 219 | nsIAtom *entry_atom = ToLocalOps(table->ops)->getKey(table, hdr); |
michael@0 | 220 | |
michael@0 | 221 | // Check for case-sensitive match first. |
michael@0 | 222 | if (match_atom == entry_atom) |
michael@0 | 223 | return true; |
michael@0 | 224 | |
michael@0 | 225 | // Use EqualsIgnoreASCIICase instead of full on unicode case conversion |
michael@0 | 226 | // in order to save on performance. This is only used in quirks mode |
michael@0 | 227 | // anyway. |
michael@0 | 228 | |
michael@0 | 229 | return |
michael@0 | 230 | nsContentUtils::EqualsIgnoreASCIICase(nsDependentAtomString(entry_atom), |
michael@0 | 231 | nsDependentAtomString(match_atom)); |
michael@0 | 232 | } |
michael@0 | 233 | |
michael@0 | 234 | static bool |
michael@0 | 235 | RuleHash_CSMatchEntry(PLDHashTable *table, const PLDHashEntryHdr *hdr, |
michael@0 | 236 | const void *key) |
michael@0 | 237 | { |
michael@0 | 238 | nsIAtom *match_atom = const_cast<nsIAtom*>(static_cast<const nsIAtom*> |
michael@0 | 239 | (key)); |
michael@0 | 240 | // Use our extra |getKey| callback to avoid code duplication. |
michael@0 | 241 | nsIAtom *entry_atom = ToLocalOps(table->ops)->getKey(table, hdr); |
michael@0 | 242 | |
michael@0 | 243 | return match_atom == entry_atom; |
michael@0 | 244 | } |
michael@0 | 245 | |
michael@0 | 246 | static bool |
michael@0 | 247 | RuleHash_InitEntry(PLDHashTable *table, PLDHashEntryHdr *hdr, |
michael@0 | 248 | const void *key) |
michael@0 | 249 | { |
michael@0 | 250 | RuleHashTableEntry* entry = static_cast<RuleHashTableEntry*>(hdr); |
michael@0 | 251 | new (entry) RuleHashTableEntry(); |
michael@0 | 252 | return true; |
michael@0 | 253 | } |
michael@0 | 254 | |
michael@0 | 255 | static void |
michael@0 | 256 | RuleHash_ClearEntry(PLDHashTable *table, PLDHashEntryHdr *hdr) |
michael@0 | 257 | { |
michael@0 | 258 | RuleHashTableEntry* entry = static_cast<RuleHashTableEntry*>(hdr); |
michael@0 | 259 | entry->~RuleHashTableEntry(); |
michael@0 | 260 | } |
michael@0 | 261 | |
michael@0 | 262 | static void |
michael@0 | 263 | RuleHash_MoveEntry(PLDHashTable *table, const PLDHashEntryHdr *from, |
michael@0 | 264 | PLDHashEntryHdr *to) |
michael@0 | 265 | { |
michael@0 | 266 | NS_PRECONDITION(from != to, "This is not going to work!"); |
michael@0 | 267 | RuleHashTableEntry *oldEntry = |
michael@0 | 268 | const_cast<RuleHashTableEntry*>( |
michael@0 | 269 | static_cast<const RuleHashTableEntry*>(from)); |
michael@0 | 270 | RuleHashTableEntry *newEntry = new (to) RuleHashTableEntry(); |
michael@0 | 271 | newEntry->mRules.SwapElements(oldEntry->mRules); |
michael@0 | 272 | oldEntry->~RuleHashTableEntry(); |
michael@0 | 273 | } |
michael@0 | 274 | |
michael@0 | 275 | static bool |
michael@0 | 276 | RuleHash_TagTable_MatchEntry(PLDHashTable *table, const PLDHashEntryHdr *hdr, |
michael@0 | 277 | const void *key) |
michael@0 | 278 | { |
michael@0 | 279 | nsIAtom *match_atom = const_cast<nsIAtom*>(static_cast<const nsIAtom*> |
michael@0 | 280 | (key)); |
michael@0 | 281 | nsIAtom *entry_atom = static_cast<const RuleHashTagTableEntry*>(hdr)->mTag; |
michael@0 | 282 | |
michael@0 | 283 | return match_atom == entry_atom; |
michael@0 | 284 | } |
michael@0 | 285 | |
michael@0 | 286 | static bool |
michael@0 | 287 | RuleHash_TagTable_InitEntry(PLDHashTable *table, PLDHashEntryHdr *hdr, |
michael@0 | 288 | const void *key) |
michael@0 | 289 | { |
michael@0 | 290 | RuleHashTagTableEntry* entry = static_cast<RuleHashTagTableEntry*>(hdr); |
michael@0 | 291 | new (entry) RuleHashTagTableEntry(); |
michael@0 | 292 | entry->mTag = const_cast<nsIAtom*>(static_cast<const nsIAtom*>(key)); |
michael@0 | 293 | return true; |
michael@0 | 294 | } |
michael@0 | 295 | |
michael@0 | 296 | static void |
michael@0 | 297 | RuleHash_TagTable_ClearEntry(PLDHashTable *table, PLDHashEntryHdr *hdr) |
michael@0 | 298 | { |
michael@0 | 299 | RuleHashTagTableEntry* entry = static_cast<RuleHashTagTableEntry*>(hdr); |
michael@0 | 300 | entry->~RuleHashTagTableEntry(); |
michael@0 | 301 | } |
michael@0 | 302 | |
michael@0 | 303 | static void |
michael@0 | 304 | RuleHash_TagTable_MoveEntry(PLDHashTable *table, const PLDHashEntryHdr *from, |
michael@0 | 305 | PLDHashEntryHdr *to) |
michael@0 | 306 | { |
michael@0 | 307 | NS_PRECONDITION(from != to, "This is not going to work!"); |
michael@0 | 308 | RuleHashTagTableEntry *oldEntry = |
michael@0 | 309 | const_cast<RuleHashTagTableEntry*>( |
michael@0 | 310 | static_cast<const RuleHashTagTableEntry*>(from)); |
michael@0 | 311 | RuleHashTagTableEntry *newEntry = new (to) RuleHashTagTableEntry(); |
michael@0 | 312 | newEntry->mTag.swap(oldEntry->mTag); |
michael@0 | 313 | newEntry->mRules.SwapElements(oldEntry->mRules); |
michael@0 | 314 | oldEntry->~RuleHashTagTableEntry(); |
michael@0 | 315 | } |
michael@0 | 316 | |
michael@0 | 317 | static nsIAtom* |
michael@0 | 318 | RuleHash_ClassTable_GetKey(PLDHashTable *table, const PLDHashEntryHdr *hdr) |
michael@0 | 319 | { |
michael@0 | 320 | const RuleHashTableEntry *entry = |
michael@0 | 321 | static_cast<const RuleHashTableEntry*>(hdr); |
michael@0 | 322 | nsCSSSelector* selector = entry->mRules[0].mSelector; |
michael@0 | 323 | if (selector->IsPseudoElement()) { |
michael@0 | 324 | selector = selector->mNext; |
michael@0 | 325 | } |
michael@0 | 326 | return selector->mClassList->mAtom; |
michael@0 | 327 | } |
michael@0 | 328 | |
michael@0 | 329 | static nsIAtom* |
michael@0 | 330 | RuleHash_IdTable_GetKey(PLDHashTable *table, const PLDHashEntryHdr *hdr) |
michael@0 | 331 | { |
michael@0 | 332 | const RuleHashTableEntry *entry = |
michael@0 | 333 | static_cast<const RuleHashTableEntry*>(hdr); |
michael@0 | 334 | nsCSSSelector* selector = entry->mRules[0].mSelector; |
michael@0 | 335 | if (selector->IsPseudoElement()) { |
michael@0 | 336 | selector = selector->mNext; |
michael@0 | 337 | } |
michael@0 | 338 | return selector->mIDList->mAtom; |
michael@0 | 339 | } |
michael@0 | 340 | |
michael@0 | 341 | static PLDHashNumber |
michael@0 | 342 | RuleHash_NameSpaceTable_HashKey(PLDHashTable *table, const void *key) |
michael@0 | 343 | { |
michael@0 | 344 | return NS_PTR_TO_INT32(key); |
michael@0 | 345 | } |
michael@0 | 346 | |
michael@0 | 347 | static bool |
michael@0 | 348 | RuleHash_NameSpaceTable_MatchEntry(PLDHashTable *table, |
michael@0 | 349 | const PLDHashEntryHdr *hdr, |
michael@0 | 350 | const void *key) |
michael@0 | 351 | { |
michael@0 | 352 | const RuleHashTableEntry *entry = |
michael@0 | 353 | static_cast<const RuleHashTableEntry*>(hdr); |
michael@0 | 354 | |
michael@0 | 355 | nsCSSSelector* selector = entry->mRules[0].mSelector; |
michael@0 | 356 | if (selector->IsPseudoElement()) { |
michael@0 | 357 | selector = selector->mNext; |
michael@0 | 358 | } |
michael@0 | 359 | return NS_PTR_TO_INT32(key) == selector->mNameSpace; |
michael@0 | 360 | } |
michael@0 | 361 | |
michael@0 | 362 | static const PLDHashTableOps RuleHash_TagTable_Ops = { |
michael@0 | 363 | PL_DHashAllocTable, |
michael@0 | 364 | PL_DHashFreeTable, |
michael@0 | 365 | PL_DHashVoidPtrKeyStub, |
michael@0 | 366 | RuleHash_TagTable_MatchEntry, |
michael@0 | 367 | RuleHash_TagTable_MoveEntry, |
michael@0 | 368 | RuleHash_TagTable_ClearEntry, |
michael@0 | 369 | PL_DHashFinalizeStub, |
michael@0 | 370 | RuleHash_TagTable_InitEntry |
michael@0 | 371 | }; |
michael@0 | 372 | |
michael@0 | 373 | // Case-sensitive ops. |
michael@0 | 374 | static const RuleHashTableOps RuleHash_ClassTable_CSOps = { |
michael@0 | 375 | { |
michael@0 | 376 | PL_DHashAllocTable, |
michael@0 | 377 | PL_DHashFreeTable, |
michael@0 | 378 | PL_DHashVoidPtrKeyStub, |
michael@0 | 379 | RuleHash_CSMatchEntry, |
michael@0 | 380 | RuleHash_MoveEntry, |
michael@0 | 381 | RuleHash_ClearEntry, |
michael@0 | 382 | PL_DHashFinalizeStub, |
michael@0 | 383 | RuleHash_InitEntry |
michael@0 | 384 | }, |
michael@0 | 385 | RuleHash_ClassTable_GetKey |
michael@0 | 386 | }; |
michael@0 | 387 | |
michael@0 | 388 | // Case-insensitive ops. |
michael@0 | 389 | static const RuleHashTableOps RuleHash_ClassTable_CIOps = { |
michael@0 | 390 | { |
michael@0 | 391 | PL_DHashAllocTable, |
michael@0 | 392 | PL_DHashFreeTable, |
michael@0 | 393 | RuleHash_CIHashKey, |
michael@0 | 394 | RuleHash_CIMatchEntry, |
michael@0 | 395 | RuleHash_MoveEntry, |
michael@0 | 396 | RuleHash_ClearEntry, |
michael@0 | 397 | PL_DHashFinalizeStub, |
michael@0 | 398 | RuleHash_InitEntry |
michael@0 | 399 | }, |
michael@0 | 400 | RuleHash_ClassTable_GetKey |
michael@0 | 401 | }; |
michael@0 | 402 | |
michael@0 | 403 | // Case-sensitive ops. |
michael@0 | 404 | static const RuleHashTableOps RuleHash_IdTable_CSOps = { |
michael@0 | 405 | { |
michael@0 | 406 | PL_DHashAllocTable, |
michael@0 | 407 | PL_DHashFreeTable, |
michael@0 | 408 | PL_DHashVoidPtrKeyStub, |
michael@0 | 409 | RuleHash_CSMatchEntry, |
michael@0 | 410 | RuleHash_MoveEntry, |
michael@0 | 411 | RuleHash_ClearEntry, |
michael@0 | 412 | PL_DHashFinalizeStub, |
michael@0 | 413 | RuleHash_InitEntry |
michael@0 | 414 | }, |
michael@0 | 415 | RuleHash_IdTable_GetKey |
michael@0 | 416 | }; |
michael@0 | 417 | |
michael@0 | 418 | // Case-insensitive ops. |
michael@0 | 419 | static const RuleHashTableOps RuleHash_IdTable_CIOps = { |
michael@0 | 420 | { |
michael@0 | 421 | PL_DHashAllocTable, |
michael@0 | 422 | PL_DHashFreeTable, |
michael@0 | 423 | RuleHash_CIHashKey, |
michael@0 | 424 | RuleHash_CIMatchEntry, |
michael@0 | 425 | RuleHash_MoveEntry, |
michael@0 | 426 | RuleHash_ClearEntry, |
michael@0 | 427 | PL_DHashFinalizeStub, |
michael@0 | 428 | RuleHash_InitEntry |
michael@0 | 429 | }, |
michael@0 | 430 | RuleHash_IdTable_GetKey |
michael@0 | 431 | }; |
michael@0 | 432 | |
michael@0 | 433 | static const PLDHashTableOps RuleHash_NameSpaceTable_Ops = { |
michael@0 | 434 | PL_DHashAllocTable, |
michael@0 | 435 | PL_DHashFreeTable, |
michael@0 | 436 | RuleHash_NameSpaceTable_HashKey, |
michael@0 | 437 | RuleHash_NameSpaceTable_MatchEntry, |
michael@0 | 438 | RuleHash_MoveEntry, |
michael@0 | 439 | RuleHash_ClearEntry, |
michael@0 | 440 | PL_DHashFinalizeStub, |
michael@0 | 441 | RuleHash_InitEntry |
michael@0 | 442 | }; |
michael@0 | 443 | |
michael@0 | 444 | #undef RULE_HASH_STATS |
michael@0 | 445 | #undef PRINT_UNIVERSAL_RULES |
michael@0 | 446 | |
michael@0 | 447 | #ifdef RULE_HASH_STATS |
michael@0 | 448 | #define RULE_HASH_STAT_INCREMENT(var_) PR_BEGIN_MACRO ++(var_); PR_END_MACRO |
michael@0 | 449 | #else |
michael@0 | 450 | #define RULE_HASH_STAT_INCREMENT(var_) PR_BEGIN_MACRO PR_END_MACRO |
michael@0 | 451 | #endif |
michael@0 | 452 | |
michael@0 | 453 | struct NodeMatchContext; |
michael@0 | 454 | |
michael@0 | 455 | class RuleHash { |
michael@0 | 456 | public: |
michael@0 | 457 | RuleHash(bool aQuirksMode); |
michael@0 | 458 | ~RuleHash(); |
michael@0 | 459 | void AppendRule(const RuleSelectorPair &aRuleInfo); |
michael@0 | 460 | void EnumerateAllRules(Element* aElement, ElementDependentRuleProcessorData* aData, |
michael@0 | 461 | NodeMatchContext& aNodeMatchContext); |
michael@0 | 462 | |
michael@0 | 463 | size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const; |
michael@0 | 464 | size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const; |
michael@0 | 465 | |
michael@0 | 466 | protected: |
michael@0 | 467 | typedef nsTArray<RuleValue> RuleValueList; |
michael@0 | 468 | void AppendRuleToTable(PLDHashTable* aTable, const void* aKey, |
michael@0 | 469 | const RuleSelectorPair& aRuleInfo); |
michael@0 | 470 | void AppendUniversalRule(const RuleSelectorPair& aRuleInfo); |
michael@0 | 471 | |
michael@0 | 472 | int32_t mRuleCount; |
michael@0 | 473 | // The hashtables are lazily initialized; we use a null .ops to |
michael@0 | 474 | // indicate that they need initialization. |
michael@0 | 475 | PLDHashTable mIdTable; |
michael@0 | 476 | PLDHashTable mClassTable; |
michael@0 | 477 | PLDHashTable mTagTable; |
michael@0 | 478 | PLDHashTable mNameSpaceTable; |
michael@0 | 479 | RuleValueList mUniversalRules; |
michael@0 | 480 | |
michael@0 | 481 | struct EnumData { |
michael@0 | 482 | const RuleValue* mCurValue; |
michael@0 | 483 | const RuleValue* mEnd; |
michael@0 | 484 | }; |
michael@0 | 485 | EnumData* mEnumList; |
michael@0 | 486 | int32_t mEnumListSize; |
michael@0 | 487 | |
michael@0 | 488 | bool mQuirksMode; |
michael@0 | 489 | |
michael@0 | 490 | inline EnumData ToEnumData(const RuleValueList& arr) { |
michael@0 | 491 | EnumData data = { arr.Elements(), arr.Elements() + arr.Length() }; |
michael@0 | 492 | return data; |
michael@0 | 493 | } |
michael@0 | 494 | |
michael@0 | 495 | #ifdef RULE_HASH_STATS |
michael@0 | 496 | uint32_t mUniversalSelectors; |
michael@0 | 497 | uint32_t mNameSpaceSelectors; |
michael@0 | 498 | uint32_t mTagSelectors; |
michael@0 | 499 | uint32_t mClassSelectors; |
michael@0 | 500 | uint32_t mIdSelectors; |
michael@0 | 501 | |
michael@0 | 502 | uint32_t mElementsMatched; |
michael@0 | 503 | |
michael@0 | 504 | uint32_t mElementUniversalCalls; |
michael@0 | 505 | uint32_t mElementNameSpaceCalls; |
michael@0 | 506 | uint32_t mElementTagCalls; |
michael@0 | 507 | uint32_t mElementClassCalls; |
michael@0 | 508 | uint32_t mElementIdCalls; |
michael@0 | 509 | #endif // RULE_HASH_STATS |
michael@0 | 510 | }; |
michael@0 | 511 | |
michael@0 | 512 | RuleHash::RuleHash(bool aQuirksMode) |
michael@0 | 513 | : mRuleCount(0), |
michael@0 | 514 | mUniversalRules(0), |
michael@0 | 515 | mEnumList(nullptr), mEnumListSize(0), |
michael@0 | 516 | mQuirksMode(aQuirksMode) |
michael@0 | 517 | #ifdef RULE_HASH_STATS |
michael@0 | 518 | , |
michael@0 | 519 | mUniversalSelectors(0), |
michael@0 | 520 | mNameSpaceSelectors(0), |
michael@0 | 521 | mTagSelectors(0), |
michael@0 | 522 | mClassSelectors(0), |
michael@0 | 523 | mIdSelectors(0), |
michael@0 | 524 | mElementsMatched(0), |
michael@0 | 525 | mElementUniversalCalls(0), |
michael@0 | 526 | mElementNameSpaceCalls(0), |
michael@0 | 527 | mElementTagCalls(0), |
michael@0 | 528 | mElementClassCalls(0), |
michael@0 | 529 | mElementIdCalls(0) |
michael@0 | 530 | #endif |
michael@0 | 531 | { |
michael@0 | 532 | MOZ_COUNT_CTOR(RuleHash); |
michael@0 | 533 | |
michael@0 | 534 | mTagTable.ops = nullptr; |
michael@0 | 535 | mIdTable.ops = nullptr; |
michael@0 | 536 | mClassTable.ops = nullptr; |
michael@0 | 537 | mNameSpaceTable.ops = nullptr; |
michael@0 | 538 | } |
michael@0 | 539 | |
michael@0 | 540 | RuleHash::~RuleHash() |
michael@0 | 541 | { |
michael@0 | 542 | MOZ_COUNT_DTOR(RuleHash); |
michael@0 | 543 | #ifdef RULE_HASH_STATS |
michael@0 | 544 | printf( |
michael@0 | 545 | "RuleHash(%p):\n" |
michael@0 | 546 | " Selectors: Universal (%u) NameSpace(%u) Tag(%u) Class(%u) Id(%u)\n" |
michael@0 | 547 | " Content Nodes: Elements(%u)\n" |
michael@0 | 548 | " Element Calls: Universal(%u) NameSpace(%u) Tag(%u) Class(%u) Id(%u)\n" |
michael@0 | 549 | static_cast<void*>(this), |
michael@0 | 550 | mUniversalSelectors, mNameSpaceSelectors, mTagSelectors, |
michael@0 | 551 | mClassSelectors, mIdSelectors, |
michael@0 | 552 | mElementsMatched, |
michael@0 | 553 | mElementUniversalCalls, mElementNameSpaceCalls, mElementTagCalls, |
michael@0 | 554 | mElementClassCalls, mElementIdCalls); |
michael@0 | 555 | #ifdef PRINT_UNIVERSAL_RULES |
michael@0 | 556 | { |
michael@0 | 557 | if (mUniversalRules.Length() > 0) { |
michael@0 | 558 | printf(" Universal rules:\n"); |
michael@0 | 559 | for (uint32_t i = 0; i < mUniversalRules.Length(); ++i) { |
michael@0 | 560 | RuleValue* value = &(mUniversalRules[i]); |
michael@0 | 561 | nsAutoString selectorText; |
michael@0 | 562 | uint32_t lineNumber = value->mRule->GetLineNumber(); |
michael@0 | 563 | nsCOMPtr<nsIStyleSheet> sheet; |
michael@0 | 564 | value->mRule->GetStyleSheet(*getter_AddRefs(sheet)); |
michael@0 | 565 | nsRefPtr<nsCSSStyleSheet> cssSheet = do_QueryObject(sheet); |
michael@0 | 566 | value->mSelector->ToString(selectorText, cssSheet); |
michael@0 | 567 | |
michael@0 | 568 | printf(" line %d, %s\n", |
michael@0 | 569 | lineNumber, NS_ConvertUTF16toUTF8(selectorText).get()); |
michael@0 | 570 | } |
michael@0 | 571 | } |
michael@0 | 572 | } |
michael@0 | 573 | #endif // PRINT_UNIVERSAL_RULES |
michael@0 | 574 | #endif // RULE_HASH_STATS |
michael@0 | 575 | // Rule Values are arena allocated no need to delete them. Their destructor |
michael@0 | 576 | // isn't doing any cleanup. So we dont even bother to enumerate through |
michael@0 | 577 | // the hash tables and call their destructors. |
michael@0 | 578 | if (nullptr != mEnumList) { |
michael@0 | 579 | delete [] mEnumList; |
michael@0 | 580 | } |
michael@0 | 581 | // delete arena for strings and small objects |
michael@0 | 582 | if (mIdTable.ops) { |
michael@0 | 583 | PL_DHashTableFinish(&mIdTable); |
michael@0 | 584 | } |
michael@0 | 585 | if (mClassTable.ops) { |
michael@0 | 586 | PL_DHashTableFinish(&mClassTable); |
michael@0 | 587 | } |
michael@0 | 588 | if (mTagTable.ops) { |
michael@0 | 589 | PL_DHashTableFinish(&mTagTable); |
michael@0 | 590 | } |
michael@0 | 591 | if (mNameSpaceTable.ops) { |
michael@0 | 592 | PL_DHashTableFinish(&mNameSpaceTable); |
michael@0 | 593 | } |
michael@0 | 594 | } |
michael@0 | 595 | |
michael@0 | 596 | void RuleHash::AppendRuleToTable(PLDHashTable* aTable, const void* aKey, |
michael@0 | 597 | const RuleSelectorPair& aRuleInfo) |
michael@0 | 598 | { |
michael@0 | 599 | // Get a new or existing entry. |
michael@0 | 600 | RuleHashTableEntry *entry = static_cast<RuleHashTableEntry*> |
michael@0 | 601 | (PL_DHashTableOperate(aTable, aKey, PL_DHASH_ADD)); |
michael@0 | 602 | if (!entry) |
michael@0 | 603 | return; |
michael@0 | 604 | entry->mRules.AppendElement(RuleValue(aRuleInfo, mRuleCount++, mQuirksMode)); |
michael@0 | 605 | } |
michael@0 | 606 | |
michael@0 | 607 | static void |
michael@0 | 608 | AppendRuleToTagTable(PLDHashTable* aTable, nsIAtom* aKey, |
michael@0 | 609 | const RuleValue& aRuleInfo) |
michael@0 | 610 | { |
michael@0 | 611 | // Get a new or exisiting entry |
michael@0 | 612 | RuleHashTagTableEntry *entry = static_cast<RuleHashTagTableEntry*> |
michael@0 | 613 | (PL_DHashTableOperate(aTable, aKey, PL_DHASH_ADD)); |
michael@0 | 614 | if (!entry) |
michael@0 | 615 | return; |
michael@0 | 616 | |
michael@0 | 617 | entry->mRules.AppendElement(aRuleInfo); |
michael@0 | 618 | } |
michael@0 | 619 | |
michael@0 | 620 | void RuleHash::AppendUniversalRule(const RuleSelectorPair& aRuleInfo) |
michael@0 | 621 | { |
michael@0 | 622 | mUniversalRules.AppendElement(RuleValue(aRuleInfo, mRuleCount++, mQuirksMode)); |
michael@0 | 623 | } |
michael@0 | 624 | |
michael@0 | 625 | void RuleHash::AppendRule(const RuleSelectorPair& aRuleInfo) |
michael@0 | 626 | { |
michael@0 | 627 | nsCSSSelector *selector = aRuleInfo.mSelector; |
michael@0 | 628 | if (selector->IsPseudoElement()) { |
michael@0 | 629 | selector = selector->mNext; |
michael@0 | 630 | } |
michael@0 | 631 | if (nullptr != selector->mIDList) { |
michael@0 | 632 | if (!mIdTable.ops) { |
michael@0 | 633 | PL_DHashTableInit(&mIdTable, |
michael@0 | 634 | mQuirksMode ? &RuleHash_IdTable_CIOps.ops |
michael@0 | 635 | : &RuleHash_IdTable_CSOps.ops, |
michael@0 | 636 | nullptr, sizeof(RuleHashTableEntry), 16); |
michael@0 | 637 | } |
michael@0 | 638 | AppendRuleToTable(&mIdTable, selector->mIDList->mAtom, aRuleInfo); |
michael@0 | 639 | RULE_HASH_STAT_INCREMENT(mIdSelectors); |
michael@0 | 640 | } |
michael@0 | 641 | else if (nullptr != selector->mClassList) { |
michael@0 | 642 | if (!mClassTable.ops) { |
michael@0 | 643 | PL_DHashTableInit(&mClassTable, |
michael@0 | 644 | mQuirksMode ? &RuleHash_ClassTable_CIOps.ops |
michael@0 | 645 | : &RuleHash_ClassTable_CSOps.ops, |
michael@0 | 646 | nullptr, sizeof(RuleHashTableEntry), 16); |
michael@0 | 647 | } |
michael@0 | 648 | AppendRuleToTable(&mClassTable, selector->mClassList->mAtom, aRuleInfo); |
michael@0 | 649 | RULE_HASH_STAT_INCREMENT(mClassSelectors); |
michael@0 | 650 | } |
michael@0 | 651 | else if (selector->mLowercaseTag) { |
michael@0 | 652 | RuleValue ruleValue(aRuleInfo, mRuleCount++, mQuirksMode); |
michael@0 | 653 | if (!mTagTable.ops) { |
michael@0 | 654 | PL_DHashTableInit(&mTagTable, &RuleHash_TagTable_Ops, nullptr, |
michael@0 | 655 | sizeof(RuleHashTagTableEntry), 16); |
michael@0 | 656 | } |
michael@0 | 657 | AppendRuleToTagTable(&mTagTable, selector->mLowercaseTag, ruleValue); |
michael@0 | 658 | RULE_HASH_STAT_INCREMENT(mTagSelectors); |
michael@0 | 659 | if (selector->mCasedTag && |
michael@0 | 660 | selector->mCasedTag != selector->mLowercaseTag) { |
michael@0 | 661 | AppendRuleToTagTable(&mTagTable, selector->mCasedTag, ruleValue); |
michael@0 | 662 | RULE_HASH_STAT_INCREMENT(mTagSelectors); |
michael@0 | 663 | } |
michael@0 | 664 | } |
michael@0 | 665 | else if (kNameSpaceID_Unknown != selector->mNameSpace) { |
michael@0 | 666 | if (!mNameSpaceTable.ops) { |
michael@0 | 667 | PL_DHashTableInit(&mNameSpaceTable, &RuleHash_NameSpaceTable_Ops, nullptr, |
michael@0 | 668 | sizeof(RuleHashTableEntry), 16); |
michael@0 | 669 | } |
michael@0 | 670 | AppendRuleToTable(&mNameSpaceTable, |
michael@0 | 671 | NS_INT32_TO_PTR(selector->mNameSpace), aRuleInfo); |
michael@0 | 672 | RULE_HASH_STAT_INCREMENT(mNameSpaceSelectors); |
michael@0 | 673 | } |
michael@0 | 674 | else { // universal tag selector |
michael@0 | 675 | AppendUniversalRule(aRuleInfo); |
michael@0 | 676 | RULE_HASH_STAT_INCREMENT(mUniversalSelectors); |
michael@0 | 677 | } |
michael@0 | 678 | } |
michael@0 | 679 | |
michael@0 | 680 | // this should cover practically all cases so we don't need to reallocate |
michael@0 | 681 | #define MIN_ENUM_LIST_SIZE 8 |
michael@0 | 682 | |
michael@0 | 683 | #ifdef RULE_HASH_STATS |
michael@0 | 684 | #define RULE_HASH_STAT_INCREMENT_LIST_COUNT(list_, var_) \ |
michael@0 | 685 | (var_) += (list_).Length() |
michael@0 | 686 | #else |
michael@0 | 687 | #define RULE_HASH_STAT_INCREMENT_LIST_COUNT(list_, var_) \ |
michael@0 | 688 | PR_BEGIN_MACRO PR_END_MACRO |
michael@0 | 689 | #endif |
michael@0 | 690 | |
michael@0 | 691 | static inline |
michael@0 | 692 | void ContentEnumFunc(const RuleValue &value, nsCSSSelector* selector, |
michael@0 | 693 | ElementDependentRuleProcessorData* data, NodeMatchContext& nodeContext, |
michael@0 | 694 | AncestorFilter *ancestorFilter); |
michael@0 | 695 | |
michael@0 | 696 | void RuleHash::EnumerateAllRules(Element* aElement, ElementDependentRuleProcessorData* aData, |
michael@0 | 697 | NodeMatchContext& aNodeContext) |
michael@0 | 698 | { |
michael@0 | 699 | int32_t nameSpace = aElement->GetNameSpaceID(); |
michael@0 | 700 | nsIAtom* tag = aElement->Tag(); |
michael@0 | 701 | nsIAtom* id = aElement->GetID(); |
michael@0 | 702 | const nsAttrValue* classList = aElement->GetClasses(); |
michael@0 | 703 | |
michael@0 | 704 | NS_ABORT_IF_FALSE(tag, "How could we not have a tag?"); |
michael@0 | 705 | |
michael@0 | 706 | int32_t classCount = classList ? classList->GetAtomCount() : 0; |
michael@0 | 707 | |
michael@0 | 708 | // assume 1 universal, tag, id, and namespace, rather than wasting |
michael@0 | 709 | // time counting |
michael@0 | 710 | int32_t testCount = classCount + 4; |
michael@0 | 711 | |
michael@0 | 712 | if (mEnumListSize < testCount) { |
michael@0 | 713 | delete [] mEnumList; |
michael@0 | 714 | mEnumListSize = std::max(testCount, MIN_ENUM_LIST_SIZE); |
michael@0 | 715 | mEnumList = new EnumData[mEnumListSize]; |
michael@0 | 716 | } |
michael@0 | 717 | |
michael@0 | 718 | int32_t valueCount = 0; |
michael@0 | 719 | RULE_HASH_STAT_INCREMENT(mElementsMatched); |
michael@0 | 720 | |
michael@0 | 721 | if (mUniversalRules.Length() != 0) { // universal rules |
michael@0 | 722 | mEnumList[valueCount++] = ToEnumData(mUniversalRules); |
michael@0 | 723 | RULE_HASH_STAT_INCREMENT_LIST_COUNT(mUniversalRules, mElementUniversalCalls); |
michael@0 | 724 | } |
michael@0 | 725 | // universal rules within the namespace |
michael@0 | 726 | if (kNameSpaceID_Unknown != nameSpace && mNameSpaceTable.ops) { |
michael@0 | 727 | RuleHashTableEntry *entry = static_cast<RuleHashTableEntry*> |
michael@0 | 728 | (PL_DHashTableOperate(&mNameSpaceTable, NS_INT32_TO_PTR(nameSpace), |
michael@0 | 729 | PL_DHASH_LOOKUP)); |
michael@0 | 730 | if (PL_DHASH_ENTRY_IS_BUSY(entry)) { |
michael@0 | 731 | mEnumList[valueCount++] = ToEnumData(entry->mRules); |
michael@0 | 732 | RULE_HASH_STAT_INCREMENT_LIST_COUNT(entry->mRules, mElementNameSpaceCalls); |
michael@0 | 733 | } |
michael@0 | 734 | } |
michael@0 | 735 | if (mTagTable.ops) { |
michael@0 | 736 | RuleHashTableEntry *entry = static_cast<RuleHashTableEntry*> |
michael@0 | 737 | (PL_DHashTableOperate(&mTagTable, tag, PL_DHASH_LOOKUP)); |
michael@0 | 738 | if (PL_DHASH_ENTRY_IS_BUSY(entry)) { |
michael@0 | 739 | mEnumList[valueCount++] = ToEnumData(entry->mRules); |
michael@0 | 740 | RULE_HASH_STAT_INCREMENT_LIST_COUNT(entry->mRules, mElementTagCalls); |
michael@0 | 741 | } |
michael@0 | 742 | } |
michael@0 | 743 | if (id && mIdTable.ops) { |
michael@0 | 744 | RuleHashTableEntry *entry = static_cast<RuleHashTableEntry*> |
michael@0 | 745 | (PL_DHashTableOperate(&mIdTable, id, PL_DHASH_LOOKUP)); |
michael@0 | 746 | if (PL_DHASH_ENTRY_IS_BUSY(entry)) { |
michael@0 | 747 | mEnumList[valueCount++] = ToEnumData(entry->mRules); |
michael@0 | 748 | RULE_HASH_STAT_INCREMENT_LIST_COUNT(entry->mRules, mElementIdCalls); |
michael@0 | 749 | } |
michael@0 | 750 | } |
michael@0 | 751 | if (mClassTable.ops) { |
michael@0 | 752 | for (int32_t index = 0; index < classCount; ++index) { |
michael@0 | 753 | RuleHashTableEntry *entry = static_cast<RuleHashTableEntry*> |
michael@0 | 754 | (PL_DHashTableOperate(&mClassTable, classList->AtomAt(index), |
michael@0 | 755 | PL_DHASH_LOOKUP)); |
michael@0 | 756 | if (PL_DHASH_ENTRY_IS_BUSY(entry)) { |
michael@0 | 757 | mEnumList[valueCount++] = ToEnumData(entry->mRules); |
michael@0 | 758 | RULE_HASH_STAT_INCREMENT_LIST_COUNT(entry->mRules, mElementClassCalls); |
michael@0 | 759 | } |
michael@0 | 760 | } |
michael@0 | 761 | } |
michael@0 | 762 | NS_ASSERTION(valueCount <= testCount, "values exceeded list size"); |
michael@0 | 763 | |
michael@0 | 764 | if (valueCount > 0) { |
michael@0 | 765 | AncestorFilter *filter = |
michael@0 | 766 | aData->mTreeMatchContext.mAncestorFilter.HasFilter() ? |
michael@0 | 767 | &aData->mTreeMatchContext.mAncestorFilter : nullptr; |
michael@0 | 768 | #ifdef DEBUG |
michael@0 | 769 | if (filter) { |
michael@0 | 770 | filter->AssertHasAllAncestors(aElement); |
michael@0 | 771 | } |
michael@0 | 772 | #endif |
michael@0 | 773 | // Merge the lists while there are still multiple lists to merge. |
michael@0 | 774 | while (valueCount > 1) { |
michael@0 | 775 | int32_t valueIndex = 0; |
michael@0 | 776 | int32_t lowestRuleIndex = mEnumList[valueIndex].mCurValue->mIndex; |
michael@0 | 777 | for (int32_t index = 1; index < valueCount; ++index) { |
michael@0 | 778 | int32_t ruleIndex = mEnumList[index].mCurValue->mIndex; |
michael@0 | 779 | if (ruleIndex < lowestRuleIndex) { |
michael@0 | 780 | valueIndex = index; |
michael@0 | 781 | lowestRuleIndex = ruleIndex; |
michael@0 | 782 | } |
michael@0 | 783 | } |
michael@0 | 784 | const RuleValue *cur = mEnumList[valueIndex].mCurValue; |
michael@0 | 785 | ContentEnumFunc(*cur, cur->mSelector, aData, aNodeContext, filter); |
michael@0 | 786 | cur++; |
michael@0 | 787 | if (cur == mEnumList[valueIndex].mEnd) { |
michael@0 | 788 | mEnumList[valueIndex] = mEnumList[--valueCount]; |
michael@0 | 789 | } else { |
michael@0 | 790 | mEnumList[valueIndex].mCurValue = cur; |
michael@0 | 791 | } |
michael@0 | 792 | } |
michael@0 | 793 | |
michael@0 | 794 | // Fast loop over single value. |
michael@0 | 795 | for (const RuleValue *value = mEnumList[0].mCurValue, |
michael@0 | 796 | *end = mEnumList[0].mEnd; |
michael@0 | 797 | value != end; ++value) { |
michael@0 | 798 | ContentEnumFunc(*value, value->mSelector, aData, aNodeContext, filter); |
michael@0 | 799 | } |
michael@0 | 800 | } |
michael@0 | 801 | } |
michael@0 | 802 | |
michael@0 | 803 | static size_t |
michael@0 | 804 | SizeOfRuleHashTableEntry(PLDHashEntryHdr* aHdr, MallocSizeOf aMallocSizeOf, void *) |
michael@0 | 805 | { |
michael@0 | 806 | RuleHashTableEntry* entry = static_cast<RuleHashTableEntry*>(aHdr); |
michael@0 | 807 | return entry->mRules.SizeOfExcludingThis(aMallocSizeOf); |
michael@0 | 808 | } |
michael@0 | 809 | |
michael@0 | 810 | size_t |
michael@0 | 811 | RuleHash::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const |
michael@0 | 812 | { |
michael@0 | 813 | size_t n = 0; |
michael@0 | 814 | |
michael@0 | 815 | if (mIdTable.ops) { |
michael@0 | 816 | n += PL_DHashTableSizeOfExcludingThis(&mIdTable, |
michael@0 | 817 | SizeOfRuleHashTableEntry, |
michael@0 | 818 | aMallocSizeOf); |
michael@0 | 819 | } |
michael@0 | 820 | |
michael@0 | 821 | if (mClassTable.ops) { |
michael@0 | 822 | n += PL_DHashTableSizeOfExcludingThis(&mClassTable, |
michael@0 | 823 | SizeOfRuleHashTableEntry, |
michael@0 | 824 | aMallocSizeOf); |
michael@0 | 825 | } |
michael@0 | 826 | |
michael@0 | 827 | if (mTagTable.ops) { |
michael@0 | 828 | n += PL_DHashTableSizeOfExcludingThis(&mTagTable, |
michael@0 | 829 | SizeOfRuleHashTableEntry, |
michael@0 | 830 | aMallocSizeOf); |
michael@0 | 831 | } |
michael@0 | 832 | |
michael@0 | 833 | if (mNameSpaceTable.ops) { |
michael@0 | 834 | n += PL_DHashTableSizeOfExcludingThis(&mNameSpaceTable, |
michael@0 | 835 | SizeOfRuleHashTableEntry, |
michael@0 | 836 | aMallocSizeOf); |
michael@0 | 837 | } |
michael@0 | 838 | |
michael@0 | 839 | n += mUniversalRules.SizeOfExcludingThis(aMallocSizeOf); |
michael@0 | 840 | |
michael@0 | 841 | return n; |
michael@0 | 842 | } |
michael@0 | 843 | |
michael@0 | 844 | size_t |
michael@0 | 845 | RuleHash::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const |
michael@0 | 846 | { |
michael@0 | 847 | return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf); |
michael@0 | 848 | } |
michael@0 | 849 | |
michael@0 | 850 | //-------------------------------- |
michael@0 | 851 | |
michael@0 | 852 | // A hash table mapping atoms to lists of selectors |
michael@0 | 853 | struct AtomSelectorEntry : public PLDHashEntryHdr { |
michael@0 | 854 | nsIAtom *mAtom; |
michael@0 | 855 | // Auto length 2, because a decent fraction of these arrays ends up |
michael@0 | 856 | // with 2 elements, and each entry is cheap. |
michael@0 | 857 | nsAutoTArray<nsCSSSelector*, 2> mSelectors; |
michael@0 | 858 | }; |
michael@0 | 859 | |
michael@0 | 860 | static void |
michael@0 | 861 | AtomSelector_ClearEntry(PLDHashTable *table, PLDHashEntryHdr *hdr) |
michael@0 | 862 | { |
michael@0 | 863 | (static_cast<AtomSelectorEntry*>(hdr))->~AtomSelectorEntry(); |
michael@0 | 864 | } |
michael@0 | 865 | |
michael@0 | 866 | static bool |
michael@0 | 867 | AtomSelector_InitEntry(PLDHashTable *table, PLDHashEntryHdr *hdr, |
michael@0 | 868 | const void *key) |
michael@0 | 869 | { |
michael@0 | 870 | AtomSelectorEntry *entry = static_cast<AtomSelectorEntry*>(hdr); |
michael@0 | 871 | new (entry) AtomSelectorEntry(); |
michael@0 | 872 | entry->mAtom = const_cast<nsIAtom*>(static_cast<const nsIAtom*>(key)); |
michael@0 | 873 | return true; |
michael@0 | 874 | } |
michael@0 | 875 | |
michael@0 | 876 | static void |
michael@0 | 877 | AtomSelector_MoveEntry(PLDHashTable *table, const PLDHashEntryHdr *from, |
michael@0 | 878 | PLDHashEntryHdr *to) |
michael@0 | 879 | { |
michael@0 | 880 | NS_PRECONDITION(from != to, "This is not going to work!"); |
michael@0 | 881 | AtomSelectorEntry *oldEntry = |
michael@0 | 882 | const_cast<AtomSelectorEntry*>(static_cast<const AtomSelectorEntry*>(from)); |
michael@0 | 883 | AtomSelectorEntry *newEntry = new (to) AtomSelectorEntry(); |
michael@0 | 884 | newEntry->mAtom = oldEntry->mAtom; |
michael@0 | 885 | newEntry->mSelectors.SwapElements(oldEntry->mSelectors); |
michael@0 | 886 | oldEntry->~AtomSelectorEntry(); |
michael@0 | 887 | } |
michael@0 | 888 | |
michael@0 | 889 | static nsIAtom* |
michael@0 | 890 | AtomSelector_GetKey(PLDHashTable *table, const PLDHashEntryHdr *hdr) |
michael@0 | 891 | { |
michael@0 | 892 | const AtomSelectorEntry *entry = static_cast<const AtomSelectorEntry*>(hdr); |
michael@0 | 893 | return entry->mAtom; |
michael@0 | 894 | } |
michael@0 | 895 | |
michael@0 | 896 | // Case-sensitive ops. |
michael@0 | 897 | static const PLDHashTableOps AtomSelector_CSOps = { |
michael@0 | 898 | PL_DHashAllocTable, |
michael@0 | 899 | PL_DHashFreeTable, |
michael@0 | 900 | PL_DHashVoidPtrKeyStub, |
michael@0 | 901 | PL_DHashMatchEntryStub, |
michael@0 | 902 | AtomSelector_MoveEntry, |
michael@0 | 903 | AtomSelector_ClearEntry, |
michael@0 | 904 | PL_DHashFinalizeStub, |
michael@0 | 905 | AtomSelector_InitEntry |
michael@0 | 906 | }; |
michael@0 | 907 | |
michael@0 | 908 | // Case-insensitive ops. |
michael@0 | 909 | static const RuleHashTableOps AtomSelector_CIOps = { |
michael@0 | 910 | { |
michael@0 | 911 | PL_DHashAllocTable, |
michael@0 | 912 | PL_DHashFreeTable, |
michael@0 | 913 | RuleHash_CIHashKey, |
michael@0 | 914 | RuleHash_CIMatchEntry, |
michael@0 | 915 | AtomSelector_MoveEntry, |
michael@0 | 916 | AtomSelector_ClearEntry, |
michael@0 | 917 | PL_DHashFinalizeStub, |
michael@0 | 918 | AtomSelector_InitEntry |
michael@0 | 919 | }, |
michael@0 | 920 | AtomSelector_GetKey |
michael@0 | 921 | }; |
michael@0 | 922 | |
michael@0 | 923 | //-------------------------------- |
michael@0 | 924 | |
michael@0 | 925 | struct RuleCascadeData { |
michael@0 | 926 | RuleCascadeData(nsIAtom *aMedium, bool aQuirksMode) |
michael@0 | 927 | : mRuleHash(aQuirksMode), |
michael@0 | 928 | mStateSelectors(), |
michael@0 | 929 | mSelectorDocumentStates(0), |
michael@0 | 930 | mKeyframesRuleTable(16), |
michael@0 | 931 | mCacheKey(aMedium), |
michael@0 | 932 | mNext(nullptr), |
michael@0 | 933 | mQuirksMode(aQuirksMode) |
michael@0 | 934 | { |
michael@0 | 935 | // mAttributeSelectors is matching on the attribute _name_, not the value, |
michael@0 | 936 | // and we case-fold names at parse-time, so this is a case-sensitive match. |
michael@0 | 937 | PL_DHashTableInit(&mAttributeSelectors, &AtomSelector_CSOps, nullptr, |
michael@0 | 938 | sizeof(AtomSelectorEntry), 16); |
michael@0 | 939 | PL_DHashTableInit(&mAnonBoxRules, &RuleHash_TagTable_Ops, nullptr, |
michael@0 | 940 | sizeof(RuleHashTagTableEntry), 16); |
michael@0 | 941 | PL_DHashTableInit(&mIdSelectors, |
michael@0 | 942 | aQuirksMode ? &AtomSelector_CIOps.ops : |
michael@0 | 943 | &AtomSelector_CSOps, |
michael@0 | 944 | nullptr, sizeof(AtomSelectorEntry), 16); |
michael@0 | 945 | PL_DHashTableInit(&mClassSelectors, |
michael@0 | 946 | aQuirksMode ? &AtomSelector_CIOps.ops : |
michael@0 | 947 | &AtomSelector_CSOps, |
michael@0 | 948 | nullptr, sizeof(AtomSelectorEntry), 16); |
michael@0 | 949 | memset(mPseudoElementRuleHashes, 0, sizeof(mPseudoElementRuleHashes)); |
michael@0 | 950 | #ifdef MOZ_XUL |
michael@0 | 951 | PL_DHashTableInit(&mXULTreeRules, &RuleHash_TagTable_Ops, nullptr, |
michael@0 | 952 | sizeof(RuleHashTagTableEntry), 16); |
michael@0 | 953 | #endif |
michael@0 | 954 | } |
michael@0 | 955 | |
michael@0 | 956 | ~RuleCascadeData() |
michael@0 | 957 | { |
michael@0 | 958 | PL_DHashTableFinish(&mAttributeSelectors); |
michael@0 | 959 | PL_DHashTableFinish(&mAnonBoxRules); |
michael@0 | 960 | PL_DHashTableFinish(&mIdSelectors); |
michael@0 | 961 | PL_DHashTableFinish(&mClassSelectors); |
michael@0 | 962 | #ifdef MOZ_XUL |
michael@0 | 963 | PL_DHashTableFinish(&mXULTreeRules); |
michael@0 | 964 | #endif |
michael@0 | 965 | for (uint32_t i = 0; i < ArrayLength(mPseudoElementRuleHashes); ++i) { |
michael@0 | 966 | delete mPseudoElementRuleHashes[i]; |
michael@0 | 967 | } |
michael@0 | 968 | } |
michael@0 | 969 | |
michael@0 | 970 | size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const; |
michael@0 | 971 | |
michael@0 | 972 | RuleHash mRuleHash; |
michael@0 | 973 | RuleHash* |
michael@0 | 974 | mPseudoElementRuleHashes[nsCSSPseudoElements::ePseudo_PseudoElementCount]; |
michael@0 | 975 | nsTArray<nsCSSRuleProcessor::StateSelector> mStateSelectors; |
michael@0 | 976 | EventStates mSelectorDocumentStates; |
michael@0 | 977 | PLDHashTable mClassSelectors; |
michael@0 | 978 | PLDHashTable mIdSelectors; |
michael@0 | 979 | nsTArray<nsCSSSelector*> mPossiblyNegatedClassSelectors; |
michael@0 | 980 | nsTArray<nsCSSSelector*> mPossiblyNegatedIDSelectors; |
michael@0 | 981 | PLDHashTable mAttributeSelectors; |
michael@0 | 982 | PLDHashTable mAnonBoxRules; |
michael@0 | 983 | #ifdef MOZ_XUL |
michael@0 | 984 | PLDHashTable mXULTreeRules; |
michael@0 | 985 | #endif |
michael@0 | 986 | |
michael@0 | 987 | nsTArray<nsFontFaceRuleContainer> mFontFaceRules; |
michael@0 | 988 | nsTArray<nsCSSKeyframesRule*> mKeyframesRules; |
michael@0 | 989 | nsTArray<nsCSSFontFeatureValuesRule*> mFontFeatureValuesRules; |
michael@0 | 990 | nsTArray<nsCSSPageRule*> mPageRules; |
michael@0 | 991 | |
michael@0 | 992 | nsDataHashtable<nsStringHashKey, nsCSSKeyframesRule*> mKeyframesRuleTable; |
michael@0 | 993 | |
michael@0 | 994 | // Looks up or creates the appropriate list in |mAttributeSelectors|. |
michael@0 | 995 | // Returns null only on allocation failure. |
michael@0 | 996 | nsTArray<nsCSSSelector*>* AttributeListFor(nsIAtom* aAttribute); |
michael@0 | 997 | |
michael@0 | 998 | nsMediaQueryResultCacheKey mCacheKey; |
michael@0 | 999 | RuleCascadeData* mNext; // for a different medium |
michael@0 | 1000 | |
michael@0 | 1001 | const bool mQuirksMode; |
michael@0 | 1002 | }; |
michael@0 | 1003 | |
michael@0 | 1004 | static size_t |
michael@0 | 1005 | SizeOfSelectorsEntry(PLDHashEntryHdr* aHdr, MallocSizeOf aMallocSizeOf, void *) |
michael@0 | 1006 | { |
michael@0 | 1007 | AtomSelectorEntry* entry = static_cast<AtomSelectorEntry*>(aHdr); |
michael@0 | 1008 | return entry->mSelectors.SizeOfExcludingThis(aMallocSizeOf); |
michael@0 | 1009 | } |
michael@0 | 1010 | |
michael@0 | 1011 | size_t |
michael@0 | 1012 | RuleCascadeData::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const |
michael@0 | 1013 | { |
michael@0 | 1014 | size_t n = aMallocSizeOf(this); |
michael@0 | 1015 | |
michael@0 | 1016 | n += mRuleHash.SizeOfExcludingThis(aMallocSizeOf); |
michael@0 | 1017 | for (uint32_t i = 0; i < ArrayLength(mPseudoElementRuleHashes); ++i) { |
michael@0 | 1018 | if (mPseudoElementRuleHashes[i]) |
michael@0 | 1019 | n += mPseudoElementRuleHashes[i]->SizeOfIncludingThis(aMallocSizeOf); |
michael@0 | 1020 | } |
michael@0 | 1021 | |
michael@0 | 1022 | n += mStateSelectors.SizeOfExcludingThis(aMallocSizeOf); |
michael@0 | 1023 | |
michael@0 | 1024 | n += PL_DHashTableSizeOfExcludingThis(&mIdSelectors, |
michael@0 | 1025 | SizeOfSelectorsEntry, aMallocSizeOf); |
michael@0 | 1026 | n += PL_DHashTableSizeOfExcludingThis(&mClassSelectors, |
michael@0 | 1027 | SizeOfSelectorsEntry, aMallocSizeOf); |
michael@0 | 1028 | |
michael@0 | 1029 | n += mPossiblyNegatedClassSelectors.SizeOfExcludingThis(aMallocSizeOf); |
michael@0 | 1030 | n += mPossiblyNegatedIDSelectors.SizeOfExcludingThis(aMallocSizeOf); |
michael@0 | 1031 | |
michael@0 | 1032 | n += PL_DHashTableSizeOfExcludingThis(&mAttributeSelectors, |
michael@0 | 1033 | SizeOfSelectorsEntry, aMallocSizeOf); |
michael@0 | 1034 | n += PL_DHashTableSizeOfExcludingThis(&mAnonBoxRules, |
michael@0 | 1035 | SizeOfRuleHashTableEntry, aMallocSizeOf); |
michael@0 | 1036 | #ifdef MOZ_XUL |
michael@0 | 1037 | n += PL_DHashTableSizeOfExcludingThis(&mXULTreeRules, |
michael@0 | 1038 | SizeOfRuleHashTableEntry, aMallocSizeOf); |
michael@0 | 1039 | #endif |
michael@0 | 1040 | |
michael@0 | 1041 | n += mFontFaceRules.SizeOfExcludingThis(aMallocSizeOf); |
michael@0 | 1042 | n += mKeyframesRules.SizeOfExcludingThis(aMallocSizeOf); |
michael@0 | 1043 | n += mFontFeatureValuesRules.SizeOfExcludingThis(aMallocSizeOf); |
michael@0 | 1044 | n += mPageRules.SizeOfExcludingThis(aMallocSizeOf); |
michael@0 | 1045 | |
michael@0 | 1046 | return n; |
michael@0 | 1047 | } |
michael@0 | 1048 | |
michael@0 | 1049 | nsTArray<nsCSSSelector*>* |
michael@0 | 1050 | RuleCascadeData::AttributeListFor(nsIAtom* aAttribute) |
michael@0 | 1051 | { |
michael@0 | 1052 | AtomSelectorEntry *entry = |
michael@0 | 1053 | static_cast<AtomSelectorEntry*> |
michael@0 | 1054 | (PL_DHashTableOperate(&mAttributeSelectors, aAttribute, |
michael@0 | 1055 | PL_DHASH_ADD)); |
michael@0 | 1056 | if (!entry) |
michael@0 | 1057 | return nullptr; |
michael@0 | 1058 | return &entry->mSelectors; |
michael@0 | 1059 | } |
michael@0 | 1060 | |
michael@0 | 1061 | // ------------------------------- |
michael@0 | 1062 | // CSS Style rule processor implementation |
michael@0 | 1063 | // |
michael@0 | 1064 | |
michael@0 | 1065 | nsCSSRuleProcessor::nsCSSRuleProcessor(const sheet_array_type& aSheets, |
michael@0 | 1066 | uint8_t aSheetType, |
michael@0 | 1067 | Element* aScopeElement) |
michael@0 | 1068 | : mSheets(aSheets) |
michael@0 | 1069 | , mRuleCascades(nullptr) |
michael@0 | 1070 | , mLastPresContext(nullptr) |
michael@0 | 1071 | , mScopeElement(aScopeElement) |
michael@0 | 1072 | , mSheetType(aSheetType) |
michael@0 | 1073 | { |
michael@0 | 1074 | NS_ASSERTION(!!mScopeElement == (aSheetType == nsStyleSet::eScopedDocSheet), |
michael@0 | 1075 | "aScopeElement must be specified iff aSheetType is " |
michael@0 | 1076 | "eScopedDocSheet"); |
michael@0 | 1077 | for (sheet_array_type::size_type i = mSheets.Length(); i-- != 0; ) { |
michael@0 | 1078 | mSheets[i]->AddRuleProcessor(this); |
michael@0 | 1079 | } |
michael@0 | 1080 | } |
michael@0 | 1081 | |
michael@0 | 1082 | nsCSSRuleProcessor::~nsCSSRuleProcessor() |
michael@0 | 1083 | { |
michael@0 | 1084 | for (sheet_array_type::size_type i = mSheets.Length(); i-- != 0; ) { |
michael@0 | 1085 | mSheets[i]->DropRuleProcessor(this); |
michael@0 | 1086 | } |
michael@0 | 1087 | mSheets.Clear(); |
michael@0 | 1088 | ClearRuleCascades(); |
michael@0 | 1089 | } |
michael@0 | 1090 | |
michael@0 | 1091 | NS_IMPL_ISUPPORTS(nsCSSRuleProcessor, nsIStyleRuleProcessor) |
michael@0 | 1092 | |
michael@0 | 1093 | /* static */ nsresult |
michael@0 | 1094 | nsCSSRuleProcessor::Startup() |
michael@0 | 1095 | { |
michael@0 | 1096 | Preferences::AddBoolVarCache(&gSupportVisitedPseudo, VISITED_PSEUDO_PREF, |
michael@0 | 1097 | true); |
michael@0 | 1098 | |
michael@0 | 1099 | return NS_OK; |
michael@0 | 1100 | } |
michael@0 | 1101 | |
michael@0 | 1102 | static bool |
michael@0 | 1103 | InitSystemMetrics() |
michael@0 | 1104 | { |
michael@0 | 1105 | NS_ASSERTION(!sSystemMetrics, "already initialized"); |
michael@0 | 1106 | |
michael@0 | 1107 | sSystemMetrics = new nsTArray< nsCOMPtr<nsIAtom> >; |
michael@0 | 1108 | NS_ENSURE_TRUE(sSystemMetrics, false); |
michael@0 | 1109 | |
michael@0 | 1110 | /*************************************************************************** |
michael@0 | 1111 | * ANY METRICS ADDED HERE SHOULD ALSO BE ADDED AS MEDIA QUERIES IN * |
michael@0 | 1112 | * nsMediaFeatures.cpp * |
michael@0 | 1113 | ***************************************************************************/ |
michael@0 | 1114 | |
michael@0 | 1115 | int32_t metricResult = |
michael@0 | 1116 | LookAndFeel::GetInt(LookAndFeel::eIntID_ScrollArrowStyle); |
michael@0 | 1117 | if (metricResult & LookAndFeel::eScrollArrow_StartBackward) { |
michael@0 | 1118 | sSystemMetrics->AppendElement(nsGkAtoms::scrollbar_start_backward); |
michael@0 | 1119 | } |
michael@0 | 1120 | if (metricResult & LookAndFeel::eScrollArrow_StartForward) { |
michael@0 | 1121 | sSystemMetrics->AppendElement(nsGkAtoms::scrollbar_start_forward); |
michael@0 | 1122 | } |
michael@0 | 1123 | if (metricResult & LookAndFeel::eScrollArrow_EndBackward) { |
michael@0 | 1124 | sSystemMetrics->AppendElement(nsGkAtoms::scrollbar_end_backward); |
michael@0 | 1125 | } |
michael@0 | 1126 | if (metricResult & LookAndFeel::eScrollArrow_EndForward) { |
michael@0 | 1127 | sSystemMetrics->AppendElement(nsGkAtoms::scrollbar_end_forward); |
michael@0 | 1128 | } |
michael@0 | 1129 | |
michael@0 | 1130 | metricResult = |
michael@0 | 1131 | LookAndFeel::GetInt(LookAndFeel::eIntID_ScrollSliderStyle); |
michael@0 | 1132 | if (metricResult != LookAndFeel::eScrollThumbStyle_Normal) { |
michael@0 | 1133 | sSystemMetrics->AppendElement(nsGkAtoms::scrollbar_thumb_proportional); |
michael@0 | 1134 | } |
michael@0 | 1135 | |
michael@0 | 1136 | metricResult = |
michael@0 | 1137 | LookAndFeel::GetInt(LookAndFeel::eIntID_ImagesInMenus); |
michael@0 | 1138 | if (metricResult) { |
michael@0 | 1139 | sSystemMetrics->AppendElement(nsGkAtoms::images_in_menus); |
michael@0 | 1140 | } |
michael@0 | 1141 | |
michael@0 | 1142 | metricResult = |
michael@0 | 1143 | LookAndFeel::GetInt(LookAndFeel::eIntID_ImagesInButtons); |
michael@0 | 1144 | if (metricResult) { |
michael@0 | 1145 | sSystemMetrics->AppendElement(nsGkAtoms::images_in_buttons); |
michael@0 | 1146 | } |
michael@0 | 1147 | |
michael@0 | 1148 | metricResult = |
michael@0 | 1149 | LookAndFeel::GetInt(LookAndFeel::eIntID_UseOverlayScrollbars); |
michael@0 | 1150 | if (metricResult) { |
michael@0 | 1151 | sSystemMetrics->AppendElement(nsGkAtoms::overlay_scrollbars); |
michael@0 | 1152 | } |
michael@0 | 1153 | |
michael@0 | 1154 | metricResult = |
michael@0 | 1155 | LookAndFeel::GetInt(LookAndFeel::eIntID_MenuBarDrag); |
michael@0 | 1156 | if (metricResult) { |
michael@0 | 1157 | sSystemMetrics->AppendElement(nsGkAtoms::menubar_drag); |
michael@0 | 1158 | } |
michael@0 | 1159 | |
michael@0 | 1160 | nsresult rv = |
michael@0 | 1161 | LookAndFeel::GetInt(LookAndFeel::eIntID_WindowsDefaultTheme, &metricResult); |
michael@0 | 1162 | if (NS_SUCCEEDED(rv) && metricResult) { |
michael@0 | 1163 | sSystemMetrics->AppendElement(nsGkAtoms::windows_default_theme); |
michael@0 | 1164 | } |
michael@0 | 1165 | |
michael@0 | 1166 | rv = LookAndFeel::GetInt(LookAndFeel::eIntID_MacGraphiteTheme, &metricResult); |
michael@0 | 1167 | if (NS_SUCCEEDED(rv) && metricResult) { |
michael@0 | 1168 | sSystemMetrics->AppendElement(nsGkAtoms::mac_graphite_theme); |
michael@0 | 1169 | } |
michael@0 | 1170 | |
michael@0 | 1171 | rv = LookAndFeel::GetInt(LookAndFeel::eIntID_MacLionTheme, &metricResult); |
michael@0 | 1172 | if (NS_SUCCEEDED(rv) && metricResult) { |
michael@0 | 1173 | sSystemMetrics->AppendElement(nsGkAtoms::mac_lion_theme); |
michael@0 | 1174 | } |
michael@0 | 1175 | |
michael@0 | 1176 | rv = LookAndFeel::GetInt(LookAndFeel::eIntID_DWMCompositor, &metricResult); |
michael@0 | 1177 | if (NS_SUCCEEDED(rv) && metricResult) { |
michael@0 | 1178 | sSystemMetrics->AppendElement(nsGkAtoms::windows_compositor); |
michael@0 | 1179 | } |
michael@0 | 1180 | |
michael@0 | 1181 | rv = LookAndFeel::GetInt(LookAndFeel::eIntID_WindowsGlass, &metricResult); |
michael@0 | 1182 | if (NS_SUCCEEDED(rv) && metricResult) { |
michael@0 | 1183 | sSystemMetrics->AppendElement(nsGkAtoms::windows_glass); |
michael@0 | 1184 | } |
michael@0 | 1185 | |
michael@0 | 1186 | rv = LookAndFeel::GetInt(LookAndFeel::eIntID_ColorPickerAvailable, &metricResult); |
michael@0 | 1187 | if (NS_SUCCEEDED(rv) && metricResult) { |
michael@0 | 1188 | sSystemMetrics->AppendElement(nsGkAtoms::color_picker_available); |
michael@0 | 1189 | } |
michael@0 | 1190 | |
michael@0 | 1191 | rv = LookAndFeel::GetInt(LookAndFeel::eIntID_WindowsClassic, &metricResult); |
michael@0 | 1192 | if (NS_SUCCEEDED(rv) && metricResult) { |
michael@0 | 1193 | sSystemMetrics->AppendElement(nsGkAtoms::windows_classic); |
michael@0 | 1194 | } |
michael@0 | 1195 | |
michael@0 | 1196 | rv = LookAndFeel::GetInt(LookAndFeel::eIntID_TouchEnabled, &metricResult); |
michael@0 | 1197 | if (NS_SUCCEEDED(rv) && metricResult) { |
michael@0 | 1198 | sSystemMetrics->AppendElement(nsGkAtoms::touch_enabled); |
michael@0 | 1199 | } |
michael@0 | 1200 | |
michael@0 | 1201 | rv = LookAndFeel::GetInt(LookAndFeel::eIntID_SwipeAnimationEnabled, |
michael@0 | 1202 | &metricResult); |
michael@0 | 1203 | if (NS_SUCCEEDED(rv) && metricResult) { |
michael@0 | 1204 | sSystemMetrics->AppendElement(nsGkAtoms::swipe_animation_enabled); |
michael@0 | 1205 | } |
michael@0 | 1206 | |
michael@0 | 1207 | rv = LookAndFeel::GetInt(LookAndFeel::eIntID_PhysicalHomeButton, |
michael@0 | 1208 | &metricResult); |
michael@0 | 1209 | if (NS_SUCCEEDED(rv) && metricResult) { |
michael@0 | 1210 | sSystemMetrics->AppendElement(nsGkAtoms::physical_home_button); |
michael@0 | 1211 | } |
michael@0 | 1212 | |
michael@0 | 1213 | #ifdef XP_WIN |
michael@0 | 1214 | if (NS_SUCCEEDED( |
michael@0 | 1215 | LookAndFeel::GetInt(LookAndFeel::eIntID_WindowsThemeIdentifier, |
michael@0 | 1216 | &metricResult))) { |
michael@0 | 1217 | nsCSSRuleProcessor::SetWindowsThemeIdentifier(static_cast<uint8_t>(metricResult)); |
michael@0 | 1218 | switch(metricResult) { |
michael@0 | 1219 | case LookAndFeel::eWindowsTheme_Aero: |
michael@0 | 1220 | sSystemMetrics->AppendElement(nsGkAtoms::windows_theme_aero); |
michael@0 | 1221 | break; |
michael@0 | 1222 | case LookAndFeel::eWindowsTheme_AeroLite: |
michael@0 | 1223 | sSystemMetrics->AppendElement(nsGkAtoms::windows_theme_aero_lite); |
michael@0 | 1224 | break; |
michael@0 | 1225 | case LookAndFeel::eWindowsTheme_LunaBlue: |
michael@0 | 1226 | sSystemMetrics->AppendElement(nsGkAtoms::windows_theme_luna_blue); |
michael@0 | 1227 | break; |
michael@0 | 1228 | case LookAndFeel::eWindowsTheme_LunaOlive: |
michael@0 | 1229 | sSystemMetrics->AppendElement(nsGkAtoms::windows_theme_luna_olive); |
michael@0 | 1230 | break; |
michael@0 | 1231 | case LookAndFeel::eWindowsTheme_LunaSilver: |
michael@0 | 1232 | sSystemMetrics->AppendElement(nsGkAtoms::windows_theme_luna_silver); |
michael@0 | 1233 | break; |
michael@0 | 1234 | case LookAndFeel::eWindowsTheme_Royale: |
michael@0 | 1235 | sSystemMetrics->AppendElement(nsGkAtoms::windows_theme_royale); |
michael@0 | 1236 | break; |
michael@0 | 1237 | case LookAndFeel::eWindowsTheme_Zune: |
michael@0 | 1238 | sSystemMetrics->AppendElement(nsGkAtoms::windows_theme_zune); |
michael@0 | 1239 | break; |
michael@0 | 1240 | case LookAndFeel::eWindowsTheme_Generic: |
michael@0 | 1241 | sSystemMetrics->AppendElement(nsGkAtoms::windows_theme_generic); |
michael@0 | 1242 | break; |
michael@0 | 1243 | } |
michael@0 | 1244 | } |
michael@0 | 1245 | #endif |
michael@0 | 1246 | |
michael@0 | 1247 | return true; |
michael@0 | 1248 | } |
michael@0 | 1249 | |
michael@0 | 1250 | /* static */ void |
michael@0 | 1251 | nsCSSRuleProcessor::FreeSystemMetrics() |
michael@0 | 1252 | { |
michael@0 | 1253 | delete sSystemMetrics; |
michael@0 | 1254 | sSystemMetrics = nullptr; |
michael@0 | 1255 | } |
michael@0 | 1256 | |
michael@0 | 1257 | /* static */ void |
michael@0 | 1258 | nsCSSRuleProcessor::Shutdown() |
michael@0 | 1259 | { |
michael@0 | 1260 | FreeSystemMetrics(); |
michael@0 | 1261 | } |
michael@0 | 1262 | |
michael@0 | 1263 | /* static */ bool |
michael@0 | 1264 | nsCSSRuleProcessor::HasSystemMetric(nsIAtom* aMetric) |
michael@0 | 1265 | { |
michael@0 | 1266 | if (!sSystemMetrics && !InitSystemMetrics()) { |
michael@0 | 1267 | return false; |
michael@0 | 1268 | } |
michael@0 | 1269 | return sSystemMetrics->IndexOf(aMetric) != sSystemMetrics->NoIndex; |
michael@0 | 1270 | } |
michael@0 | 1271 | |
michael@0 | 1272 | #ifdef XP_WIN |
michael@0 | 1273 | /* static */ uint8_t |
michael@0 | 1274 | nsCSSRuleProcessor::GetWindowsThemeIdentifier() |
michael@0 | 1275 | { |
michael@0 | 1276 | if (!sSystemMetrics) |
michael@0 | 1277 | InitSystemMetrics(); |
michael@0 | 1278 | return sWinThemeId; |
michael@0 | 1279 | } |
michael@0 | 1280 | #endif |
michael@0 | 1281 | |
michael@0 | 1282 | /* static */ |
michael@0 | 1283 | EventStates |
michael@0 | 1284 | nsCSSRuleProcessor::GetContentState(Element* aElement, const TreeMatchContext& aTreeMatchContext) |
michael@0 | 1285 | { |
michael@0 | 1286 | EventStates state = aElement->StyleState(); |
michael@0 | 1287 | |
michael@0 | 1288 | // If we are not supposed to mark visited links as such, be sure to |
michael@0 | 1289 | // flip the bits appropriately. We want to do this here, rather |
michael@0 | 1290 | // than in GetContentStateForVisitedHandling, so that we don't |
michael@0 | 1291 | // expose that :visited support is disabled to the Web page. |
michael@0 | 1292 | if (state.HasState(NS_EVENT_STATE_VISITED) && |
michael@0 | 1293 | (!gSupportVisitedPseudo || |
michael@0 | 1294 | aElement->OwnerDoc()->IsBeingUsedAsImage() || |
michael@0 | 1295 | aTreeMatchContext.mUsingPrivateBrowsing)) { |
michael@0 | 1296 | state &= ~NS_EVENT_STATE_VISITED; |
michael@0 | 1297 | state |= NS_EVENT_STATE_UNVISITED; |
michael@0 | 1298 | } |
michael@0 | 1299 | return state; |
michael@0 | 1300 | } |
michael@0 | 1301 | |
michael@0 | 1302 | /* static */ |
michael@0 | 1303 | bool |
michael@0 | 1304 | nsCSSRuleProcessor::IsLink(Element* aElement) |
michael@0 | 1305 | { |
michael@0 | 1306 | EventStates state = aElement->StyleState(); |
michael@0 | 1307 | return state.HasAtLeastOneOfStates(NS_EVENT_STATE_VISITED | NS_EVENT_STATE_UNVISITED); |
michael@0 | 1308 | } |
michael@0 | 1309 | |
michael@0 | 1310 | /* static */ |
michael@0 | 1311 | EventStates |
michael@0 | 1312 | nsCSSRuleProcessor::GetContentStateForVisitedHandling( |
michael@0 | 1313 | Element* aElement, |
michael@0 | 1314 | const TreeMatchContext& aTreeMatchContext, |
michael@0 | 1315 | nsRuleWalker::VisitedHandlingType aVisitedHandling, |
michael@0 | 1316 | bool aIsRelevantLink) |
michael@0 | 1317 | { |
michael@0 | 1318 | EventStates contentState = GetContentState(aElement, aTreeMatchContext); |
michael@0 | 1319 | if (contentState.HasAtLeastOneOfStates(NS_EVENT_STATE_VISITED | NS_EVENT_STATE_UNVISITED)) { |
michael@0 | 1320 | NS_ABORT_IF_FALSE(IsLink(aElement), "IsLink() should match state"); |
michael@0 | 1321 | contentState &= ~(NS_EVENT_STATE_VISITED | NS_EVENT_STATE_UNVISITED); |
michael@0 | 1322 | if (aIsRelevantLink) { |
michael@0 | 1323 | switch (aVisitedHandling) { |
michael@0 | 1324 | case nsRuleWalker::eRelevantLinkUnvisited: |
michael@0 | 1325 | contentState |= NS_EVENT_STATE_UNVISITED; |
michael@0 | 1326 | break; |
michael@0 | 1327 | case nsRuleWalker::eRelevantLinkVisited: |
michael@0 | 1328 | contentState |= NS_EVENT_STATE_VISITED; |
michael@0 | 1329 | break; |
michael@0 | 1330 | case nsRuleWalker::eLinksVisitedOrUnvisited: |
michael@0 | 1331 | contentState |= NS_EVENT_STATE_UNVISITED | NS_EVENT_STATE_VISITED; |
michael@0 | 1332 | break; |
michael@0 | 1333 | } |
michael@0 | 1334 | } else { |
michael@0 | 1335 | contentState |= NS_EVENT_STATE_UNVISITED; |
michael@0 | 1336 | } |
michael@0 | 1337 | } |
michael@0 | 1338 | return contentState; |
michael@0 | 1339 | } |
michael@0 | 1340 | |
michael@0 | 1341 | /** |
michael@0 | 1342 | * A |NodeMatchContext| has data about matching a selector (without |
michael@0 | 1343 | * combinators) against a single node. It contains only input to the |
michael@0 | 1344 | * matching. |
michael@0 | 1345 | * |
michael@0 | 1346 | * Unlike |RuleProcessorData|, which is similar, a |NodeMatchContext| |
michael@0 | 1347 | * can vary depending on the selector matching process. In other words, |
michael@0 | 1348 | * there might be multiple NodeMatchContexts corresponding to a single |
michael@0 | 1349 | * node, but only one possible RuleProcessorData. |
michael@0 | 1350 | */ |
michael@0 | 1351 | struct NodeMatchContext { |
michael@0 | 1352 | // In order to implement nsCSSRuleProcessor::HasStateDependentStyle, |
michael@0 | 1353 | // we need to be able to see if a node might match an |
michael@0 | 1354 | // event-state-dependent selector for any value of that event state. |
michael@0 | 1355 | // So mStateMask contains the states that should NOT be tested. |
michael@0 | 1356 | // |
michael@0 | 1357 | // NOTE: For |mStateMask| to work correctly, it's important that any |
michael@0 | 1358 | // change that changes multiple state bits include all those state |
michael@0 | 1359 | // bits in the notification. Otherwise, if multiple states change but |
michael@0 | 1360 | // we do separate notifications then we might determine the style is |
michael@0 | 1361 | // not state-dependent when it really is (e.g., determining that a |
michael@0 | 1362 | // :hover:active rule no longer matches when both states are unset). |
michael@0 | 1363 | const EventStates mStateMask; |
michael@0 | 1364 | |
michael@0 | 1365 | // Is this link the unique link whose visitedness can affect the style |
michael@0 | 1366 | // of the node being matched? (That link is the nearest link to the |
michael@0 | 1367 | // node being matched that is itself or an ancestor.) |
michael@0 | 1368 | // |
michael@0 | 1369 | // Always false when TreeMatchContext::mForStyling is false. (We |
michael@0 | 1370 | // could figure it out for SelectorListMatches, but we're starting |
michael@0 | 1371 | // from the middle of the selector list when doing |
michael@0 | 1372 | // Has{Attribute,State}DependentStyle, so we can't tell. So when |
michael@0 | 1373 | // mForStyling is false, we have to assume we don't know.) |
michael@0 | 1374 | const bool mIsRelevantLink; |
michael@0 | 1375 | |
michael@0 | 1376 | NodeMatchContext(EventStates aStateMask, bool aIsRelevantLink) |
michael@0 | 1377 | : mStateMask(aStateMask) |
michael@0 | 1378 | , mIsRelevantLink(aIsRelevantLink) |
michael@0 | 1379 | { |
michael@0 | 1380 | } |
michael@0 | 1381 | }; |
michael@0 | 1382 | |
michael@0 | 1383 | static bool ValueIncludes(const nsSubstring& aValueList, |
michael@0 | 1384 | const nsSubstring& aValue, |
michael@0 | 1385 | const nsStringComparator& aComparator) |
michael@0 | 1386 | { |
michael@0 | 1387 | const char16_t *p = aValueList.BeginReading(), |
michael@0 | 1388 | *p_end = aValueList.EndReading(); |
michael@0 | 1389 | |
michael@0 | 1390 | while (p < p_end) { |
michael@0 | 1391 | // skip leading space |
michael@0 | 1392 | while (p != p_end && nsContentUtils::IsHTMLWhitespace(*p)) |
michael@0 | 1393 | ++p; |
michael@0 | 1394 | |
michael@0 | 1395 | const char16_t *val_start = p; |
michael@0 | 1396 | |
michael@0 | 1397 | // look for space or end |
michael@0 | 1398 | while (p != p_end && !nsContentUtils::IsHTMLWhitespace(*p)) |
michael@0 | 1399 | ++p; |
michael@0 | 1400 | |
michael@0 | 1401 | const char16_t *val_end = p; |
michael@0 | 1402 | |
michael@0 | 1403 | if (val_start < val_end && |
michael@0 | 1404 | aValue.Equals(Substring(val_start, val_end), aComparator)) |
michael@0 | 1405 | return true; |
michael@0 | 1406 | |
michael@0 | 1407 | ++p; // we know the next character is not whitespace |
michael@0 | 1408 | } |
michael@0 | 1409 | return false; |
michael@0 | 1410 | } |
michael@0 | 1411 | |
michael@0 | 1412 | // Return whether we should apply a "global" (i.e., universal-tag) |
michael@0 | 1413 | // selector for event states in quirks mode. Note that |
michael@0 | 1414 | // |IsLink()| is checked separately by the caller, so we return |
michael@0 | 1415 | // false for |nsGkAtoms::a|, which here means a named anchor. |
michael@0 | 1416 | inline bool IsQuirkEventSensitive(nsIAtom *aContentTag) |
michael@0 | 1417 | { |
michael@0 | 1418 | return bool ((nsGkAtoms::button == aContentTag) || |
michael@0 | 1419 | (nsGkAtoms::img == aContentTag) || |
michael@0 | 1420 | (nsGkAtoms::input == aContentTag) || |
michael@0 | 1421 | (nsGkAtoms::label == aContentTag) || |
michael@0 | 1422 | (nsGkAtoms::select == aContentTag) || |
michael@0 | 1423 | (nsGkAtoms::textarea == aContentTag)); |
michael@0 | 1424 | } |
michael@0 | 1425 | |
michael@0 | 1426 | |
michael@0 | 1427 | static inline bool |
michael@0 | 1428 | IsSignificantChild(nsIContent* aChild, bool aTextIsSignificant, |
michael@0 | 1429 | bool aWhitespaceIsSignificant) |
michael@0 | 1430 | { |
michael@0 | 1431 | return nsStyleUtil::IsSignificantChild(aChild, aTextIsSignificant, |
michael@0 | 1432 | aWhitespaceIsSignificant); |
michael@0 | 1433 | } |
michael@0 | 1434 | |
michael@0 | 1435 | // This function is to be called once we have fetched a value for an attribute |
michael@0 | 1436 | // whose namespace and name match those of aAttrSelector. This function |
michael@0 | 1437 | // performs comparisons on the value only, based on aAttrSelector->mFunction. |
michael@0 | 1438 | static bool AttrMatchesValue(const nsAttrSelector* aAttrSelector, |
michael@0 | 1439 | const nsString& aValue, bool isHTML) |
michael@0 | 1440 | { |
michael@0 | 1441 | NS_PRECONDITION(aAttrSelector, "Must have an attribute selector"); |
michael@0 | 1442 | |
michael@0 | 1443 | // http://lists.w3.org/Archives/Public/www-style/2008Apr/0038.html |
michael@0 | 1444 | // *= (CONTAINSMATCH) ~= (INCLUDES) ^= (BEGINSMATCH) $= (ENDSMATCH) |
michael@0 | 1445 | // all accept the empty string, but match nothing. |
michael@0 | 1446 | if (aAttrSelector->mValue.IsEmpty() && |
michael@0 | 1447 | (aAttrSelector->mFunction == NS_ATTR_FUNC_INCLUDES || |
michael@0 | 1448 | aAttrSelector->mFunction == NS_ATTR_FUNC_ENDSMATCH || |
michael@0 | 1449 | aAttrSelector->mFunction == NS_ATTR_FUNC_BEGINSMATCH || |
michael@0 | 1450 | aAttrSelector->mFunction == NS_ATTR_FUNC_CONTAINSMATCH)) |
michael@0 | 1451 | return false; |
michael@0 | 1452 | |
michael@0 | 1453 | const nsDefaultStringComparator defaultComparator; |
michael@0 | 1454 | const nsASCIICaseInsensitiveStringComparator ciComparator; |
michael@0 | 1455 | const nsStringComparator& comparator = |
michael@0 | 1456 | (aAttrSelector->mCaseSensitive || !isHTML) |
michael@0 | 1457 | ? static_cast<const nsStringComparator&>(defaultComparator) |
michael@0 | 1458 | : static_cast<const nsStringComparator&>(ciComparator); |
michael@0 | 1459 | |
michael@0 | 1460 | switch (aAttrSelector->mFunction) { |
michael@0 | 1461 | case NS_ATTR_FUNC_EQUALS: |
michael@0 | 1462 | return aValue.Equals(aAttrSelector->mValue, comparator); |
michael@0 | 1463 | case NS_ATTR_FUNC_INCLUDES: |
michael@0 | 1464 | return ValueIncludes(aValue, aAttrSelector->mValue, comparator); |
michael@0 | 1465 | case NS_ATTR_FUNC_DASHMATCH: |
michael@0 | 1466 | return nsStyleUtil::DashMatchCompare(aValue, aAttrSelector->mValue, comparator); |
michael@0 | 1467 | case NS_ATTR_FUNC_ENDSMATCH: |
michael@0 | 1468 | return StringEndsWith(aValue, aAttrSelector->mValue, comparator); |
michael@0 | 1469 | case NS_ATTR_FUNC_BEGINSMATCH: |
michael@0 | 1470 | return StringBeginsWith(aValue, aAttrSelector->mValue, comparator); |
michael@0 | 1471 | case NS_ATTR_FUNC_CONTAINSMATCH: |
michael@0 | 1472 | return FindInReadable(aAttrSelector->mValue, aValue, comparator); |
michael@0 | 1473 | default: |
michael@0 | 1474 | NS_NOTREACHED("Shouldn't be ending up here"); |
michael@0 | 1475 | return false; |
michael@0 | 1476 | } |
michael@0 | 1477 | } |
michael@0 | 1478 | |
michael@0 | 1479 | static inline bool |
michael@0 | 1480 | edgeChildMatches(Element* aElement, TreeMatchContext& aTreeMatchContext, |
michael@0 | 1481 | bool checkFirst, bool checkLast) |
michael@0 | 1482 | { |
michael@0 | 1483 | nsIContent *parent = aElement->GetParent(); |
michael@0 | 1484 | if (!parent) { |
michael@0 | 1485 | return false; |
michael@0 | 1486 | } |
michael@0 | 1487 | |
michael@0 | 1488 | if (aTreeMatchContext.mForStyling) |
michael@0 | 1489 | parent->SetFlags(NODE_HAS_EDGE_CHILD_SELECTOR); |
michael@0 | 1490 | |
michael@0 | 1491 | return (!checkFirst || |
michael@0 | 1492 | aTreeMatchContext.mNthIndexCache. |
michael@0 | 1493 | GetNthIndex(aElement, false, false, true) == 1) && |
michael@0 | 1494 | (!checkLast || |
michael@0 | 1495 | aTreeMatchContext.mNthIndexCache. |
michael@0 | 1496 | GetNthIndex(aElement, false, true, true) == 1); |
michael@0 | 1497 | } |
michael@0 | 1498 | |
michael@0 | 1499 | static inline bool |
michael@0 | 1500 | nthChildGenericMatches(Element* aElement, |
michael@0 | 1501 | TreeMatchContext& aTreeMatchContext, |
michael@0 | 1502 | nsPseudoClassList* pseudoClass, |
michael@0 | 1503 | bool isOfType, bool isFromEnd) |
michael@0 | 1504 | { |
michael@0 | 1505 | nsIContent *parent = aElement->GetParent(); |
michael@0 | 1506 | if (!parent) { |
michael@0 | 1507 | return false; |
michael@0 | 1508 | } |
michael@0 | 1509 | |
michael@0 | 1510 | if (aTreeMatchContext.mForStyling) { |
michael@0 | 1511 | if (isFromEnd) |
michael@0 | 1512 | parent->SetFlags(NODE_HAS_SLOW_SELECTOR); |
michael@0 | 1513 | else |
michael@0 | 1514 | parent->SetFlags(NODE_HAS_SLOW_SELECTOR_LATER_SIBLINGS); |
michael@0 | 1515 | } |
michael@0 | 1516 | |
michael@0 | 1517 | const int32_t index = aTreeMatchContext.mNthIndexCache. |
michael@0 | 1518 | GetNthIndex(aElement, isOfType, isFromEnd, false); |
michael@0 | 1519 | if (index <= 0) { |
michael@0 | 1520 | // Node is anonymous content (not really a child of its parent). |
michael@0 | 1521 | return false; |
michael@0 | 1522 | } |
michael@0 | 1523 | |
michael@0 | 1524 | const int32_t a = pseudoClass->u.mNumbers[0]; |
michael@0 | 1525 | const int32_t b = pseudoClass->u.mNumbers[1]; |
michael@0 | 1526 | // result should be true if there exists n >= 0 such that |
michael@0 | 1527 | // a * n + b == index. |
michael@0 | 1528 | if (a == 0) { |
michael@0 | 1529 | return b == index; |
michael@0 | 1530 | } |
michael@0 | 1531 | |
michael@0 | 1532 | // Integer division in C does truncation (towards 0). So |
michael@0 | 1533 | // check that the result is nonnegative, and that there was no |
michael@0 | 1534 | // truncation. |
michael@0 | 1535 | const int32_t n = (index - b) / a; |
michael@0 | 1536 | return n >= 0 && (a * n == index - b); |
michael@0 | 1537 | } |
michael@0 | 1538 | |
michael@0 | 1539 | static inline bool |
michael@0 | 1540 | edgeOfTypeMatches(Element* aElement, TreeMatchContext& aTreeMatchContext, |
michael@0 | 1541 | bool checkFirst, bool checkLast) |
michael@0 | 1542 | { |
michael@0 | 1543 | nsIContent *parent = aElement->GetParent(); |
michael@0 | 1544 | if (!parent) { |
michael@0 | 1545 | return false; |
michael@0 | 1546 | } |
michael@0 | 1547 | |
michael@0 | 1548 | if (aTreeMatchContext.mForStyling) { |
michael@0 | 1549 | if (checkLast) |
michael@0 | 1550 | parent->SetFlags(NODE_HAS_SLOW_SELECTOR); |
michael@0 | 1551 | else |
michael@0 | 1552 | parent->SetFlags(NODE_HAS_SLOW_SELECTOR_LATER_SIBLINGS); |
michael@0 | 1553 | } |
michael@0 | 1554 | |
michael@0 | 1555 | return (!checkFirst || |
michael@0 | 1556 | aTreeMatchContext.mNthIndexCache. |
michael@0 | 1557 | GetNthIndex(aElement, true, false, true) == 1) && |
michael@0 | 1558 | (!checkLast || |
michael@0 | 1559 | aTreeMatchContext.mNthIndexCache. |
michael@0 | 1560 | GetNthIndex(aElement, true, true, true) == 1); |
michael@0 | 1561 | } |
michael@0 | 1562 | |
michael@0 | 1563 | static inline bool |
michael@0 | 1564 | checkGenericEmptyMatches(Element* aElement, |
michael@0 | 1565 | TreeMatchContext& aTreeMatchContext, |
michael@0 | 1566 | bool isWhitespaceSignificant) |
michael@0 | 1567 | { |
michael@0 | 1568 | nsIContent *child = nullptr; |
michael@0 | 1569 | int32_t index = -1; |
michael@0 | 1570 | |
michael@0 | 1571 | if (aTreeMatchContext.mForStyling) |
michael@0 | 1572 | aElement->SetFlags(NODE_HAS_EMPTY_SELECTOR); |
michael@0 | 1573 | |
michael@0 | 1574 | do { |
michael@0 | 1575 | child = aElement->GetChildAt(++index); |
michael@0 | 1576 | // stop at first non-comment (and non-whitespace for |
michael@0 | 1577 | // :-moz-only-whitespace) node |
michael@0 | 1578 | } while (child && !IsSignificantChild(child, true, isWhitespaceSignificant)); |
michael@0 | 1579 | return (child == nullptr); |
michael@0 | 1580 | } |
michael@0 | 1581 | |
michael@0 | 1582 | // Arrays of the states that are relevant for various pseudoclasses. |
michael@0 | 1583 | static const EventStates sPseudoClassStateDependences[] = { |
michael@0 | 1584 | #define CSS_PSEUDO_CLASS(_name, _value, _pref) \ |
michael@0 | 1585 | EventStates(), |
michael@0 | 1586 | #define CSS_STATE_DEPENDENT_PSEUDO_CLASS(_name, _value, _pref, _states) \ |
michael@0 | 1587 | _states, |
michael@0 | 1588 | #include "nsCSSPseudoClassList.h" |
michael@0 | 1589 | #undef CSS_STATE_DEPENDENT_PSEUDO_CLASS |
michael@0 | 1590 | #undef CSS_PSEUDO_CLASS |
michael@0 | 1591 | // Add more entries for our fake values to make sure we can't |
michael@0 | 1592 | // index out of bounds into this array no matter what. |
michael@0 | 1593 | EventStates(), |
michael@0 | 1594 | EventStates() |
michael@0 | 1595 | }; |
michael@0 | 1596 | |
michael@0 | 1597 | static const EventStates sPseudoClassStates[] = { |
michael@0 | 1598 | #define CSS_PSEUDO_CLASS(_name, _value, _pref) \ |
michael@0 | 1599 | EventStates(), |
michael@0 | 1600 | #define CSS_STATE_PSEUDO_CLASS(_name, _value, _pref, _states) \ |
michael@0 | 1601 | _states, |
michael@0 | 1602 | #include "nsCSSPseudoClassList.h" |
michael@0 | 1603 | #undef CSS_STATE_PSEUDO_CLASS |
michael@0 | 1604 | #undef CSS_PSEUDO_CLASS |
michael@0 | 1605 | // Add more entries for our fake values to make sure we can't |
michael@0 | 1606 | // index out of bounds into this array no matter what. |
michael@0 | 1607 | EventStates(), |
michael@0 | 1608 | EventStates() |
michael@0 | 1609 | }; |
michael@0 | 1610 | static_assert(MOZ_ARRAY_LENGTH(sPseudoClassStates) == |
michael@0 | 1611 | nsCSSPseudoClasses::ePseudoClass_NotPseudoClass + 1, |
michael@0 | 1612 | "ePseudoClass_NotPseudoClass is no longer at the end of" |
michael@0 | 1613 | "sPseudoClassStates"); |
michael@0 | 1614 | |
michael@0 | 1615 | static bool |
michael@0 | 1616 | StateSelectorMatches(Element* aElement, |
michael@0 | 1617 | nsCSSSelector* aSelector, |
michael@0 | 1618 | NodeMatchContext& aNodeMatchContext, |
michael@0 | 1619 | TreeMatchContext& aTreeMatchContext, |
michael@0 | 1620 | bool* const aDependence, |
michael@0 | 1621 | EventStates aStatesToCheck) |
michael@0 | 1622 | { |
michael@0 | 1623 | NS_PRECONDITION(!aStatesToCheck.IsEmpty(), |
michael@0 | 1624 | "should only need to call StateSelectorMatches if " |
michael@0 | 1625 | "aStatesToCheck is not empty"); |
michael@0 | 1626 | |
michael@0 | 1627 | const bool isNegated = aDependence != nullptr; |
michael@0 | 1628 | |
michael@0 | 1629 | // Bit-based pseudo-classes |
michael@0 | 1630 | if (aStatesToCheck.HasAtLeastOneOfStates(NS_EVENT_STATE_HOVER | NS_EVENT_STATE_ACTIVE) && |
michael@0 | 1631 | aTreeMatchContext.mCompatMode == eCompatibility_NavQuirks && |
michael@0 | 1632 | // global selector: |
michael@0 | 1633 | !aSelector->HasTagSelector() && !aSelector->mIDList && |
michael@0 | 1634 | !aSelector->mClassList && !aSelector->mAttrList && |
michael@0 | 1635 | // This (or the other way around) both make :not() asymmetric |
michael@0 | 1636 | // in quirks mode (and it's hard to work around since we're |
michael@0 | 1637 | // testing the current mNegations, not the first |
michael@0 | 1638 | // (unnegated)). This at least makes it closer to the spec. |
michael@0 | 1639 | !isNegated && |
michael@0 | 1640 | // important for |IsQuirkEventSensitive|: |
michael@0 | 1641 | aElement->IsHTML() && !nsCSSRuleProcessor::IsLink(aElement) && |
michael@0 | 1642 | !IsQuirkEventSensitive(aElement->Tag())) { |
michael@0 | 1643 | // In quirks mode, only make certain elements sensitive to |
michael@0 | 1644 | // selectors ":hover" and ":active". |
michael@0 | 1645 | return false; |
michael@0 | 1646 | } |
michael@0 | 1647 | |
michael@0 | 1648 | if (aTreeMatchContext.mForStyling && |
michael@0 | 1649 | aStatesToCheck.HasAtLeastOneOfStates(NS_EVENT_STATE_HOVER)) { |
michael@0 | 1650 | // Mark the element as having :hover-dependent style |
michael@0 | 1651 | aElement->SetHasRelevantHoverRules(); |
michael@0 | 1652 | } |
michael@0 | 1653 | |
michael@0 | 1654 | if (aNodeMatchContext.mStateMask.HasAtLeastOneOfStates(aStatesToCheck)) { |
michael@0 | 1655 | if (aDependence) { |
michael@0 | 1656 | *aDependence = true; |
michael@0 | 1657 | } |
michael@0 | 1658 | } else { |
michael@0 | 1659 | EventStates contentState = |
michael@0 | 1660 | nsCSSRuleProcessor::GetContentStateForVisitedHandling( |
michael@0 | 1661 | aElement, |
michael@0 | 1662 | aTreeMatchContext, |
michael@0 | 1663 | aTreeMatchContext.VisitedHandling(), |
michael@0 | 1664 | aNodeMatchContext.mIsRelevantLink); |
michael@0 | 1665 | if (!contentState.HasAtLeastOneOfStates(aStatesToCheck)) { |
michael@0 | 1666 | return false; |
michael@0 | 1667 | } |
michael@0 | 1668 | } |
michael@0 | 1669 | |
michael@0 | 1670 | return true; |
michael@0 | 1671 | } |
michael@0 | 1672 | |
michael@0 | 1673 | static bool |
michael@0 | 1674 | StateSelectorMatches(Element* aElement, |
michael@0 | 1675 | nsCSSSelector* aSelector, |
michael@0 | 1676 | NodeMatchContext& aNodeMatchContext, |
michael@0 | 1677 | TreeMatchContext& aTreeMatchContext) |
michael@0 | 1678 | { |
michael@0 | 1679 | for (nsPseudoClassList* pseudoClass = aSelector->mPseudoClassList; |
michael@0 | 1680 | pseudoClass; pseudoClass = pseudoClass->mNext) { |
michael@0 | 1681 | EventStates statesToCheck = sPseudoClassStates[pseudoClass->mType]; |
michael@0 | 1682 | if (!statesToCheck.IsEmpty() && |
michael@0 | 1683 | !StateSelectorMatches(aElement, aSelector, aNodeMatchContext, |
michael@0 | 1684 | aTreeMatchContext, nullptr, statesToCheck)) { |
michael@0 | 1685 | return false; |
michael@0 | 1686 | } |
michael@0 | 1687 | } |
michael@0 | 1688 | return true; |
michael@0 | 1689 | } |
michael@0 | 1690 | |
michael@0 | 1691 | // |aDependence| has two functions: |
michael@0 | 1692 | // * when non-null, it indicates that we're processing a negation, |
michael@0 | 1693 | // which is done only when SelectorMatches calls itself recursively |
michael@0 | 1694 | // * what it points to should be set to true whenever a test is skipped |
michael@0 | 1695 | // because of aNodeMatchContent.mStateMask |
michael@0 | 1696 | static bool SelectorMatches(Element* aElement, |
michael@0 | 1697 | nsCSSSelector* aSelector, |
michael@0 | 1698 | NodeMatchContext& aNodeMatchContext, |
michael@0 | 1699 | TreeMatchContext& aTreeMatchContext, |
michael@0 | 1700 | bool* const aDependence = nullptr) |
michael@0 | 1701 | |
michael@0 | 1702 | { |
michael@0 | 1703 | NS_PRECONDITION(!aSelector->IsPseudoElement(), |
michael@0 | 1704 | "Pseudo-element snuck into SelectorMatches?"); |
michael@0 | 1705 | NS_ABORT_IF_FALSE(aTreeMatchContext.mForStyling || |
michael@0 | 1706 | !aNodeMatchContext.mIsRelevantLink, |
michael@0 | 1707 | "mIsRelevantLink should be set to false when mForStyling " |
michael@0 | 1708 | "is false since we don't know how to set it correctly in " |
michael@0 | 1709 | "Has(Attribute|State)DependentStyle"); |
michael@0 | 1710 | |
michael@0 | 1711 | // namespace/tag match |
michael@0 | 1712 | // optimization : bail out early if we can |
michael@0 | 1713 | if ((kNameSpaceID_Unknown != aSelector->mNameSpace && |
michael@0 | 1714 | aElement->GetNameSpaceID() != aSelector->mNameSpace)) |
michael@0 | 1715 | return false; |
michael@0 | 1716 | |
michael@0 | 1717 | if (aSelector->mLowercaseTag) { |
michael@0 | 1718 | nsIAtom* selectorTag = |
michael@0 | 1719 | (aTreeMatchContext.mIsHTMLDocument && aElement->IsHTML()) ? |
michael@0 | 1720 | aSelector->mLowercaseTag : aSelector->mCasedTag; |
michael@0 | 1721 | if (selectorTag != aElement->Tag()) { |
michael@0 | 1722 | return false; |
michael@0 | 1723 | } |
michael@0 | 1724 | } |
michael@0 | 1725 | |
michael@0 | 1726 | nsAtomList* IDList = aSelector->mIDList; |
michael@0 | 1727 | if (IDList) { |
michael@0 | 1728 | nsIAtom* id = aElement->GetID(); |
michael@0 | 1729 | if (id) { |
michael@0 | 1730 | // case sensitivity: bug 93371 |
michael@0 | 1731 | const bool isCaseSensitive = |
michael@0 | 1732 | aTreeMatchContext.mCompatMode != eCompatibility_NavQuirks; |
michael@0 | 1733 | |
michael@0 | 1734 | if (isCaseSensitive) { |
michael@0 | 1735 | do { |
michael@0 | 1736 | if (IDList->mAtom != id) { |
michael@0 | 1737 | return false; |
michael@0 | 1738 | } |
michael@0 | 1739 | IDList = IDList->mNext; |
michael@0 | 1740 | } while (IDList); |
michael@0 | 1741 | } else { |
michael@0 | 1742 | // Use EqualsIgnoreASCIICase instead of full on unicode case conversion |
michael@0 | 1743 | // in order to save on performance. This is only used in quirks mode |
michael@0 | 1744 | // anyway. |
michael@0 | 1745 | nsDependentAtomString id1Str(id); |
michael@0 | 1746 | do { |
michael@0 | 1747 | if (!nsContentUtils::EqualsIgnoreASCIICase(id1Str, |
michael@0 | 1748 | nsDependentAtomString(IDList->mAtom))) { |
michael@0 | 1749 | return false; |
michael@0 | 1750 | } |
michael@0 | 1751 | IDList = IDList->mNext; |
michael@0 | 1752 | } while (IDList); |
michael@0 | 1753 | } |
michael@0 | 1754 | } else { |
michael@0 | 1755 | // Element has no id but we have an id selector |
michael@0 | 1756 | return false; |
michael@0 | 1757 | } |
michael@0 | 1758 | } |
michael@0 | 1759 | |
michael@0 | 1760 | nsAtomList* classList = aSelector->mClassList; |
michael@0 | 1761 | if (classList) { |
michael@0 | 1762 | // test for class match |
michael@0 | 1763 | const nsAttrValue *elementClasses = aElement->GetClasses(); |
michael@0 | 1764 | if (!elementClasses) { |
michael@0 | 1765 | // Element has no classes but we have a class selector |
michael@0 | 1766 | return false; |
michael@0 | 1767 | } |
michael@0 | 1768 | |
michael@0 | 1769 | // case sensitivity: bug 93371 |
michael@0 | 1770 | const bool isCaseSensitive = |
michael@0 | 1771 | aTreeMatchContext.mCompatMode != eCompatibility_NavQuirks; |
michael@0 | 1772 | |
michael@0 | 1773 | while (classList) { |
michael@0 | 1774 | if (!elementClasses->Contains(classList->mAtom, |
michael@0 | 1775 | isCaseSensitive ? |
michael@0 | 1776 | eCaseMatters : eIgnoreCase)) { |
michael@0 | 1777 | return false; |
michael@0 | 1778 | } |
michael@0 | 1779 | classList = classList->mNext; |
michael@0 | 1780 | } |
michael@0 | 1781 | } |
michael@0 | 1782 | |
michael@0 | 1783 | const bool isNegated = (aDependence != nullptr); |
michael@0 | 1784 | // The selectors for which we set node bits are, unfortunately, early |
michael@0 | 1785 | // in this function (because they're pseudo-classes, which are |
michael@0 | 1786 | // generally quick to test, and thus earlier). If they were later, |
michael@0 | 1787 | // we'd probably avoid setting those bits in more cases where setting |
michael@0 | 1788 | // them is unnecessary. |
michael@0 | 1789 | NS_ASSERTION(aNodeMatchContext.mStateMask.IsEmpty() || |
michael@0 | 1790 | !aTreeMatchContext.mForStyling, |
michael@0 | 1791 | "mForStyling must be false if we're just testing for " |
michael@0 | 1792 | "state-dependence"); |
michael@0 | 1793 | |
michael@0 | 1794 | // test for pseudo class match |
michael@0 | 1795 | for (nsPseudoClassList* pseudoClass = aSelector->mPseudoClassList; |
michael@0 | 1796 | pseudoClass; pseudoClass = pseudoClass->mNext) { |
michael@0 | 1797 | EventStates statesToCheck = sPseudoClassStates[pseudoClass->mType]; |
michael@0 | 1798 | if (statesToCheck.IsEmpty()) { |
michael@0 | 1799 | // keep the cases here in the same order as the list in |
michael@0 | 1800 | // nsCSSPseudoClassList.h |
michael@0 | 1801 | switch (pseudoClass->mType) { |
michael@0 | 1802 | case nsCSSPseudoClasses::ePseudoClass_empty: |
michael@0 | 1803 | if (!checkGenericEmptyMatches(aElement, aTreeMatchContext, true)) { |
michael@0 | 1804 | return false; |
michael@0 | 1805 | } |
michael@0 | 1806 | break; |
michael@0 | 1807 | |
michael@0 | 1808 | case nsCSSPseudoClasses::ePseudoClass_mozOnlyWhitespace: |
michael@0 | 1809 | if (!checkGenericEmptyMatches(aElement, aTreeMatchContext, false)) { |
michael@0 | 1810 | return false; |
michael@0 | 1811 | } |
michael@0 | 1812 | break; |
michael@0 | 1813 | |
michael@0 | 1814 | case nsCSSPseudoClasses::ePseudoClass_mozEmptyExceptChildrenWithLocalname: |
michael@0 | 1815 | { |
michael@0 | 1816 | NS_ASSERTION(pseudoClass->u.mString, "Must have string!"); |
michael@0 | 1817 | nsIContent *child = nullptr; |
michael@0 | 1818 | int32_t index = -1; |
michael@0 | 1819 | |
michael@0 | 1820 | if (aTreeMatchContext.mForStyling) |
michael@0 | 1821 | // FIXME: This isn't sufficient to handle: |
michael@0 | 1822 | // :-moz-empty-except-children-with-localname() + E |
michael@0 | 1823 | // :-moz-empty-except-children-with-localname() ~ E |
michael@0 | 1824 | // because we don't know to restyle the grandparent of the |
michael@0 | 1825 | // inserted/removed element (as in bug 534804 for :empty). |
michael@0 | 1826 | aElement->SetFlags(NODE_HAS_SLOW_SELECTOR); |
michael@0 | 1827 | do { |
michael@0 | 1828 | child = aElement->GetChildAt(++index); |
michael@0 | 1829 | } while (child && |
michael@0 | 1830 | (!IsSignificantChild(child, true, false) || |
michael@0 | 1831 | (child->GetNameSpaceID() == aElement->GetNameSpaceID() && |
michael@0 | 1832 | child->Tag()->Equals(nsDependentString(pseudoClass->u.mString))))); |
michael@0 | 1833 | if (child != nullptr) { |
michael@0 | 1834 | return false; |
michael@0 | 1835 | } |
michael@0 | 1836 | } |
michael@0 | 1837 | break; |
michael@0 | 1838 | |
michael@0 | 1839 | case nsCSSPseudoClasses::ePseudoClass_lang: |
michael@0 | 1840 | { |
michael@0 | 1841 | NS_ASSERTION(nullptr != pseudoClass->u.mString, "null lang parameter"); |
michael@0 | 1842 | if (!pseudoClass->u.mString || !*pseudoClass->u.mString) { |
michael@0 | 1843 | return false; |
michael@0 | 1844 | } |
michael@0 | 1845 | |
michael@0 | 1846 | // We have to determine the language of the current element. Since |
michael@0 | 1847 | // this is currently no property and since the language is inherited |
michael@0 | 1848 | // from the parent we have to be prepared to look at all parent |
michael@0 | 1849 | // nodes. The language itself is encoded in the LANG attribute. |
michael@0 | 1850 | nsAutoString language; |
michael@0 | 1851 | aElement->GetLang(language); |
michael@0 | 1852 | if (!language.IsEmpty()) { |
michael@0 | 1853 | if (!nsStyleUtil::DashMatchCompare(language, |
michael@0 | 1854 | nsDependentString(pseudoClass->u.mString), |
michael@0 | 1855 | nsASCIICaseInsensitiveStringComparator())) { |
michael@0 | 1856 | return false; |
michael@0 | 1857 | } |
michael@0 | 1858 | // This pseudo-class matched; move on to the next thing |
michael@0 | 1859 | break; |
michael@0 | 1860 | } |
michael@0 | 1861 | |
michael@0 | 1862 | nsIDocument* doc = aTreeMatchContext.mDocument; |
michael@0 | 1863 | if (doc) { |
michael@0 | 1864 | // Try to get the language from the HTTP header or if this |
michael@0 | 1865 | // is missing as well from the preferences. |
michael@0 | 1866 | // The content language can be a comma-separated list of |
michael@0 | 1867 | // language codes. |
michael@0 | 1868 | doc->GetContentLanguage(language); |
michael@0 | 1869 | |
michael@0 | 1870 | nsDependentString langString(pseudoClass->u.mString); |
michael@0 | 1871 | language.StripWhitespace(); |
michael@0 | 1872 | int32_t begin = 0; |
michael@0 | 1873 | int32_t len = language.Length(); |
michael@0 | 1874 | while (begin < len) { |
michael@0 | 1875 | int32_t end = language.FindChar(char16_t(','), begin); |
michael@0 | 1876 | if (end == kNotFound) { |
michael@0 | 1877 | end = len; |
michael@0 | 1878 | } |
michael@0 | 1879 | if (nsStyleUtil::DashMatchCompare(Substring(language, begin, |
michael@0 | 1880 | end-begin), |
michael@0 | 1881 | langString, |
michael@0 | 1882 | nsASCIICaseInsensitiveStringComparator())) { |
michael@0 | 1883 | break; |
michael@0 | 1884 | } |
michael@0 | 1885 | begin = end + 1; |
michael@0 | 1886 | } |
michael@0 | 1887 | if (begin < len) { |
michael@0 | 1888 | // This pseudo-class matched |
michael@0 | 1889 | break; |
michael@0 | 1890 | } |
michael@0 | 1891 | } |
michael@0 | 1892 | |
michael@0 | 1893 | return false; |
michael@0 | 1894 | } |
michael@0 | 1895 | break; |
michael@0 | 1896 | |
michael@0 | 1897 | case nsCSSPseudoClasses::ePseudoClass_mozBoundElement: |
michael@0 | 1898 | if (aTreeMatchContext.mScopedRoot != aElement) { |
michael@0 | 1899 | return false; |
michael@0 | 1900 | } |
michael@0 | 1901 | break; |
michael@0 | 1902 | |
michael@0 | 1903 | case nsCSSPseudoClasses::ePseudoClass_root: |
michael@0 | 1904 | if (aElement != aElement->OwnerDoc()->GetRootElement()) { |
michael@0 | 1905 | return false; |
michael@0 | 1906 | } |
michael@0 | 1907 | break; |
michael@0 | 1908 | |
michael@0 | 1909 | case nsCSSPseudoClasses::ePseudoClass_any: |
michael@0 | 1910 | { |
michael@0 | 1911 | nsCSSSelectorList *l; |
michael@0 | 1912 | for (l = pseudoClass->u.mSelectors; l; l = l->mNext) { |
michael@0 | 1913 | nsCSSSelector *s = l->mSelectors; |
michael@0 | 1914 | NS_ABORT_IF_FALSE(!s->mNext && !s->IsPseudoElement(), |
michael@0 | 1915 | "parser failed"); |
michael@0 | 1916 | if (SelectorMatches(aElement, s, aNodeMatchContext, |
michael@0 | 1917 | aTreeMatchContext)) { |
michael@0 | 1918 | break; |
michael@0 | 1919 | } |
michael@0 | 1920 | } |
michael@0 | 1921 | if (!l) { |
michael@0 | 1922 | return false; |
michael@0 | 1923 | } |
michael@0 | 1924 | } |
michael@0 | 1925 | break; |
michael@0 | 1926 | |
michael@0 | 1927 | case nsCSSPseudoClasses::ePseudoClass_firstChild: |
michael@0 | 1928 | if (!edgeChildMatches(aElement, aTreeMatchContext, true, false)) { |
michael@0 | 1929 | return false; |
michael@0 | 1930 | } |
michael@0 | 1931 | break; |
michael@0 | 1932 | |
michael@0 | 1933 | case nsCSSPseudoClasses::ePseudoClass_firstNode: |
michael@0 | 1934 | { |
michael@0 | 1935 | nsIContent *firstNode = nullptr; |
michael@0 | 1936 | nsIContent *parent = aElement->GetParent(); |
michael@0 | 1937 | if (parent) { |
michael@0 | 1938 | if (aTreeMatchContext.mForStyling) |
michael@0 | 1939 | parent->SetFlags(NODE_HAS_EDGE_CHILD_SELECTOR); |
michael@0 | 1940 | |
michael@0 | 1941 | int32_t index = -1; |
michael@0 | 1942 | do { |
michael@0 | 1943 | firstNode = parent->GetChildAt(++index); |
michael@0 | 1944 | // stop at first non-comment and non-whitespace node |
michael@0 | 1945 | } while (firstNode && |
michael@0 | 1946 | !IsSignificantChild(firstNode, true, false)); |
michael@0 | 1947 | } |
michael@0 | 1948 | if (aElement != firstNode) { |
michael@0 | 1949 | return false; |
michael@0 | 1950 | } |
michael@0 | 1951 | } |
michael@0 | 1952 | break; |
michael@0 | 1953 | |
michael@0 | 1954 | case nsCSSPseudoClasses::ePseudoClass_lastChild: |
michael@0 | 1955 | if (!edgeChildMatches(aElement, aTreeMatchContext, false, true)) { |
michael@0 | 1956 | return false; |
michael@0 | 1957 | } |
michael@0 | 1958 | break; |
michael@0 | 1959 | |
michael@0 | 1960 | case nsCSSPseudoClasses::ePseudoClass_lastNode: |
michael@0 | 1961 | { |
michael@0 | 1962 | nsIContent *lastNode = nullptr; |
michael@0 | 1963 | nsIContent *parent = aElement->GetParent(); |
michael@0 | 1964 | if (parent) { |
michael@0 | 1965 | if (aTreeMatchContext.mForStyling) |
michael@0 | 1966 | parent->SetFlags(NODE_HAS_EDGE_CHILD_SELECTOR); |
michael@0 | 1967 | |
michael@0 | 1968 | uint32_t index = parent->GetChildCount(); |
michael@0 | 1969 | do { |
michael@0 | 1970 | lastNode = parent->GetChildAt(--index); |
michael@0 | 1971 | // stop at first non-comment and non-whitespace node |
michael@0 | 1972 | } while (lastNode && |
michael@0 | 1973 | !IsSignificantChild(lastNode, true, false)); |
michael@0 | 1974 | } |
michael@0 | 1975 | if (aElement != lastNode) { |
michael@0 | 1976 | return false; |
michael@0 | 1977 | } |
michael@0 | 1978 | } |
michael@0 | 1979 | break; |
michael@0 | 1980 | |
michael@0 | 1981 | case nsCSSPseudoClasses::ePseudoClass_onlyChild: |
michael@0 | 1982 | if (!edgeChildMatches(aElement, aTreeMatchContext, true, true)) { |
michael@0 | 1983 | return false; |
michael@0 | 1984 | } |
michael@0 | 1985 | break; |
michael@0 | 1986 | |
michael@0 | 1987 | case nsCSSPseudoClasses::ePseudoClass_firstOfType: |
michael@0 | 1988 | if (!edgeOfTypeMatches(aElement, aTreeMatchContext, true, false)) { |
michael@0 | 1989 | return false; |
michael@0 | 1990 | } |
michael@0 | 1991 | break; |
michael@0 | 1992 | |
michael@0 | 1993 | case nsCSSPseudoClasses::ePseudoClass_lastOfType: |
michael@0 | 1994 | if (!edgeOfTypeMatches(aElement, aTreeMatchContext, false, true)) { |
michael@0 | 1995 | return false; |
michael@0 | 1996 | } |
michael@0 | 1997 | break; |
michael@0 | 1998 | |
michael@0 | 1999 | case nsCSSPseudoClasses::ePseudoClass_onlyOfType: |
michael@0 | 2000 | if (!edgeOfTypeMatches(aElement, aTreeMatchContext, true, true)) { |
michael@0 | 2001 | return false; |
michael@0 | 2002 | } |
michael@0 | 2003 | break; |
michael@0 | 2004 | |
michael@0 | 2005 | case nsCSSPseudoClasses::ePseudoClass_nthChild: |
michael@0 | 2006 | if (!nthChildGenericMatches(aElement, aTreeMatchContext, pseudoClass, |
michael@0 | 2007 | false, false)) { |
michael@0 | 2008 | return false; |
michael@0 | 2009 | } |
michael@0 | 2010 | break; |
michael@0 | 2011 | |
michael@0 | 2012 | case nsCSSPseudoClasses::ePseudoClass_nthLastChild: |
michael@0 | 2013 | if (!nthChildGenericMatches(aElement, aTreeMatchContext, pseudoClass, |
michael@0 | 2014 | false, true)) { |
michael@0 | 2015 | return false; |
michael@0 | 2016 | } |
michael@0 | 2017 | break; |
michael@0 | 2018 | |
michael@0 | 2019 | case nsCSSPseudoClasses::ePseudoClass_nthOfType: |
michael@0 | 2020 | if (!nthChildGenericMatches(aElement, aTreeMatchContext, pseudoClass, |
michael@0 | 2021 | true, false)) { |
michael@0 | 2022 | return false; |
michael@0 | 2023 | } |
michael@0 | 2024 | break; |
michael@0 | 2025 | |
michael@0 | 2026 | case nsCSSPseudoClasses::ePseudoClass_nthLastOfType: |
michael@0 | 2027 | if (!nthChildGenericMatches(aElement, aTreeMatchContext, pseudoClass, |
michael@0 | 2028 | true, true)) { |
michael@0 | 2029 | return false; |
michael@0 | 2030 | } |
michael@0 | 2031 | break; |
michael@0 | 2032 | |
michael@0 | 2033 | case nsCSSPseudoClasses::ePseudoClass_mozIsHTML: |
michael@0 | 2034 | if (!aTreeMatchContext.mIsHTMLDocument || !aElement->IsHTML()) { |
michael@0 | 2035 | return false; |
michael@0 | 2036 | } |
michael@0 | 2037 | break; |
michael@0 | 2038 | |
michael@0 | 2039 | case nsCSSPseudoClasses::ePseudoClass_mozSystemMetric: |
michael@0 | 2040 | { |
michael@0 | 2041 | nsCOMPtr<nsIAtom> metric = do_GetAtom(pseudoClass->u.mString); |
michael@0 | 2042 | if (!nsCSSRuleProcessor::HasSystemMetric(metric)) { |
michael@0 | 2043 | return false; |
michael@0 | 2044 | } |
michael@0 | 2045 | } |
michael@0 | 2046 | break; |
michael@0 | 2047 | |
michael@0 | 2048 | case nsCSSPseudoClasses::ePseudoClass_mozLocaleDir: |
michael@0 | 2049 | { |
michael@0 | 2050 | bool docIsRTL = |
michael@0 | 2051 | aTreeMatchContext.mDocument->GetDocumentState(). |
michael@0 | 2052 | HasState(NS_DOCUMENT_STATE_RTL_LOCALE); |
michael@0 | 2053 | |
michael@0 | 2054 | nsDependentString dirString(pseudoClass->u.mString); |
michael@0 | 2055 | NS_ASSERTION(dirString.EqualsLiteral("ltr") || |
michael@0 | 2056 | dirString.EqualsLiteral("rtl"), |
michael@0 | 2057 | "invalid value for -moz-locale-dir"); |
michael@0 | 2058 | |
michael@0 | 2059 | if (dirString.EqualsLiteral("rtl") != docIsRTL) { |
michael@0 | 2060 | return false; |
michael@0 | 2061 | } |
michael@0 | 2062 | } |
michael@0 | 2063 | break; |
michael@0 | 2064 | |
michael@0 | 2065 | case nsCSSPseudoClasses::ePseudoClass_mozLWTheme: |
michael@0 | 2066 | { |
michael@0 | 2067 | if (aTreeMatchContext.mDocument->GetDocumentLWTheme() <= |
michael@0 | 2068 | nsIDocument::Doc_Theme_None) { |
michael@0 | 2069 | return false; |
michael@0 | 2070 | } |
michael@0 | 2071 | } |
michael@0 | 2072 | break; |
michael@0 | 2073 | |
michael@0 | 2074 | case nsCSSPseudoClasses::ePseudoClass_mozLWThemeBrightText: |
michael@0 | 2075 | { |
michael@0 | 2076 | if (aTreeMatchContext.mDocument->GetDocumentLWTheme() != |
michael@0 | 2077 | nsIDocument::Doc_Theme_Bright) { |
michael@0 | 2078 | return false; |
michael@0 | 2079 | } |
michael@0 | 2080 | } |
michael@0 | 2081 | break; |
michael@0 | 2082 | |
michael@0 | 2083 | case nsCSSPseudoClasses::ePseudoClass_mozLWThemeDarkText: |
michael@0 | 2084 | { |
michael@0 | 2085 | if (aTreeMatchContext.mDocument->GetDocumentLWTheme() != |
michael@0 | 2086 | nsIDocument::Doc_Theme_Dark) { |
michael@0 | 2087 | return false; |
michael@0 | 2088 | } |
michael@0 | 2089 | } |
michael@0 | 2090 | break; |
michael@0 | 2091 | |
michael@0 | 2092 | case nsCSSPseudoClasses::ePseudoClass_mozWindowInactive: |
michael@0 | 2093 | if (!aTreeMatchContext.mDocument->GetDocumentState(). |
michael@0 | 2094 | HasState(NS_DOCUMENT_STATE_WINDOW_INACTIVE)) { |
michael@0 | 2095 | return false; |
michael@0 | 2096 | } |
michael@0 | 2097 | break; |
michael@0 | 2098 | |
michael@0 | 2099 | case nsCSSPseudoClasses::ePseudoClass_mozTableBorderNonzero: |
michael@0 | 2100 | { |
michael@0 | 2101 | if (!aElement->IsHTML(nsGkAtoms::table)) { |
michael@0 | 2102 | return false; |
michael@0 | 2103 | } |
michael@0 | 2104 | const nsAttrValue *val = aElement->GetParsedAttr(nsGkAtoms::border); |
michael@0 | 2105 | if (!val || |
michael@0 | 2106 | (val->Type() == nsAttrValue::eInteger && |
michael@0 | 2107 | val->GetIntegerValue() == 0)) { |
michael@0 | 2108 | return false; |
michael@0 | 2109 | } |
michael@0 | 2110 | } |
michael@0 | 2111 | break; |
michael@0 | 2112 | |
michael@0 | 2113 | case nsCSSPseudoClasses::ePseudoClass_dir: |
michael@0 | 2114 | { |
michael@0 | 2115 | if (aDependence) { |
michael@0 | 2116 | EventStates states |
michael@0 | 2117 | = sPseudoClassStateDependences[pseudoClass->mType]; |
michael@0 | 2118 | if (aNodeMatchContext.mStateMask.HasAtLeastOneOfStates(states)) { |
michael@0 | 2119 | *aDependence = true; |
michael@0 | 2120 | return false; |
michael@0 | 2121 | } |
michael@0 | 2122 | } |
michael@0 | 2123 | |
michael@0 | 2124 | // if we only had to consider HTML, directionality would be exclusively |
michael@0 | 2125 | // LTR or RTL, and this could be just |
michael@0 | 2126 | // |
michael@0 | 2127 | // if (dirString.EqualsLiteral("rtl") != |
michael@0 | 2128 | // aElement->StyleState().HasState(NS_EVENT_STATE_RTL) |
michael@0 | 2129 | // |
michael@0 | 2130 | // However, in markup languages where there is no direction attribute |
michael@0 | 2131 | // we have to consider the possibility that neither -moz-dir(rtl) nor |
michael@0 | 2132 | // -moz-dir(ltr) matches. |
michael@0 | 2133 | EventStates state = aElement->StyleState(); |
michael@0 | 2134 | bool elementIsRTL = state.HasState(NS_EVENT_STATE_RTL); |
michael@0 | 2135 | bool elementIsLTR = state.HasState(NS_EVENT_STATE_LTR); |
michael@0 | 2136 | nsDependentString dirString(pseudoClass->u.mString); |
michael@0 | 2137 | |
michael@0 | 2138 | if ((dirString.EqualsLiteral("rtl") && !elementIsRTL) || |
michael@0 | 2139 | (dirString.EqualsLiteral("ltr") && !elementIsLTR)) { |
michael@0 | 2140 | return false; |
michael@0 | 2141 | } |
michael@0 | 2142 | } |
michael@0 | 2143 | break; |
michael@0 | 2144 | |
michael@0 | 2145 | case nsCSSPseudoClasses::ePseudoClass_scope: |
michael@0 | 2146 | if (aTreeMatchContext.mForScopedStyle) { |
michael@0 | 2147 | if (aTreeMatchContext.mCurrentStyleScope) { |
michael@0 | 2148 | // If mCurrentStyleScope is null, aElement must be the style |
michael@0 | 2149 | // scope root. This is because the PopStyleScopeForSelectorMatching |
michael@0 | 2150 | // call in SelectorMatchesTree sets mCurrentStyleScope to null |
michael@0 | 2151 | // as soon as we visit the style scope element, and we won't |
michael@0 | 2152 | // progress further up the tree after this call to |
michael@0 | 2153 | // SelectorMatches. Thus if mCurrentStyleScope is still set, |
michael@0 | 2154 | // we know the selector does not match. |
michael@0 | 2155 | return false; |
michael@0 | 2156 | } |
michael@0 | 2157 | } else if (aTreeMatchContext.HasSpecifiedScope()) { |
michael@0 | 2158 | if (!aTreeMatchContext.IsScopeElement(aElement)) { |
michael@0 | 2159 | return false; |
michael@0 | 2160 | } |
michael@0 | 2161 | } else { |
michael@0 | 2162 | if (aElement != aElement->OwnerDoc()->GetRootElement()) { |
michael@0 | 2163 | return false; |
michael@0 | 2164 | } |
michael@0 | 2165 | } |
michael@0 | 2166 | break; |
michael@0 | 2167 | |
michael@0 | 2168 | default: |
michael@0 | 2169 | NS_ABORT_IF_FALSE(false, "How did that happen?"); |
michael@0 | 2170 | } |
michael@0 | 2171 | } else { |
michael@0 | 2172 | if (!StateSelectorMatches(aElement, aSelector, aNodeMatchContext, |
michael@0 | 2173 | aTreeMatchContext, aDependence, |
michael@0 | 2174 | statesToCheck)) { |
michael@0 | 2175 | return false; |
michael@0 | 2176 | } |
michael@0 | 2177 | } |
michael@0 | 2178 | } |
michael@0 | 2179 | |
michael@0 | 2180 | bool result = true; |
michael@0 | 2181 | if (aSelector->mAttrList) { |
michael@0 | 2182 | // test for attribute match |
michael@0 | 2183 | if (!aElement->HasAttrs()) { |
michael@0 | 2184 | // if no attributes on the content, no match |
michael@0 | 2185 | return false; |
michael@0 | 2186 | } else { |
michael@0 | 2187 | result = true; |
michael@0 | 2188 | nsAttrSelector* attr = aSelector->mAttrList; |
michael@0 | 2189 | nsIAtom* matchAttribute; |
michael@0 | 2190 | |
michael@0 | 2191 | do { |
michael@0 | 2192 | bool isHTML = |
michael@0 | 2193 | (aTreeMatchContext.mIsHTMLDocument && aElement->IsHTML()); |
michael@0 | 2194 | matchAttribute = isHTML ? attr->mLowercaseAttr : attr->mCasedAttr; |
michael@0 | 2195 | if (attr->mNameSpace == kNameSpaceID_Unknown) { |
michael@0 | 2196 | // Attr selector with a wildcard namespace. We have to examine all |
michael@0 | 2197 | // the attributes on our content node.... This sort of selector is |
michael@0 | 2198 | // essentially a boolean OR, over all namespaces, of equivalent attr |
michael@0 | 2199 | // selectors with those namespaces. So to evaluate whether it |
michael@0 | 2200 | // matches, evaluate for each namespace (the only namespaces that |
michael@0 | 2201 | // have a chance at matching, of course, are ones that the element |
michael@0 | 2202 | // actually has attributes in), short-circuiting if we ever match. |
michael@0 | 2203 | result = false; |
michael@0 | 2204 | const nsAttrName* attrName; |
michael@0 | 2205 | for (uint32_t i = 0; (attrName = aElement->GetAttrNameAt(i)); ++i) { |
michael@0 | 2206 | if (attrName->LocalName() != matchAttribute) { |
michael@0 | 2207 | continue; |
michael@0 | 2208 | } |
michael@0 | 2209 | if (attr->mFunction == NS_ATTR_FUNC_SET) { |
michael@0 | 2210 | result = true; |
michael@0 | 2211 | } else { |
michael@0 | 2212 | nsAutoString value; |
michael@0 | 2213 | #ifdef DEBUG |
michael@0 | 2214 | bool hasAttr = |
michael@0 | 2215 | #endif |
michael@0 | 2216 | aElement->GetAttr(attrName->NamespaceID(), |
michael@0 | 2217 | attrName->LocalName(), value); |
michael@0 | 2218 | NS_ASSERTION(hasAttr, "GetAttrNameAt lied"); |
michael@0 | 2219 | result = AttrMatchesValue(attr, value, isHTML); |
michael@0 | 2220 | } |
michael@0 | 2221 | |
michael@0 | 2222 | // At this point |result| has been set by us |
michael@0 | 2223 | // explicitly in this loop. If it's false, we may still match |
michael@0 | 2224 | // -- the content may have another attribute with the same name but |
michael@0 | 2225 | // in a different namespace. But if it's true, we are done (we |
michael@0 | 2226 | // can short-circuit the boolean OR described above). |
michael@0 | 2227 | if (result) { |
michael@0 | 2228 | break; |
michael@0 | 2229 | } |
michael@0 | 2230 | } |
michael@0 | 2231 | } |
michael@0 | 2232 | else if (attr->mFunction == NS_ATTR_FUNC_EQUALS) { |
michael@0 | 2233 | result = |
michael@0 | 2234 | aElement-> |
michael@0 | 2235 | AttrValueIs(attr->mNameSpace, matchAttribute, attr->mValue, |
michael@0 | 2236 | (!isHTML || attr->mCaseSensitive) ? eCaseMatters |
michael@0 | 2237 | : eIgnoreCase); |
michael@0 | 2238 | } |
michael@0 | 2239 | else if (!aElement->HasAttr(attr->mNameSpace, matchAttribute)) { |
michael@0 | 2240 | result = false; |
michael@0 | 2241 | } |
michael@0 | 2242 | else if (attr->mFunction != NS_ATTR_FUNC_SET) { |
michael@0 | 2243 | nsAutoString value; |
michael@0 | 2244 | #ifdef DEBUG |
michael@0 | 2245 | bool hasAttr = |
michael@0 | 2246 | #endif |
michael@0 | 2247 | aElement->GetAttr(attr->mNameSpace, matchAttribute, value); |
michael@0 | 2248 | NS_ASSERTION(hasAttr, "HasAttr lied"); |
michael@0 | 2249 | result = AttrMatchesValue(attr, value, isHTML); |
michael@0 | 2250 | } |
michael@0 | 2251 | |
michael@0 | 2252 | attr = attr->mNext; |
michael@0 | 2253 | } while (attr && result); |
michael@0 | 2254 | } |
michael@0 | 2255 | } |
michael@0 | 2256 | |
michael@0 | 2257 | // apply SelectorMatches to the negated selectors in the chain |
michael@0 | 2258 | if (!isNegated) { |
michael@0 | 2259 | for (nsCSSSelector *negation = aSelector->mNegations; |
michael@0 | 2260 | result && negation; negation = negation->mNegations) { |
michael@0 | 2261 | bool dependence = false; |
michael@0 | 2262 | result = !SelectorMatches(aElement, negation, aNodeMatchContext, |
michael@0 | 2263 | aTreeMatchContext, &dependence); |
michael@0 | 2264 | // If the selector does match due to the dependence on |
michael@0 | 2265 | // aNodeMatchContext.mStateMask, then we want to keep result true |
michael@0 | 2266 | // so that the final result of SelectorMatches is true. Doing so |
michael@0 | 2267 | // tells StateEnumFunc that there is a dependence on the state. |
michael@0 | 2268 | result = result || dependence; |
michael@0 | 2269 | } |
michael@0 | 2270 | } |
michael@0 | 2271 | return result; |
michael@0 | 2272 | } |
michael@0 | 2273 | |
michael@0 | 2274 | #undef STATE_CHECK |
michael@0 | 2275 | |
michael@0 | 2276 | // Right now, there are four operators: |
michael@0 | 2277 | // ' ', the descendant combinator, is greedy |
michael@0 | 2278 | // '~', the indirect adjacent sibling combinator, is greedy |
michael@0 | 2279 | // '+' and '>', the direct adjacent sibling and child combinators, are not |
michael@0 | 2280 | #define NS_IS_GREEDY_OPERATOR(ch) \ |
michael@0 | 2281 | ((ch) == char16_t(' ') || (ch) == char16_t('~')) |
michael@0 | 2282 | |
michael@0 | 2283 | static bool SelectorMatchesTree(Element* aPrevElement, |
michael@0 | 2284 | nsCSSSelector* aSelector, |
michael@0 | 2285 | TreeMatchContext& aTreeMatchContext, |
michael@0 | 2286 | bool aLookForRelevantLink) |
michael@0 | 2287 | { |
michael@0 | 2288 | MOZ_ASSERT(!aSelector || !aSelector->IsPseudoElement()); |
michael@0 | 2289 | nsCSSSelector* selector = aSelector; |
michael@0 | 2290 | Element* prevElement = aPrevElement; |
michael@0 | 2291 | while (selector) { // check compound selectors |
michael@0 | 2292 | NS_ASSERTION(!selector->mNext || |
michael@0 | 2293 | selector->mNext->mOperator != char16_t(0), |
michael@0 | 2294 | "compound selector without combinator"); |
michael@0 | 2295 | |
michael@0 | 2296 | // If after the previous selector match we are now outside the |
michael@0 | 2297 | // current style scope, we don't need to match any further. |
michael@0 | 2298 | if (aTreeMatchContext.mForScopedStyle && |
michael@0 | 2299 | !aTreeMatchContext.IsWithinStyleScopeForSelectorMatching()) { |
michael@0 | 2300 | return false; |
michael@0 | 2301 | } |
michael@0 | 2302 | |
michael@0 | 2303 | // for adjacent sibling combinators, the content to test against the |
michael@0 | 2304 | // selector is the previous sibling *element* |
michael@0 | 2305 | Element* element = nullptr; |
michael@0 | 2306 | if (char16_t('+') == selector->mOperator || |
michael@0 | 2307 | char16_t('~') == selector->mOperator) { |
michael@0 | 2308 | // The relevant link must be an ancestor of the node being matched. |
michael@0 | 2309 | aLookForRelevantLink = false; |
michael@0 | 2310 | nsIContent* parent = prevElement->GetParent(); |
michael@0 | 2311 | if (parent) { |
michael@0 | 2312 | if (aTreeMatchContext.mForStyling) |
michael@0 | 2313 | parent->SetFlags(NODE_HAS_SLOW_SELECTOR_LATER_SIBLINGS); |
michael@0 | 2314 | |
michael@0 | 2315 | element = prevElement->GetPreviousElementSibling(); |
michael@0 | 2316 | } |
michael@0 | 2317 | } |
michael@0 | 2318 | // for descendant combinators and child combinators, the element |
michael@0 | 2319 | // to test against is the parent |
michael@0 | 2320 | else { |
michael@0 | 2321 | nsIContent *content = prevElement->GetParent(); |
michael@0 | 2322 | // GetParent could return a document fragment; we only want |
michael@0 | 2323 | // element parents. |
michael@0 | 2324 | if (content && content->IsElement()) { |
michael@0 | 2325 | element = content->AsElement(); |
michael@0 | 2326 | if (aTreeMatchContext.mForScopedStyle) { |
michael@0 | 2327 | // We are moving up to the parent element; tell the |
michael@0 | 2328 | // TreeMatchContext, so that in case this element is the |
michael@0 | 2329 | // style scope element, selector matching stops before we |
michael@0 | 2330 | // traverse further up the tree. |
michael@0 | 2331 | aTreeMatchContext.PopStyleScopeForSelectorMatching(element); |
michael@0 | 2332 | } |
michael@0 | 2333 | |
michael@0 | 2334 | // Compatibility hack: First try matching this selector as though the |
michael@0 | 2335 | // <xbl:children> element wasn't in the tree to allow old selectors |
michael@0 | 2336 | // were written before <xbl:children> participated in CSS selector |
michael@0 | 2337 | // matching to work. |
michael@0 | 2338 | if (selector->mOperator == '>' && element->IsActiveChildrenElement()) { |
michael@0 | 2339 | Element* styleScope = aTreeMatchContext.mCurrentStyleScope; |
michael@0 | 2340 | if (SelectorMatchesTree(element, selector, aTreeMatchContext, |
michael@0 | 2341 | aLookForRelevantLink)) { |
michael@0 | 2342 | // It matched, don't try matching on the <xbl:children> element at |
michael@0 | 2343 | // all. |
michael@0 | 2344 | return true; |
michael@0 | 2345 | } |
michael@0 | 2346 | // We want to reset mCurrentStyleScope on aTreeMatchContext |
michael@0 | 2347 | // back to its state before the SelectorMatchesTree call, in |
michael@0 | 2348 | // case that call happens to traverse past the style scope element |
michael@0 | 2349 | // and sets it to null. |
michael@0 | 2350 | aTreeMatchContext.mCurrentStyleScope = styleScope; |
michael@0 | 2351 | } |
michael@0 | 2352 | } |
michael@0 | 2353 | } |
michael@0 | 2354 | if (!element) { |
michael@0 | 2355 | return false; |
michael@0 | 2356 | } |
michael@0 | 2357 | NodeMatchContext nodeContext(EventStates(), |
michael@0 | 2358 | aLookForRelevantLink && |
michael@0 | 2359 | nsCSSRuleProcessor::IsLink(element)); |
michael@0 | 2360 | if (nodeContext.mIsRelevantLink) { |
michael@0 | 2361 | // If we find an ancestor of the matched node that is a link |
michael@0 | 2362 | // during the matching process, then it's the relevant link (see |
michael@0 | 2363 | // constructor call above). |
michael@0 | 2364 | // Since we are still matching against selectors that contain |
michael@0 | 2365 | // :visited (they'll just fail), we will always find such a node |
michael@0 | 2366 | // during the selector matching process if there is a relevant |
michael@0 | 2367 | // link that can influence selector matching. |
michael@0 | 2368 | aLookForRelevantLink = false; |
michael@0 | 2369 | aTreeMatchContext.SetHaveRelevantLink(); |
michael@0 | 2370 | } |
michael@0 | 2371 | if (SelectorMatches(element, selector, nodeContext, aTreeMatchContext)) { |
michael@0 | 2372 | // to avoid greedy matching, we need to recur if this is a |
michael@0 | 2373 | // descendant or general sibling combinator and the next |
michael@0 | 2374 | // combinator is different, but we can make an exception for |
michael@0 | 2375 | // sibling, then parent, since a sibling's parent is always the |
michael@0 | 2376 | // same. |
michael@0 | 2377 | if (NS_IS_GREEDY_OPERATOR(selector->mOperator) && |
michael@0 | 2378 | selector->mNext && |
michael@0 | 2379 | selector->mNext->mOperator != selector->mOperator && |
michael@0 | 2380 | !(selector->mOperator == '~' && |
michael@0 | 2381 | NS_IS_ANCESTOR_OPERATOR(selector->mNext->mOperator))) { |
michael@0 | 2382 | |
michael@0 | 2383 | // pretend the selector didn't match, and step through content |
michael@0 | 2384 | // while testing the same selector |
michael@0 | 2385 | |
michael@0 | 2386 | // This approach is slightly strange in that when it recurs |
michael@0 | 2387 | // it tests from the top of the content tree, down. This |
michael@0 | 2388 | // doesn't matter much for performance since most selectors |
michael@0 | 2389 | // don't match. (If most did, it might be faster...) |
michael@0 | 2390 | Element* styleScope = aTreeMatchContext.mCurrentStyleScope; |
michael@0 | 2391 | if (SelectorMatchesTree(element, selector, aTreeMatchContext, |
michael@0 | 2392 | aLookForRelevantLink)) { |
michael@0 | 2393 | return true; |
michael@0 | 2394 | } |
michael@0 | 2395 | // We want to reset mCurrentStyleScope on aTreeMatchContext |
michael@0 | 2396 | // back to its state before the SelectorMatchesTree call, in |
michael@0 | 2397 | // case that call happens to traverse past the style scope element |
michael@0 | 2398 | // and sets it to null. |
michael@0 | 2399 | aTreeMatchContext.mCurrentStyleScope = styleScope; |
michael@0 | 2400 | } |
michael@0 | 2401 | selector = selector->mNext; |
michael@0 | 2402 | } |
michael@0 | 2403 | else { |
michael@0 | 2404 | // for adjacent sibling and child combinators, if we didn't find |
michael@0 | 2405 | // a match, we're done |
michael@0 | 2406 | if (!NS_IS_GREEDY_OPERATOR(selector->mOperator)) { |
michael@0 | 2407 | return false; // parent was required to match |
michael@0 | 2408 | } |
michael@0 | 2409 | } |
michael@0 | 2410 | prevElement = element; |
michael@0 | 2411 | } |
michael@0 | 2412 | return true; // all the selectors matched. |
michael@0 | 2413 | } |
michael@0 | 2414 | |
michael@0 | 2415 | static inline |
michael@0 | 2416 | void ContentEnumFunc(const RuleValue& value, nsCSSSelector* aSelector, |
michael@0 | 2417 | ElementDependentRuleProcessorData* data, NodeMatchContext& nodeContext, |
michael@0 | 2418 | AncestorFilter *ancestorFilter) |
michael@0 | 2419 | { |
michael@0 | 2420 | if (nodeContext.mIsRelevantLink) { |
michael@0 | 2421 | data->mTreeMatchContext.SetHaveRelevantLink(); |
michael@0 | 2422 | } |
michael@0 | 2423 | if (ancestorFilter && |
michael@0 | 2424 | !ancestorFilter->MightHaveMatchingAncestor<RuleValue::eMaxAncestorHashes>( |
michael@0 | 2425 | value.mAncestorSelectorHashes)) { |
michael@0 | 2426 | // We won't match; nothing else to do here |
michael@0 | 2427 | return; |
michael@0 | 2428 | } |
michael@0 | 2429 | if (!data->mTreeMatchContext.SetStyleScopeForSelectorMatching(data->mElement, |
michael@0 | 2430 | data->mScope)) { |
michael@0 | 2431 | // The selector is for a rule in a scoped style sheet, and the subject |
michael@0 | 2432 | // of the selector matching is not in its scope. |
michael@0 | 2433 | return; |
michael@0 | 2434 | } |
michael@0 | 2435 | nsCSSSelector* selector = aSelector; |
michael@0 | 2436 | if (selector->IsPseudoElement()) { |
michael@0 | 2437 | PseudoElementRuleProcessorData* pdata = |
michael@0 | 2438 | static_cast<PseudoElementRuleProcessorData*>(data); |
michael@0 | 2439 | if (!pdata->mPseudoElement && selector->mPseudoClassList) { |
michael@0 | 2440 | // We can get here when calling getComputedStyle(aElt, aPseudo) if: |
michael@0 | 2441 | // |
michael@0 | 2442 | // * aPseudo is a pseudo-element that supports a user action |
michael@0 | 2443 | // pseudo-class, like "::-moz-placeholder"; |
michael@0 | 2444 | // * there is a style rule that uses a pseudo-class on this |
michael@0 | 2445 | // pseudo-element in the document, like ::-moz-placeholder:hover; and |
michael@0 | 2446 | // * aElt does not have such a pseudo-element. |
michael@0 | 2447 | // |
michael@0 | 2448 | // We know that the selector can't match, since there is no element for |
michael@0 | 2449 | // the user action pseudo-class to match against. |
michael@0 | 2450 | return; |
michael@0 | 2451 | } |
michael@0 | 2452 | if (!StateSelectorMatches(pdata->mPseudoElement, aSelector, nodeContext, |
michael@0 | 2453 | data->mTreeMatchContext)) { |
michael@0 | 2454 | return; |
michael@0 | 2455 | } |
michael@0 | 2456 | selector = selector->mNext; |
michael@0 | 2457 | } |
michael@0 | 2458 | if (SelectorMatches(data->mElement, selector, nodeContext, |
michael@0 | 2459 | data->mTreeMatchContext)) { |
michael@0 | 2460 | nsCSSSelector *next = selector->mNext; |
michael@0 | 2461 | if (!next || SelectorMatchesTree(data->mElement, next, |
michael@0 | 2462 | data->mTreeMatchContext, |
michael@0 | 2463 | !nodeContext.mIsRelevantLink)) { |
michael@0 | 2464 | css::StyleRule *rule = value.mRule; |
michael@0 | 2465 | rule->RuleMatched(); |
michael@0 | 2466 | data->mRuleWalker->Forward(rule); |
michael@0 | 2467 | // nsStyleSet will deal with the !important rule |
michael@0 | 2468 | } |
michael@0 | 2469 | } |
michael@0 | 2470 | } |
michael@0 | 2471 | |
michael@0 | 2472 | /* virtual */ void |
michael@0 | 2473 | nsCSSRuleProcessor::RulesMatching(ElementRuleProcessorData *aData) |
michael@0 | 2474 | { |
michael@0 | 2475 | RuleCascadeData* cascade = GetRuleCascade(aData->mPresContext); |
michael@0 | 2476 | |
michael@0 | 2477 | if (cascade) { |
michael@0 | 2478 | NodeMatchContext nodeContext(EventStates(), |
michael@0 | 2479 | nsCSSRuleProcessor::IsLink(aData->mElement)); |
michael@0 | 2480 | cascade->mRuleHash.EnumerateAllRules(aData->mElement, aData, nodeContext); |
michael@0 | 2481 | } |
michael@0 | 2482 | } |
michael@0 | 2483 | |
michael@0 | 2484 | /* virtual */ void |
michael@0 | 2485 | nsCSSRuleProcessor::RulesMatching(PseudoElementRuleProcessorData* aData) |
michael@0 | 2486 | { |
michael@0 | 2487 | RuleCascadeData* cascade = GetRuleCascade(aData->mPresContext); |
michael@0 | 2488 | |
michael@0 | 2489 | if (cascade) { |
michael@0 | 2490 | RuleHash* ruleHash = cascade->mPseudoElementRuleHashes[aData->mPseudoType]; |
michael@0 | 2491 | if (ruleHash) { |
michael@0 | 2492 | NodeMatchContext nodeContext(EventStates(), |
michael@0 | 2493 | nsCSSRuleProcessor::IsLink(aData->mElement)); |
michael@0 | 2494 | ruleHash->EnumerateAllRules(aData->mElement, aData, nodeContext); |
michael@0 | 2495 | } |
michael@0 | 2496 | } |
michael@0 | 2497 | } |
michael@0 | 2498 | |
michael@0 | 2499 | /* virtual */ void |
michael@0 | 2500 | nsCSSRuleProcessor::RulesMatching(AnonBoxRuleProcessorData* aData) |
michael@0 | 2501 | { |
michael@0 | 2502 | RuleCascadeData* cascade = GetRuleCascade(aData->mPresContext); |
michael@0 | 2503 | |
michael@0 | 2504 | if (cascade && cascade->mAnonBoxRules.entryCount) { |
michael@0 | 2505 | RuleHashTagTableEntry* entry = static_cast<RuleHashTagTableEntry*> |
michael@0 | 2506 | (PL_DHashTableOperate(&cascade->mAnonBoxRules, aData->mPseudoTag, |
michael@0 | 2507 | PL_DHASH_LOOKUP)); |
michael@0 | 2508 | if (PL_DHASH_ENTRY_IS_BUSY(entry)) { |
michael@0 | 2509 | nsTArray<RuleValue>& rules = entry->mRules; |
michael@0 | 2510 | for (RuleValue *value = rules.Elements(), *end = value + rules.Length(); |
michael@0 | 2511 | value != end; ++value) { |
michael@0 | 2512 | value->mRule->RuleMatched(); |
michael@0 | 2513 | aData->mRuleWalker->Forward(value->mRule); |
michael@0 | 2514 | } |
michael@0 | 2515 | } |
michael@0 | 2516 | } |
michael@0 | 2517 | } |
michael@0 | 2518 | |
michael@0 | 2519 | #ifdef MOZ_XUL |
michael@0 | 2520 | /* virtual */ void |
michael@0 | 2521 | nsCSSRuleProcessor::RulesMatching(XULTreeRuleProcessorData* aData) |
michael@0 | 2522 | { |
michael@0 | 2523 | RuleCascadeData* cascade = GetRuleCascade(aData->mPresContext); |
michael@0 | 2524 | |
michael@0 | 2525 | if (cascade && cascade->mXULTreeRules.entryCount) { |
michael@0 | 2526 | RuleHashTagTableEntry* entry = static_cast<RuleHashTagTableEntry*> |
michael@0 | 2527 | (PL_DHashTableOperate(&cascade->mXULTreeRules, aData->mPseudoTag, |
michael@0 | 2528 | PL_DHASH_LOOKUP)); |
michael@0 | 2529 | if (PL_DHASH_ENTRY_IS_BUSY(entry)) { |
michael@0 | 2530 | NodeMatchContext nodeContext(EventStates(), |
michael@0 | 2531 | nsCSSRuleProcessor::IsLink(aData->mElement)); |
michael@0 | 2532 | nsTArray<RuleValue>& rules = entry->mRules; |
michael@0 | 2533 | for (RuleValue *value = rules.Elements(), *end = value + rules.Length(); |
michael@0 | 2534 | value != end; ++value) { |
michael@0 | 2535 | if (aData->mComparator->PseudoMatches(value->mSelector)) { |
michael@0 | 2536 | ContentEnumFunc(*value, value->mSelector->mNext, aData, nodeContext, |
michael@0 | 2537 | nullptr); |
michael@0 | 2538 | } |
michael@0 | 2539 | } |
michael@0 | 2540 | } |
michael@0 | 2541 | } |
michael@0 | 2542 | } |
michael@0 | 2543 | #endif |
michael@0 | 2544 | |
michael@0 | 2545 | static inline nsRestyleHint RestyleHintForOp(char16_t oper) |
michael@0 | 2546 | { |
michael@0 | 2547 | if (oper == char16_t('+') || oper == char16_t('~')) { |
michael@0 | 2548 | return eRestyle_LaterSiblings; |
michael@0 | 2549 | } |
michael@0 | 2550 | |
michael@0 | 2551 | if (oper != char16_t(0)) { |
michael@0 | 2552 | return eRestyle_Subtree; |
michael@0 | 2553 | } |
michael@0 | 2554 | |
michael@0 | 2555 | return eRestyle_Self; |
michael@0 | 2556 | } |
michael@0 | 2557 | |
michael@0 | 2558 | nsRestyleHint |
michael@0 | 2559 | nsCSSRuleProcessor::HasStateDependentStyle(ElementDependentRuleProcessorData* aData, |
michael@0 | 2560 | Element* aStatefulElement, |
michael@0 | 2561 | nsCSSPseudoElements::Type aPseudoType, |
michael@0 | 2562 | EventStates aStateMask) |
michael@0 | 2563 | { |
michael@0 | 2564 | MOZ_ASSERT(!aData->mTreeMatchContext.mForScopedStyle, |
michael@0 | 2565 | "mCurrentStyleScope will need to be saved and restored after the " |
michael@0 | 2566 | "SelectorMatchesTree call"); |
michael@0 | 2567 | |
michael@0 | 2568 | bool isPseudoElement = |
michael@0 | 2569 | aPseudoType != nsCSSPseudoElements::ePseudo_NotPseudoElement; |
michael@0 | 2570 | |
michael@0 | 2571 | RuleCascadeData* cascade = GetRuleCascade(aData->mPresContext); |
michael@0 | 2572 | |
michael@0 | 2573 | // Look up the content node in the state rule list, which points to |
michael@0 | 2574 | // any (CSS2 definition) simple selector (whether or not it is the |
michael@0 | 2575 | // subject) that has a state pseudo-class on it. This means that this |
michael@0 | 2576 | // code will be matching selectors that aren't real selectors in any |
michael@0 | 2577 | // stylesheet (e.g., if there is a selector "body > p:hover > a", then |
michael@0 | 2578 | // "body > p:hover" will be in |cascade->mStateSelectors|). Note that |
michael@0 | 2579 | // |ComputeSelectorStateDependence| below determines which selectors are in |
michael@0 | 2580 | // |cascade->mStateSelectors|. |
michael@0 | 2581 | nsRestyleHint hint = nsRestyleHint(0); |
michael@0 | 2582 | if (cascade) { |
michael@0 | 2583 | StateSelector *iter = cascade->mStateSelectors.Elements(), |
michael@0 | 2584 | *end = iter + cascade->mStateSelectors.Length(); |
michael@0 | 2585 | NodeMatchContext nodeContext(aStateMask, false); |
michael@0 | 2586 | for(; iter != end; ++iter) { |
michael@0 | 2587 | nsCSSSelector* selector = iter->mSelector; |
michael@0 | 2588 | EventStates states = iter->mStates; |
michael@0 | 2589 | |
michael@0 | 2590 | if (selector->IsPseudoElement() != isPseudoElement) { |
michael@0 | 2591 | continue; |
michael@0 | 2592 | } |
michael@0 | 2593 | |
michael@0 | 2594 | nsCSSSelector* selectorForPseudo; |
michael@0 | 2595 | if (isPseudoElement) { |
michael@0 | 2596 | if (selector->PseudoType() != aPseudoType) { |
michael@0 | 2597 | continue; |
michael@0 | 2598 | } |
michael@0 | 2599 | selectorForPseudo = selector; |
michael@0 | 2600 | selector = selector->mNext; |
michael@0 | 2601 | } |
michael@0 | 2602 | |
michael@0 | 2603 | nsRestyleHint possibleChange = RestyleHintForOp(selector->mOperator); |
michael@0 | 2604 | |
michael@0 | 2605 | // If hint already includes all the bits of possibleChange, |
michael@0 | 2606 | // don't bother calling SelectorMatches, since even if it returns false |
michael@0 | 2607 | // hint won't change. |
michael@0 | 2608 | // Also don't bother calling SelectorMatches if none of the |
michael@0 | 2609 | // states passed in are relevant here. |
michael@0 | 2610 | if ((possibleChange & ~hint) && |
michael@0 | 2611 | states.HasAtLeastOneOfStates(aStateMask) && |
michael@0 | 2612 | // We can optimize away testing selectors that only involve :hover, a |
michael@0 | 2613 | // namespace, and a tag name against nodes that don't have the |
michael@0 | 2614 | // NodeHasRelevantHoverRules flag: such a selector didn't match |
michael@0 | 2615 | // the tag name or namespace the first time around (since the :hover |
michael@0 | 2616 | // didn't set the NodeHasRelevantHoverRules flag), so it won't |
michael@0 | 2617 | // match it now. Check for our selector only having :hover states, or |
michael@0 | 2618 | // the element having the hover rules flag, or the selector having |
michael@0 | 2619 | // some sort of non-namespace, non-tagname data in it. |
michael@0 | 2620 | (states != NS_EVENT_STATE_HOVER || |
michael@0 | 2621 | aStatefulElement->HasRelevantHoverRules() || |
michael@0 | 2622 | selector->mIDList || selector->mClassList || |
michael@0 | 2623 | // We generally expect an mPseudoClassList, since we have a :hover. |
michael@0 | 2624 | // The question is whether we have anything else in there. |
michael@0 | 2625 | (selector->mPseudoClassList && |
michael@0 | 2626 | (selector->mPseudoClassList->mNext || |
michael@0 | 2627 | selector->mPseudoClassList->mType != |
michael@0 | 2628 | nsCSSPseudoClasses::ePseudoClass_hover)) || |
michael@0 | 2629 | selector->mAttrList || selector->mNegations) && |
michael@0 | 2630 | (!isPseudoElement || |
michael@0 | 2631 | StateSelectorMatches(aStatefulElement, selectorForPseudo, |
michael@0 | 2632 | nodeContext, aData->mTreeMatchContext, |
michael@0 | 2633 | nullptr, aStateMask)) && |
michael@0 | 2634 | SelectorMatches(aData->mElement, selector, nodeContext, |
michael@0 | 2635 | aData->mTreeMatchContext) && |
michael@0 | 2636 | SelectorMatchesTree(aData->mElement, selector->mNext, |
michael@0 | 2637 | aData->mTreeMatchContext, |
michael@0 | 2638 | false)) |
michael@0 | 2639 | { |
michael@0 | 2640 | hint = nsRestyleHint(hint | possibleChange); |
michael@0 | 2641 | } |
michael@0 | 2642 | } |
michael@0 | 2643 | } |
michael@0 | 2644 | return hint; |
michael@0 | 2645 | } |
michael@0 | 2646 | |
michael@0 | 2647 | nsRestyleHint |
michael@0 | 2648 | nsCSSRuleProcessor::HasStateDependentStyle(StateRuleProcessorData* aData) |
michael@0 | 2649 | { |
michael@0 | 2650 | return HasStateDependentStyle(aData, |
michael@0 | 2651 | aData->mElement, |
michael@0 | 2652 | nsCSSPseudoElements::ePseudo_NotPseudoElement, |
michael@0 | 2653 | aData->mStateMask); |
michael@0 | 2654 | } |
michael@0 | 2655 | |
michael@0 | 2656 | nsRestyleHint |
michael@0 | 2657 | nsCSSRuleProcessor::HasStateDependentStyle(PseudoElementStateRuleProcessorData* aData) |
michael@0 | 2658 | { |
michael@0 | 2659 | return HasStateDependentStyle(aData, |
michael@0 | 2660 | aData->mPseudoElement, |
michael@0 | 2661 | aData->mPseudoType, |
michael@0 | 2662 | aData->mStateMask); |
michael@0 | 2663 | } |
michael@0 | 2664 | |
michael@0 | 2665 | bool |
michael@0 | 2666 | nsCSSRuleProcessor::HasDocumentStateDependentStyle(StateRuleProcessorData* aData) |
michael@0 | 2667 | { |
michael@0 | 2668 | RuleCascadeData* cascade = GetRuleCascade(aData->mPresContext); |
michael@0 | 2669 | |
michael@0 | 2670 | return cascade && cascade->mSelectorDocumentStates.HasAtLeastOneOfStates(aData->mStateMask); |
michael@0 | 2671 | } |
michael@0 | 2672 | |
michael@0 | 2673 | struct AttributeEnumData { |
michael@0 | 2674 | AttributeEnumData(AttributeRuleProcessorData *aData) |
michael@0 | 2675 | : data(aData), change(nsRestyleHint(0)) {} |
michael@0 | 2676 | |
michael@0 | 2677 | AttributeRuleProcessorData *data; |
michael@0 | 2678 | nsRestyleHint change; |
michael@0 | 2679 | }; |
michael@0 | 2680 | |
michael@0 | 2681 | |
michael@0 | 2682 | static void |
michael@0 | 2683 | AttributeEnumFunc(nsCSSSelector* aSelector, AttributeEnumData* aData) |
michael@0 | 2684 | { |
michael@0 | 2685 | AttributeRuleProcessorData *data = aData->data; |
michael@0 | 2686 | |
michael@0 | 2687 | if (!data->mTreeMatchContext.SetStyleScopeForSelectorMatching(data->mElement, |
michael@0 | 2688 | data->mScope)) { |
michael@0 | 2689 | // The selector is for a rule in a scoped style sheet, and the subject |
michael@0 | 2690 | // of the selector matching is not in its scope. |
michael@0 | 2691 | return; |
michael@0 | 2692 | } |
michael@0 | 2693 | |
michael@0 | 2694 | nsRestyleHint possibleChange = RestyleHintForOp(aSelector->mOperator); |
michael@0 | 2695 | |
michael@0 | 2696 | // If enumData->change already includes all the bits of possibleChange, don't |
michael@0 | 2697 | // bother calling SelectorMatches, since even if it returns false |
michael@0 | 2698 | // enumData->change won't change. |
michael@0 | 2699 | NodeMatchContext nodeContext(EventStates(), false); |
michael@0 | 2700 | if ((possibleChange & ~(aData->change)) && |
michael@0 | 2701 | SelectorMatches(data->mElement, aSelector, nodeContext, |
michael@0 | 2702 | data->mTreeMatchContext) && |
michael@0 | 2703 | SelectorMatchesTree(data->mElement, aSelector->mNext, |
michael@0 | 2704 | data->mTreeMatchContext, false)) { |
michael@0 | 2705 | aData->change = nsRestyleHint(aData->change | possibleChange); |
michael@0 | 2706 | } |
michael@0 | 2707 | } |
michael@0 | 2708 | |
michael@0 | 2709 | static MOZ_ALWAYS_INLINE void |
michael@0 | 2710 | EnumerateSelectors(nsTArray<nsCSSSelector*>& aSelectors, AttributeEnumData* aData) |
michael@0 | 2711 | { |
michael@0 | 2712 | nsCSSSelector **iter = aSelectors.Elements(), |
michael@0 | 2713 | **end = iter + aSelectors.Length(); |
michael@0 | 2714 | for (; iter != end; ++iter) { |
michael@0 | 2715 | AttributeEnumFunc(*iter, aData); |
michael@0 | 2716 | } |
michael@0 | 2717 | } |
michael@0 | 2718 | |
michael@0 | 2719 | nsRestyleHint |
michael@0 | 2720 | nsCSSRuleProcessor::HasAttributeDependentStyle(AttributeRuleProcessorData* aData) |
michael@0 | 2721 | { |
michael@0 | 2722 | // We could try making use of aData->mModType, but :not rules make it a bit |
michael@0 | 2723 | // of a pain to do so... So just ignore it for now. |
michael@0 | 2724 | |
michael@0 | 2725 | AttributeEnumData data(aData); |
michael@0 | 2726 | |
michael@0 | 2727 | // Don't do our special handling of certain attributes if the attr |
michael@0 | 2728 | // hasn't changed yet. |
michael@0 | 2729 | if (aData->mAttrHasChanged) { |
michael@0 | 2730 | // check for the lwtheme and lwthemetextcolor attribute on root XUL elements |
michael@0 | 2731 | if ((aData->mAttribute == nsGkAtoms::lwtheme || |
michael@0 | 2732 | aData->mAttribute == nsGkAtoms::lwthemetextcolor) && |
michael@0 | 2733 | aData->mElement->GetNameSpaceID() == kNameSpaceID_XUL && |
michael@0 | 2734 | aData->mElement == aData->mElement->OwnerDoc()->GetRootElement()) |
michael@0 | 2735 | { |
michael@0 | 2736 | data.change = nsRestyleHint(data.change | eRestyle_Subtree); |
michael@0 | 2737 | } |
michael@0 | 2738 | |
michael@0 | 2739 | // We don't know the namespace of the attribute, and xml:lang applies to |
michael@0 | 2740 | // all elements. If the lang attribute changes, we need to restyle our |
michael@0 | 2741 | // whole subtree, since the :lang selector on our descendants can examine |
michael@0 | 2742 | // our lang attribute. |
michael@0 | 2743 | if (aData->mAttribute == nsGkAtoms::lang) { |
michael@0 | 2744 | data.change = nsRestyleHint(data.change | eRestyle_Subtree); |
michael@0 | 2745 | } |
michael@0 | 2746 | } |
michael@0 | 2747 | |
michael@0 | 2748 | RuleCascadeData* cascade = GetRuleCascade(aData->mPresContext); |
michael@0 | 2749 | |
michael@0 | 2750 | // Since we get both before and after notifications for attributes, we |
michael@0 | 2751 | // don't have to ignore aData->mAttribute while matching. Just check |
michael@0 | 2752 | // whether we have selectors relevant to aData->mAttribute that we |
michael@0 | 2753 | // match. If this is the before change notification, that will catch |
michael@0 | 2754 | // rules we might stop matching; if the after change notification, the |
michael@0 | 2755 | // ones we might have started matching. |
michael@0 | 2756 | if (cascade) { |
michael@0 | 2757 | if (aData->mAttribute == aData->mElement->GetIDAttributeName()) { |
michael@0 | 2758 | nsIAtom* id = aData->mElement->GetID(); |
michael@0 | 2759 | if (id) { |
michael@0 | 2760 | AtomSelectorEntry *entry = |
michael@0 | 2761 | static_cast<AtomSelectorEntry*> |
michael@0 | 2762 | (PL_DHashTableOperate(&cascade->mIdSelectors, |
michael@0 | 2763 | id, PL_DHASH_LOOKUP)); |
michael@0 | 2764 | if (PL_DHASH_ENTRY_IS_BUSY(entry)) { |
michael@0 | 2765 | EnumerateSelectors(entry->mSelectors, &data); |
michael@0 | 2766 | } |
michael@0 | 2767 | } |
michael@0 | 2768 | |
michael@0 | 2769 | EnumerateSelectors(cascade->mPossiblyNegatedIDSelectors, &data); |
michael@0 | 2770 | } |
michael@0 | 2771 | |
michael@0 | 2772 | if (aData->mAttribute == aData->mElement->GetClassAttributeName()) { |
michael@0 | 2773 | const nsAttrValue* elementClasses = aData->mElement->GetClasses(); |
michael@0 | 2774 | if (elementClasses) { |
michael@0 | 2775 | int32_t atomCount = elementClasses->GetAtomCount(); |
michael@0 | 2776 | for (int32_t i = 0; i < atomCount; ++i) { |
michael@0 | 2777 | nsIAtom* curClass = elementClasses->AtomAt(i); |
michael@0 | 2778 | AtomSelectorEntry *entry = |
michael@0 | 2779 | static_cast<AtomSelectorEntry*> |
michael@0 | 2780 | (PL_DHashTableOperate(&cascade->mClassSelectors, |
michael@0 | 2781 | curClass, PL_DHASH_LOOKUP)); |
michael@0 | 2782 | if (PL_DHASH_ENTRY_IS_BUSY(entry)) { |
michael@0 | 2783 | EnumerateSelectors(entry->mSelectors, &data); |
michael@0 | 2784 | } |
michael@0 | 2785 | } |
michael@0 | 2786 | } |
michael@0 | 2787 | |
michael@0 | 2788 | EnumerateSelectors(cascade->mPossiblyNegatedClassSelectors, &data); |
michael@0 | 2789 | } |
michael@0 | 2790 | |
michael@0 | 2791 | AtomSelectorEntry *entry = |
michael@0 | 2792 | static_cast<AtomSelectorEntry*> |
michael@0 | 2793 | (PL_DHashTableOperate(&cascade->mAttributeSelectors, |
michael@0 | 2794 | aData->mAttribute, PL_DHASH_LOOKUP)); |
michael@0 | 2795 | if (PL_DHASH_ENTRY_IS_BUSY(entry)) { |
michael@0 | 2796 | EnumerateSelectors(entry->mSelectors, &data); |
michael@0 | 2797 | } |
michael@0 | 2798 | } |
michael@0 | 2799 | |
michael@0 | 2800 | return data.change; |
michael@0 | 2801 | } |
michael@0 | 2802 | |
michael@0 | 2803 | /* virtual */ bool |
michael@0 | 2804 | nsCSSRuleProcessor::MediumFeaturesChanged(nsPresContext* aPresContext) |
michael@0 | 2805 | { |
michael@0 | 2806 | RuleCascadeData *old = mRuleCascades; |
michael@0 | 2807 | // We don't want to do anything if there aren't any sets of rules |
michael@0 | 2808 | // cached yet (or somebody cleared them and is thus responsible for |
michael@0 | 2809 | // rebuilding things), since we should not build the rule cascade too |
michael@0 | 2810 | // early (e.g., before we know whether the quirk style sheet should be |
michael@0 | 2811 | // enabled). And if there's nothing cached, it doesn't matter if |
michael@0 | 2812 | // anything changed. See bug 448281. |
michael@0 | 2813 | if (old) { |
michael@0 | 2814 | RefreshRuleCascade(aPresContext); |
michael@0 | 2815 | } |
michael@0 | 2816 | return (old != mRuleCascades); |
michael@0 | 2817 | } |
michael@0 | 2818 | |
michael@0 | 2819 | /* virtual */ size_t |
michael@0 | 2820 | nsCSSRuleProcessor::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const |
michael@0 | 2821 | { |
michael@0 | 2822 | size_t n = 0; |
michael@0 | 2823 | n += mSheets.SizeOfExcludingThis(aMallocSizeOf); |
michael@0 | 2824 | for (RuleCascadeData* cascade = mRuleCascades; cascade; |
michael@0 | 2825 | cascade = cascade->mNext) { |
michael@0 | 2826 | n += cascade->SizeOfIncludingThis(aMallocSizeOf); |
michael@0 | 2827 | } |
michael@0 | 2828 | |
michael@0 | 2829 | return n; |
michael@0 | 2830 | } |
michael@0 | 2831 | |
michael@0 | 2832 | /* virtual */ size_t |
michael@0 | 2833 | nsCSSRuleProcessor::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const |
michael@0 | 2834 | { |
michael@0 | 2835 | return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf); |
michael@0 | 2836 | } |
michael@0 | 2837 | |
michael@0 | 2838 | // Append all the currently-active font face rules to aArray. Return |
michael@0 | 2839 | // true for success and false for failure. |
michael@0 | 2840 | bool |
michael@0 | 2841 | nsCSSRuleProcessor::AppendFontFaceRules( |
michael@0 | 2842 | nsPresContext *aPresContext, |
michael@0 | 2843 | nsTArray<nsFontFaceRuleContainer>& aArray) |
michael@0 | 2844 | { |
michael@0 | 2845 | RuleCascadeData* cascade = GetRuleCascade(aPresContext); |
michael@0 | 2846 | |
michael@0 | 2847 | if (cascade) { |
michael@0 | 2848 | if (!aArray.AppendElements(cascade->mFontFaceRules)) |
michael@0 | 2849 | return false; |
michael@0 | 2850 | } |
michael@0 | 2851 | |
michael@0 | 2852 | return true; |
michael@0 | 2853 | } |
michael@0 | 2854 | |
michael@0 | 2855 | nsCSSKeyframesRule* |
michael@0 | 2856 | nsCSSRuleProcessor::KeyframesRuleForName(nsPresContext* aPresContext, |
michael@0 | 2857 | const nsString& aName) |
michael@0 | 2858 | { |
michael@0 | 2859 | RuleCascadeData* cascade = GetRuleCascade(aPresContext); |
michael@0 | 2860 | |
michael@0 | 2861 | if (cascade) { |
michael@0 | 2862 | return cascade->mKeyframesRuleTable.Get(aName); |
michael@0 | 2863 | } |
michael@0 | 2864 | |
michael@0 | 2865 | return nullptr; |
michael@0 | 2866 | } |
michael@0 | 2867 | |
michael@0 | 2868 | // Append all the currently-active page rules to aArray. Return |
michael@0 | 2869 | // true for success and false for failure. |
michael@0 | 2870 | bool |
michael@0 | 2871 | nsCSSRuleProcessor::AppendPageRules( |
michael@0 | 2872 | nsPresContext* aPresContext, |
michael@0 | 2873 | nsTArray<nsCSSPageRule*>& aArray) |
michael@0 | 2874 | { |
michael@0 | 2875 | RuleCascadeData* cascade = GetRuleCascade(aPresContext); |
michael@0 | 2876 | |
michael@0 | 2877 | if (cascade) { |
michael@0 | 2878 | if (!aArray.AppendElements(cascade->mPageRules)) { |
michael@0 | 2879 | return false; |
michael@0 | 2880 | } |
michael@0 | 2881 | } |
michael@0 | 2882 | |
michael@0 | 2883 | return true; |
michael@0 | 2884 | } |
michael@0 | 2885 | |
michael@0 | 2886 | bool |
michael@0 | 2887 | nsCSSRuleProcessor::AppendFontFeatureValuesRules( |
michael@0 | 2888 | nsPresContext *aPresContext, |
michael@0 | 2889 | nsTArray<nsCSSFontFeatureValuesRule*>& aArray) |
michael@0 | 2890 | { |
michael@0 | 2891 | RuleCascadeData* cascade = GetRuleCascade(aPresContext); |
michael@0 | 2892 | |
michael@0 | 2893 | if (cascade) { |
michael@0 | 2894 | if (!aArray.AppendElements(cascade->mFontFeatureValuesRules)) |
michael@0 | 2895 | return false; |
michael@0 | 2896 | } |
michael@0 | 2897 | |
michael@0 | 2898 | return true; |
michael@0 | 2899 | } |
michael@0 | 2900 | |
michael@0 | 2901 | nsresult |
michael@0 | 2902 | nsCSSRuleProcessor::ClearRuleCascades() |
michael@0 | 2903 | { |
michael@0 | 2904 | // We rely on our caller (perhaps indirectly) to do something that |
michael@0 | 2905 | // will rebuild style data and the user font set (either |
michael@0 | 2906 | // nsIPresShell::ReconstructStyleData or |
michael@0 | 2907 | // nsPresContext::RebuildAllStyleData). |
michael@0 | 2908 | RuleCascadeData *data = mRuleCascades; |
michael@0 | 2909 | mRuleCascades = nullptr; |
michael@0 | 2910 | while (data) { |
michael@0 | 2911 | RuleCascadeData *next = data->mNext; |
michael@0 | 2912 | delete data; |
michael@0 | 2913 | data = next; |
michael@0 | 2914 | } |
michael@0 | 2915 | return NS_OK; |
michael@0 | 2916 | } |
michael@0 | 2917 | |
michael@0 | 2918 | |
michael@0 | 2919 | // This function should return the set of states that this selector |
michael@0 | 2920 | // depends on; this is used to implement HasStateDependentStyle. It |
michael@0 | 2921 | // does NOT recur down into things like :not and :-moz-any. |
michael@0 | 2922 | inline |
michael@0 | 2923 | EventStates ComputeSelectorStateDependence(nsCSSSelector& aSelector) |
michael@0 | 2924 | { |
michael@0 | 2925 | EventStates states; |
michael@0 | 2926 | for (nsPseudoClassList* pseudoClass = aSelector.mPseudoClassList; |
michael@0 | 2927 | pseudoClass; pseudoClass = pseudoClass->mNext) { |
michael@0 | 2928 | // Tree pseudo-elements overload mPseudoClassList for things that |
michael@0 | 2929 | // aren't pseudo-classes. |
michael@0 | 2930 | if (pseudoClass->mType >= nsCSSPseudoClasses::ePseudoClass_Count) { |
michael@0 | 2931 | continue; |
michael@0 | 2932 | } |
michael@0 | 2933 | states |= sPseudoClassStateDependences[pseudoClass->mType]; |
michael@0 | 2934 | } |
michael@0 | 2935 | return states; |
michael@0 | 2936 | } |
michael@0 | 2937 | |
michael@0 | 2938 | static bool |
michael@0 | 2939 | AddSelector(RuleCascadeData* aCascade, |
michael@0 | 2940 | // The part between combinators at the top level of the selector |
michael@0 | 2941 | nsCSSSelector* aSelectorInTopLevel, |
michael@0 | 2942 | // The part we should look through (might be in :not or :-moz-any()) |
michael@0 | 2943 | nsCSSSelector* aSelectorPart) |
michael@0 | 2944 | { |
michael@0 | 2945 | // It's worth noting that this loop over negations isn't quite |
michael@0 | 2946 | // optimal for two reasons. One, we could add something to one of |
michael@0 | 2947 | // these lists twice, which means we'll check it twice, but I don't |
michael@0 | 2948 | // think that's worth worrying about. (We do the same for multiple |
michael@0 | 2949 | // attribute selectors on the same attribute.) Two, we don't really |
michael@0 | 2950 | // need to check negations past the first in the current |
michael@0 | 2951 | // implementation (and they're rare as well), but that might change |
michael@0 | 2952 | // in the future if :not() is extended. |
michael@0 | 2953 | for (nsCSSSelector* negation = aSelectorPart; negation; |
michael@0 | 2954 | negation = negation->mNegations) { |
michael@0 | 2955 | // Track both document states and attribute dependence in pseudo-classes. |
michael@0 | 2956 | for (nsPseudoClassList* pseudoClass = negation->mPseudoClassList; |
michael@0 | 2957 | pseudoClass; pseudoClass = pseudoClass->mNext) { |
michael@0 | 2958 | switch (pseudoClass->mType) { |
michael@0 | 2959 | case nsCSSPseudoClasses::ePseudoClass_mozLocaleDir: { |
michael@0 | 2960 | aCascade->mSelectorDocumentStates |= NS_DOCUMENT_STATE_RTL_LOCALE; |
michael@0 | 2961 | break; |
michael@0 | 2962 | } |
michael@0 | 2963 | case nsCSSPseudoClasses::ePseudoClass_mozWindowInactive: { |
michael@0 | 2964 | aCascade->mSelectorDocumentStates |= NS_DOCUMENT_STATE_WINDOW_INACTIVE; |
michael@0 | 2965 | break; |
michael@0 | 2966 | } |
michael@0 | 2967 | case nsCSSPseudoClasses::ePseudoClass_mozTableBorderNonzero: { |
michael@0 | 2968 | nsTArray<nsCSSSelector*> *array = |
michael@0 | 2969 | aCascade->AttributeListFor(nsGkAtoms::border); |
michael@0 | 2970 | if (!array) { |
michael@0 | 2971 | return false; |
michael@0 | 2972 | } |
michael@0 | 2973 | array->AppendElement(aSelectorInTopLevel); |
michael@0 | 2974 | break; |
michael@0 | 2975 | } |
michael@0 | 2976 | default: { |
michael@0 | 2977 | break; |
michael@0 | 2978 | } |
michael@0 | 2979 | } |
michael@0 | 2980 | } |
michael@0 | 2981 | |
michael@0 | 2982 | // Build mStateSelectors. |
michael@0 | 2983 | EventStates dependentStates = ComputeSelectorStateDependence(*negation); |
michael@0 | 2984 | if (!dependentStates.IsEmpty()) { |
michael@0 | 2985 | aCascade->mStateSelectors.AppendElement( |
michael@0 | 2986 | nsCSSRuleProcessor::StateSelector(dependentStates, |
michael@0 | 2987 | aSelectorInTopLevel)); |
michael@0 | 2988 | } |
michael@0 | 2989 | |
michael@0 | 2990 | // Build mIDSelectors |
michael@0 | 2991 | if (negation == aSelectorInTopLevel) { |
michael@0 | 2992 | for (nsAtomList* curID = negation->mIDList; curID; |
michael@0 | 2993 | curID = curID->mNext) { |
michael@0 | 2994 | AtomSelectorEntry *entry = |
michael@0 | 2995 | static_cast<AtomSelectorEntry*>(PL_DHashTableOperate(&aCascade->mIdSelectors, |
michael@0 | 2996 | curID->mAtom, |
michael@0 | 2997 | PL_DHASH_ADD)); |
michael@0 | 2998 | if (entry) { |
michael@0 | 2999 | entry->mSelectors.AppendElement(aSelectorInTopLevel); |
michael@0 | 3000 | } |
michael@0 | 3001 | } |
michael@0 | 3002 | } else if (negation->mIDList) { |
michael@0 | 3003 | aCascade->mPossiblyNegatedIDSelectors.AppendElement(aSelectorInTopLevel); |
michael@0 | 3004 | } |
michael@0 | 3005 | |
michael@0 | 3006 | // Build mClassSelectors |
michael@0 | 3007 | if (negation == aSelectorInTopLevel) { |
michael@0 | 3008 | for (nsAtomList* curClass = negation->mClassList; curClass; |
michael@0 | 3009 | curClass = curClass->mNext) { |
michael@0 | 3010 | AtomSelectorEntry *entry = |
michael@0 | 3011 | static_cast<AtomSelectorEntry*>(PL_DHashTableOperate(&aCascade->mClassSelectors, |
michael@0 | 3012 | curClass->mAtom, |
michael@0 | 3013 | PL_DHASH_ADD)); |
michael@0 | 3014 | if (entry) { |
michael@0 | 3015 | entry->mSelectors.AppendElement(aSelectorInTopLevel); |
michael@0 | 3016 | } |
michael@0 | 3017 | } |
michael@0 | 3018 | } else if (negation->mClassList) { |
michael@0 | 3019 | aCascade->mPossiblyNegatedClassSelectors.AppendElement(aSelectorInTopLevel); |
michael@0 | 3020 | } |
michael@0 | 3021 | |
michael@0 | 3022 | // Build mAttributeSelectors. |
michael@0 | 3023 | for (nsAttrSelector *attr = negation->mAttrList; attr; |
michael@0 | 3024 | attr = attr->mNext) { |
michael@0 | 3025 | nsTArray<nsCSSSelector*> *array = |
michael@0 | 3026 | aCascade->AttributeListFor(attr->mCasedAttr); |
michael@0 | 3027 | if (!array) { |
michael@0 | 3028 | return false; |
michael@0 | 3029 | } |
michael@0 | 3030 | array->AppendElement(aSelectorInTopLevel); |
michael@0 | 3031 | if (attr->mLowercaseAttr != attr->mCasedAttr) { |
michael@0 | 3032 | array = aCascade->AttributeListFor(attr->mLowercaseAttr); |
michael@0 | 3033 | if (!array) { |
michael@0 | 3034 | return false; |
michael@0 | 3035 | } |
michael@0 | 3036 | array->AppendElement(aSelectorInTopLevel); |
michael@0 | 3037 | } |
michael@0 | 3038 | } |
michael@0 | 3039 | |
michael@0 | 3040 | // Recur through any :-moz-any selectors |
michael@0 | 3041 | for (nsPseudoClassList* pseudoClass = negation->mPseudoClassList; |
michael@0 | 3042 | pseudoClass; pseudoClass = pseudoClass->mNext) { |
michael@0 | 3043 | if (pseudoClass->mType == nsCSSPseudoClasses::ePseudoClass_any) { |
michael@0 | 3044 | for (nsCSSSelectorList *l = pseudoClass->u.mSelectors; l; l = l->mNext) { |
michael@0 | 3045 | nsCSSSelector *s = l->mSelectors; |
michael@0 | 3046 | if (!AddSelector(aCascade, aSelectorInTopLevel, s)) { |
michael@0 | 3047 | return false; |
michael@0 | 3048 | } |
michael@0 | 3049 | } |
michael@0 | 3050 | } |
michael@0 | 3051 | } |
michael@0 | 3052 | } |
michael@0 | 3053 | |
michael@0 | 3054 | return true; |
michael@0 | 3055 | } |
michael@0 | 3056 | |
michael@0 | 3057 | static bool |
michael@0 | 3058 | AddRule(RuleSelectorPair* aRuleInfo, RuleCascadeData* aCascade) |
michael@0 | 3059 | { |
michael@0 | 3060 | RuleCascadeData * const cascade = aCascade; |
michael@0 | 3061 | |
michael@0 | 3062 | // Build the rule hash. |
michael@0 | 3063 | nsCSSPseudoElements::Type pseudoType = aRuleInfo->mSelector->PseudoType(); |
michael@0 | 3064 | if (MOZ_LIKELY(pseudoType == nsCSSPseudoElements::ePseudo_NotPseudoElement)) { |
michael@0 | 3065 | cascade->mRuleHash.AppendRule(*aRuleInfo); |
michael@0 | 3066 | } else if (pseudoType < nsCSSPseudoElements::ePseudo_PseudoElementCount) { |
michael@0 | 3067 | RuleHash*& ruleHash = cascade->mPseudoElementRuleHashes[pseudoType]; |
michael@0 | 3068 | if (!ruleHash) { |
michael@0 | 3069 | ruleHash = new RuleHash(cascade->mQuirksMode); |
michael@0 | 3070 | if (!ruleHash) { |
michael@0 | 3071 | // Out of memory; give up |
michael@0 | 3072 | return false; |
michael@0 | 3073 | } |
michael@0 | 3074 | } |
michael@0 | 3075 | NS_ASSERTION(aRuleInfo->mSelector->mNext, |
michael@0 | 3076 | "Must have mNext; parser screwed up"); |
michael@0 | 3077 | NS_ASSERTION(aRuleInfo->mSelector->mNext->mOperator == ':', |
michael@0 | 3078 | "Unexpected mNext combinator"); |
michael@0 | 3079 | ruleHash->AppendRule(*aRuleInfo); |
michael@0 | 3080 | } else if (pseudoType == nsCSSPseudoElements::ePseudo_AnonBox) { |
michael@0 | 3081 | NS_ASSERTION(!aRuleInfo->mSelector->mCasedTag && |
michael@0 | 3082 | !aRuleInfo->mSelector->mIDList && |
michael@0 | 3083 | !aRuleInfo->mSelector->mClassList && |
michael@0 | 3084 | !aRuleInfo->mSelector->mPseudoClassList && |
michael@0 | 3085 | !aRuleInfo->mSelector->mAttrList && |
michael@0 | 3086 | !aRuleInfo->mSelector->mNegations && |
michael@0 | 3087 | !aRuleInfo->mSelector->mNext && |
michael@0 | 3088 | aRuleInfo->mSelector->mNameSpace == kNameSpaceID_Unknown, |
michael@0 | 3089 | "Parser messed up with anon box selector"); |
michael@0 | 3090 | |
michael@0 | 3091 | // Index doesn't matter here, since we'll just be walking these |
michael@0 | 3092 | // rules in order; just pass 0. |
michael@0 | 3093 | AppendRuleToTagTable(&cascade->mAnonBoxRules, |
michael@0 | 3094 | aRuleInfo->mSelector->mLowercaseTag, |
michael@0 | 3095 | RuleValue(*aRuleInfo, 0, aCascade->mQuirksMode)); |
michael@0 | 3096 | } else { |
michael@0 | 3097 | #ifdef MOZ_XUL |
michael@0 | 3098 | NS_ASSERTION(pseudoType == nsCSSPseudoElements::ePseudo_XULTree, |
michael@0 | 3099 | "Unexpected pseudo type"); |
michael@0 | 3100 | // Index doesn't matter here, since we'll just be walking these |
michael@0 | 3101 | // rules in order; just pass 0. |
michael@0 | 3102 | AppendRuleToTagTable(&cascade->mXULTreeRules, |
michael@0 | 3103 | aRuleInfo->mSelector->mLowercaseTag, |
michael@0 | 3104 | RuleValue(*aRuleInfo, 0, aCascade->mQuirksMode)); |
michael@0 | 3105 | #else |
michael@0 | 3106 | NS_NOTREACHED("Unexpected pseudo type"); |
michael@0 | 3107 | #endif |
michael@0 | 3108 | } |
michael@0 | 3109 | |
michael@0 | 3110 | for (nsCSSSelector* selector = aRuleInfo->mSelector; |
michael@0 | 3111 | selector; selector = selector->mNext) { |
michael@0 | 3112 | if (selector->IsPseudoElement()) { |
michael@0 | 3113 | nsCSSPseudoElements::Type pseudo = selector->PseudoType(); |
michael@0 | 3114 | if (pseudo >= nsCSSPseudoElements::ePseudo_PseudoElementCount || |
michael@0 | 3115 | !nsCSSPseudoElements::PseudoElementSupportsUserActionState(pseudo)) { |
michael@0 | 3116 | NS_ASSERTION(!selector->mNegations, "Shouldn't have negations"); |
michael@0 | 3117 | // We do store selectors ending with pseudo-elements that allow :hover |
michael@0 | 3118 | // and :active after them in the hashtables corresponding to that |
michael@0 | 3119 | // selector's mNext (i.e. the thing that matches against the element), |
michael@0 | 3120 | // but we want to make sure that selectors for any other kinds of |
michael@0 | 3121 | // pseudo-elements don't end up in the hashtables. In particular, tree |
michael@0 | 3122 | // pseudos store strange things in mPseudoClassList that we don't want |
michael@0 | 3123 | // to try to match elements against. |
michael@0 | 3124 | continue; |
michael@0 | 3125 | } |
michael@0 | 3126 | } |
michael@0 | 3127 | if (!AddSelector(cascade, selector, selector)) { |
michael@0 | 3128 | return false; |
michael@0 | 3129 | } |
michael@0 | 3130 | } |
michael@0 | 3131 | |
michael@0 | 3132 | return true; |
michael@0 | 3133 | } |
michael@0 | 3134 | |
michael@0 | 3135 | struct PerWeightDataListItem : public RuleSelectorPair { |
michael@0 | 3136 | PerWeightDataListItem(css::StyleRule* aRule, nsCSSSelector* aSelector) |
michael@0 | 3137 | : RuleSelectorPair(aRule, aSelector) |
michael@0 | 3138 | , mNext(nullptr) |
michael@0 | 3139 | {} |
michael@0 | 3140 | // No destructor; these are arena-allocated |
michael@0 | 3141 | |
michael@0 | 3142 | |
michael@0 | 3143 | // Placement new to arena allocate the PerWeightDataListItem |
michael@0 | 3144 | void *operator new(size_t aSize, PLArenaPool &aArena) CPP_THROW_NEW { |
michael@0 | 3145 | void *mem; |
michael@0 | 3146 | PL_ARENA_ALLOCATE(mem, &aArena, aSize); |
michael@0 | 3147 | return mem; |
michael@0 | 3148 | } |
michael@0 | 3149 | |
michael@0 | 3150 | PerWeightDataListItem *mNext; |
michael@0 | 3151 | }; |
michael@0 | 3152 | |
michael@0 | 3153 | struct PerWeightData { |
michael@0 | 3154 | PerWeightData() |
michael@0 | 3155 | : mRuleSelectorPairs(nullptr) |
michael@0 | 3156 | , mTail(&mRuleSelectorPairs) |
michael@0 | 3157 | {} |
michael@0 | 3158 | |
michael@0 | 3159 | int32_t mWeight; |
michael@0 | 3160 | PerWeightDataListItem *mRuleSelectorPairs; |
michael@0 | 3161 | PerWeightDataListItem **mTail; |
michael@0 | 3162 | }; |
michael@0 | 3163 | |
michael@0 | 3164 | struct RuleByWeightEntry : public PLDHashEntryHdr { |
michael@0 | 3165 | PerWeightData data; // mWeight is key, mRuleSelectorPairs are value |
michael@0 | 3166 | }; |
michael@0 | 3167 | |
michael@0 | 3168 | static PLDHashNumber |
michael@0 | 3169 | HashIntKey(PLDHashTable *table, const void *key) |
michael@0 | 3170 | { |
michael@0 | 3171 | return PLDHashNumber(NS_PTR_TO_INT32(key)); |
michael@0 | 3172 | } |
michael@0 | 3173 | |
michael@0 | 3174 | static bool |
michael@0 | 3175 | MatchWeightEntry(PLDHashTable *table, const PLDHashEntryHdr *hdr, |
michael@0 | 3176 | const void *key) |
michael@0 | 3177 | { |
michael@0 | 3178 | const RuleByWeightEntry *entry = (const RuleByWeightEntry *)hdr; |
michael@0 | 3179 | return entry->data.mWeight == NS_PTR_TO_INT32(key); |
michael@0 | 3180 | } |
michael@0 | 3181 | |
michael@0 | 3182 | static bool |
michael@0 | 3183 | InitWeightEntry(PLDHashTable *table, PLDHashEntryHdr *hdr, |
michael@0 | 3184 | const void *key) |
michael@0 | 3185 | { |
michael@0 | 3186 | RuleByWeightEntry* entry = static_cast<RuleByWeightEntry*>(hdr); |
michael@0 | 3187 | new (entry) RuleByWeightEntry(); |
michael@0 | 3188 | return true; |
michael@0 | 3189 | } |
michael@0 | 3190 | |
michael@0 | 3191 | static const PLDHashTableOps gRulesByWeightOps = { |
michael@0 | 3192 | PL_DHashAllocTable, |
michael@0 | 3193 | PL_DHashFreeTable, |
michael@0 | 3194 | HashIntKey, |
michael@0 | 3195 | MatchWeightEntry, |
michael@0 | 3196 | PL_DHashMoveEntryStub, |
michael@0 | 3197 | PL_DHashClearEntryStub, |
michael@0 | 3198 | PL_DHashFinalizeStub, |
michael@0 | 3199 | InitWeightEntry |
michael@0 | 3200 | }; |
michael@0 | 3201 | |
michael@0 | 3202 | struct CascadeEnumData { |
michael@0 | 3203 | CascadeEnumData(nsPresContext* aPresContext, |
michael@0 | 3204 | nsTArray<nsFontFaceRuleContainer>& aFontFaceRules, |
michael@0 | 3205 | nsTArray<nsCSSKeyframesRule*>& aKeyframesRules, |
michael@0 | 3206 | nsTArray<nsCSSFontFeatureValuesRule*>& aFontFeatureValuesRules, |
michael@0 | 3207 | nsTArray<nsCSSPageRule*>& aPageRules, |
michael@0 | 3208 | nsMediaQueryResultCacheKey& aKey, |
michael@0 | 3209 | uint8_t aSheetType) |
michael@0 | 3210 | : mPresContext(aPresContext), |
michael@0 | 3211 | mFontFaceRules(aFontFaceRules), |
michael@0 | 3212 | mKeyframesRules(aKeyframesRules), |
michael@0 | 3213 | mFontFeatureValuesRules(aFontFeatureValuesRules), |
michael@0 | 3214 | mPageRules(aPageRules), |
michael@0 | 3215 | mCacheKey(aKey), |
michael@0 | 3216 | mSheetType(aSheetType) |
michael@0 | 3217 | { |
michael@0 | 3218 | if (!PL_DHashTableInit(&mRulesByWeight, &gRulesByWeightOps, nullptr, |
michael@0 | 3219 | sizeof(RuleByWeightEntry), 64, fallible_t())) |
michael@0 | 3220 | mRulesByWeight.ops = nullptr; |
michael@0 | 3221 | |
michael@0 | 3222 | // Initialize our arena |
michael@0 | 3223 | PL_INIT_ARENA_POOL(&mArena, "CascadeEnumDataArena", |
michael@0 | 3224 | NS_CASCADEENUMDATA_ARENA_BLOCK_SIZE); |
michael@0 | 3225 | } |
michael@0 | 3226 | |
michael@0 | 3227 | ~CascadeEnumData() |
michael@0 | 3228 | { |
michael@0 | 3229 | if (mRulesByWeight.ops) |
michael@0 | 3230 | PL_DHashTableFinish(&mRulesByWeight); |
michael@0 | 3231 | PL_FinishArenaPool(&mArena); |
michael@0 | 3232 | } |
michael@0 | 3233 | |
michael@0 | 3234 | nsPresContext* mPresContext; |
michael@0 | 3235 | nsTArray<nsFontFaceRuleContainer>& mFontFaceRules; |
michael@0 | 3236 | nsTArray<nsCSSKeyframesRule*>& mKeyframesRules; |
michael@0 | 3237 | nsTArray<nsCSSFontFeatureValuesRule*>& mFontFeatureValuesRules; |
michael@0 | 3238 | nsTArray<nsCSSPageRule*>& mPageRules; |
michael@0 | 3239 | nsMediaQueryResultCacheKey& mCacheKey; |
michael@0 | 3240 | PLArenaPool mArena; |
michael@0 | 3241 | // Hooray, a manual PLDHashTable since nsClassHashtable doesn't |
michael@0 | 3242 | // provide a getter that gives me a *reference* to the value. |
michael@0 | 3243 | PLDHashTable mRulesByWeight; // of PerWeightDataListItem linked lists |
michael@0 | 3244 | uint8_t mSheetType; |
michael@0 | 3245 | }; |
michael@0 | 3246 | |
michael@0 | 3247 | /* |
michael@0 | 3248 | * This enumerates style rules in a sheet (and recursively into any |
michael@0 | 3249 | * grouping rules) in order to: |
michael@0 | 3250 | * (1) add any style rules, in order, into data->mRulesByWeight (for |
michael@0 | 3251 | * the primary CSS cascade), where they are separated by weight |
michael@0 | 3252 | * but kept in order per-weight, and |
michael@0 | 3253 | * (2) add any @font-face rules, in order, into data->mFontFaceRules. |
michael@0 | 3254 | * (3) add any @keyframes rules, in order, into data->mKeyframesRules. |
michael@0 | 3255 | * (4) add any @font-feature-value rules, in order, |
michael@0 | 3256 | * into data->mFontFeatureValuesRules. |
michael@0 | 3257 | * (5) add any @page rules, in order, into data->mPageRules. |
michael@0 | 3258 | */ |
michael@0 | 3259 | static bool |
michael@0 | 3260 | CascadeRuleEnumFunc(css::Rule* aRule, void* aData) |
michael@0 | 3261 | { |
michael@0 | 3262 | CascadeEnumData* data = (CascadeEnumData*)aData; |
michael@0 | 3263 | int32_t type = aRule->GetType(); |
michael@0 | 3264 | |
michael@0 | 3265 | if (css::Rule::STYLE_RULE == type) { |
michael@0 | 3266 | css::StyleRule* styleRule = static_cast<css::StyleRule*>(aRule); |
michael@0 | 3267 | |
michael@0 | 3268 | for (nsCSSSelectorList *sel = styleRule->Selector(); |
michael@0 | 3269 | sel; sel = sel->mNext) { |
michael@0 | 3270 | int32_t weight = sel->mWeight; |
michael@0 | 3271 | RuleByWeightEntry *entry = static_cast<RuleByWeightEntry*>( |
michael@0 | 3272 | PL_DHashTableOperate(&data->mRulesByWeight, NS_INT32_TO_PTR(weight), |
michael@0 | 3273 | PL_DHASH_ADD)); |
michael@0 | 3274 | if (!entry) |
michael@0 | 3275 | return false; |
michael@0 | 3276 | entry->data.mWeight = weight; |
michael@0 | 3277 | // entry->data.mRuleSelectorPairs should be linked in forward order; |
michael@0 | 3278 | // entry->data.mTail is the slot to write to. |
michael@0 | 3279 | PerWeightDataListItem *newItem = |
michael@0 | 3280 | new (data->mArena) PerWeightDataListItem(styleRule, sel->mSelectors); |
michael@0 | 3281 | if (newItem) { |
michael@0 | 3282 | *(entry->data.mTail) = newItem; |
michael@0 | 3283 | entry->data.mTail = &newItem->mNext; |
michael@0 | 3284 | } |
michael@0 | 3285 | } |
michael@0 | 3286 | } |
michael@0 | 3287 | else if (css::Rule::MEDIA_RULE == type || |
michael@0 | 3288 | css::Rule::DOCUMENT_RULE == type || |
michael@0 | 3289 | css::Rule::SUPPORTS_RULE == type) { |
michael@0 | 3290 | css::GroupRule* groupRule = static_cast<css::GroupRule*>(aRule); |
michael@0 | 3291 | if (groupRule->UseForPresentation(data->mPresContext, data->mCacheKey)) |
michael@0 | 3292 | if (!groupRule->EnumerateRulesForwards(CascadeRuleEnumFunc, aData)) |
michael@0 | 3293 | return false; |
michael@0 | 3294 | } |
michael@0 | 3295 | else if (css::Rule::FONT_FACE_RULE == type) { |
michael@0 | 3296 | nsCSSFontFaceRule *fontFaceRule = static_cast<nsCSSFontFaceRule*>(aRule); |
michael@0 | 3297 | nsFontFaceRuleContainer *ptr = data->mFontFaceRules.AppendElement(); |
michael@0 | 3298 | if (!ptr) |
michael@0 | 3299 | return false; |
michael@0 | 3300 | ptr->mRule = fontFaceRule; |
michael@0 | 3301 | ptr->mSheetType = data->mSheetType; |
michael@0 | 3302 | } |
michael@0 | 3303 | else if (css::Rule::KEYFRAMES_RULE == type) { |
michael@0 | 3304 | nsCSSKeyframesRule *keyframesRule = |
michael@0 | 3305 | static_cast<nsCSSKeyframesRule*>(aRule); |
michael@0 | 3306 | if (!data->mKeyframesRules.AppendElement(keyframesRule)) { |
michael@0 | 3307 | return false; |
michael@0 | 3308 | } |
michael@0 | 3309 | } |
michael@0 | 3310 | else if (css::Rule::FONT_FEATURE_VALUES_RULE == type) { |
michael@0 | 3311 | nsCSSFontFeatureValuesRule *fontFeatureValuesRule = |
michael@0 | 3312 | static_cast<nsCSSFontFeatureValuesRule*>(aRule); |
michael@0 | 3313 | if (!data->mFontFeatureValuesRules.AppendElement(fontFeatureValuesRule)) { |
michael@0 | 3314 | return false; |
michael@0 | 3315 | } |
michael@0 | 3316 | } |
michael@0 | 3317 | else if (css::Rule::PAGE_RULE == type) { |
michael@0 | 3318 | nsCSSPageRule* pageRule = static_cast<nsCSSPageRule*>(aRule); |
michael@0 | 3319 | if (!data->mPageRules.AppendElement(pageRule)) { |
michael@0 | 3320 | return false; |
michael@0 | 3321 | } |
michael@0 | 3322 | } |
michael@0 | 3323 | return true; |
michael@0 | 3324 | } |
michael@0 | 3325 | |
michael@0 | 3326 | /* static */ bool |
michael@0 | 3327 | nsCSSRuleProcessor::CascadeSheet(nsCSSStyleSheet* aSheet, CascadeEnumData* aData) |
michael@0 | 3328 | { |
michael@0 | 3329 | if (aSheet->IsApplicable() && |
michael@0 | 3330 | aSheet->UseForPresentation(aData->mPresContext, aData->mCacheKey) && |
michael@0 | 3331 | aSheet->mInner) { |
michael@0 | 3332 | nsCSSStyleSheet* child = aSheet->mInner->mFirstChild; |
michael@0 | 3333 | while (child) { |
michael@0 | 3334 | CascadeSheet(child, aData); |
michael@0 | 3335 | child = child->mNext; |
michael@0 | 3336 | } |
michael@0 | 3337 | |
michael@0 | 3338 | if (!aSheet->mInner->mOrderedRules.EnumerateForwards(CascadeRuleEnumFunc, |
michael@0 | 3339 | aData)) |
michael@0 | 3340 | return false; |
michael@0 | 3341 | } |
michael@0 | 3342 | return true; |
michael@0 | 3343 | } |
michael@0 | 3344 | |
michael@0 | 3345 | static int CompareWeightData(const void* aArg1, const void* aArg2, |
michael@0 | 3346 | void* closure) |
michael@0 | 3347 | { |
michael@0 | 3348 | const PerWeightData* arg1 = static_cast<const PerWeightData*>(aArg1); |
michael@0 | 3349 | const PerWeightData* arg2 = static_cast<const PerWeightData*>(aArg2); |
michael@0 | 3350 | return arg1->mWeight - arg2->mWeight; // put lower weight first |
michael@0 | 3351 | } |
michael@0 | 3352 | |
michael@0 | 3353 | |
michael@0 | 3354 | struct FillWeightArrayData { |
michael@0 | 3355 | FillWeightArrayData(PerWeightData* aArrayData) : |
michael@0 | 3356 | mIndex(0), |
michael@0 | 3357 | mWeightArray(aArrayData) |
michael@0 | 3358 | { |
michael@0 | 3359 | } |
michael@0 | 3360 | int32_t mIndex; |
michael@0 | 3361 | PerWeightData* mWeightArray; |
michael@0 | 3362 | }; |
michael@0 | 3363 | |
michael@0 | 3364 | |
michael@0 | 3365 | static PLDHashOperator |
michael@0 | 3366 | FillWeightArray(PLDHashTable *table, PLDHashEntryHdr *hdr, |
michael@0 | 3367 | uint32_t number, void *arg) |
michael@0 | 3368 | { |
michael@0 | 3369 | FillWeightArrayData* data = static_cast<FillWeightArrayData*>(arg); |
michael@0 | 3370 | const RuleByWeightEntry *entry = (const RuleByWeightEntry *)hdr; |
michael@0 | 3371 | |
michael@0 | 3372 | data->mWeightArray[data->mIndex++] = entry->data; |
michael@0 | 3373 | |
michael@0 | 3374 | return PL_DHASH_NEXT; |
michael@0 | 3375 | } |
michael@0 | 3376 | |
michael@0 | 3377 | RuleCascadeData* |
michael@0 | 3378 | nsCSSRuleProcessor::GetRuleCascade(nsPresContext* aPresContext) |
michael@0 | 3379 | { |
michael@0 | 3380 | // FIXME: Make this infallible! |
michael@0 | 3381 | |
michael@0 | 3382 | // If anything changes about the presentation context, we will be |
michael@0 | 3383 | // notified. Otherwise, our cache is valid if mLastPresContext |
michael@0 | 3384 | // matches aPresContext. (The only rule processors used for multiple |
michael@0 | 3385 | // pres contexts are for XBL. These rule processors are probably less |
michael@0 | 3386 | // likely to have @media rules, and thus the cache is pretty likely to |
michael@0 | 3387 | // hit instantly even when we're switching between pres contexts.) |
michael@0 | 3388 | |
michael@0 | 3389 | if (!mRuleCascades || aPresContext != mLastPresContext) { |
michael@0 | 3390 | RefreshRuleCascade(aPresContext); |
michael@0 | 3391 | } |
michael@0 | 3392 | mLastPresContext = aPresContext; |
michael@0 | 3393 | |
michael@0 | 3394 | return mRuleCascades; |
michael@0 | 3395 | } |
michael@0 | 3396 | |
michael@0 | 3397 | void |
michael@0 | 3398 | nsCSSRuleProcessor::RefreshRuleCascade(nsPresContext* aPresContext) |
michael@0 | 3399 | { |
michael@0 | 3400 | // Having RuleCascadeData objects be per-medium (over all variation |
michael@0 | 3401 | // caused by media queries, handled through mCacheKey) works for now |
michael@0 | 3402 | // since nsCSSRuleProcessor objects are per-document. (For a given |
michael@0 | 3403 | // set of stylesheets they can vary based on medium (@media) or |
michael@0 | 3404 | // document (@-moz-document).) |
michael@0 | 3405 | |
michael@0 | 3406 | for (RuleCascadeData **cascadep = &mRuleCascades, *cascade; |
michael@0 | 3407 | (cascade = *cascadep); cascadep = &cascade->mNext) { |
michael@0 | 3408 | if (cascade->mCacheKey.Matches(aPresContext)) { |
michael@0 | 3409 | // Ensure that the current one is always mRuleCascades. |
michael@0 | 3410 | *cascadep = cascade->mNext; |
michael@0 | 3411 | cascade->mNext = mRuleCascades; |
michael@0 | 3412 | mRuleCascades = cascade; |
michael@0 | 3413 | |
michael@0 | 3414 | return; |
michael@0 | 3415 | } |
michael@0 | 3416 | } |
michael@0 | 3417 | |
michael@0 | 3418 | if (mSheets.Length() != 0) { |
michael@0 | 3419 | nsAutoPtr<RuleCascadeData> newCascade( |
michael@0 | 3420 | new RuleCascadeData(aPresContext->Medium(), |
michael@0 | 3421 | eCompatibility_NavQuirks == aPresContext->CompatibilityMode())); |
michael@0 | 3422 | if (newCascade) { |
michael@0 | 3423 | CascadeEnumData data(aPresContext, newCascade->mFontFaceRules, |
michael@0 | 3424 | newCascade->mKeyframesRules, |
michael@0 | 3425 | newCascade->mFontFeatureValuesRules, |
michael@0 | 3426 | newCascade->mPageRules, |
michael@0 | 3427 | newCascade->mCacheKey, |
michael@0 | 3428 | mSheetType); |
michael@0 | 3429 | if (!data.mRulesByWeight.ops) |
michael@0 | 3430 | return; /* out of memory */ |
michael@0 | 3431 | |
michael@0 | 3432 | for (uint32_t i = 0; i < mSheets.Length(); ++i) { |
michael@0 | 3433 | if (!CascadeSheet(mSheets.ElementAt(i), &data)) |
michael@0 | 3434 | return; /* out of memory */ |
michael@0 | 3435 | } |
michael@0 | 3436 | |
michael@0 | 3437 | // Sort the hash table of per-weight linked lists by weight. |
michael@0 | 3438 | uint32_t weightCount = data.mRulesByWeight.entryCount; |
michael@0 | 3439 | nsAutoArrayPtr<PerWeightData> weightArray(new PerWeightData[weightCount]); |
michael@0 | 3440 | FillWeightArrayData fwData(weightArray); |
michael@0 | 3441 | PL_DHashTableEnumerate(&data.mRulesByWeight, FillWeightArray, &fwData); |
michael@0 | 3442 | NS_QuickSort(weightArray, weightCount, sizeof(PerWeightData), |
michael@0 | 3443 | CompareWeightData, nullptr); |
michael@0 | 3444 | |
michael@0 | 3445 | // Put things into the rule hash. |
michael@0 | 3446 | // The primary sort is by weight... |
michael@0 | 3447 | for (uint32_t i = 0; i < weightCount; ++i) { |
michael@0 | 3448 | // and the secondary sort is by order. mRuleSelectorPairs is already in |
michael@0 | 3449 | // the right order.. |
michael@0 | 3450 | for (PerWeightDataListItem *cur = weightArray[i].mRuleSelectorPairs; |
michael@0 | 3451 | cur; |
michael@0 | 3452 | cur = cur->mNext) { |
michael@0 | 3453 | if (!AddRule(cur, newCascade)) |
michael@0 | 3454 | return; /* out of memory */ |
michael@0 | 3455 | } |
michael@0 | 3456 | } |
michael@0 | 3457 | |
michael@0 | 3458 | // Build mKeyframesRuleTable. |
michael@0 | 3459 | for (nsTArray<nsCSSKeyframesRule*>::size_type i = 0, |
michael@0 | 3460 | iEnd = newCascade->mKeyframesRules.Length(); i < iEnd; ++i) { |
michael@0 | 3461 | nsCSSKeyframesRule* rule = newCascade->mKeyframesRules[i]; |
michael@0 | 3462 | newCascade->mKeyframesRuleTable.Put(rule->GetName(), rule); |
michael@0 | 3463 | } |
michael@0 | 3464 | |
michael@0 | 3465 | // Ensure that the current one is always mRuleCascades. |
michael@0 | 3466 | newCascade->mNext = mRuleCascades; |
michael@0 | 3467 | mRuleCascades = newCascade.forget(); |
michael@0 | 3468 | } |
michael@0 | 3469 | } |
michael@0 | 3470 | return; |
michael@0 | 3471 | } |
michael@0 | 3472 | |
michael@0 | 3473 | /* static */ bool |
michael@0 | 3474 | nsCSSRuleProcessor::SelectorListMatches(Element* aElement, |
michael@0 | 3475 | TreeMatchContext& aTreeMatchContext, |
michael@0 | 3476 | nsCSSSelectorList* aSelectorList) |
michael@0 | 3477 | { |
michael@0 | 3478 | MOZ_ASSERT(!aTreeMatchContext.mForScopedStyle, |
michael@0 | 3479 | "mCurrentStyleScope will need to be saved and restored after the " |
michael@0 | 3480 | "SelectorMatchesTree call"); |
michael@0 | 3481 | |
michael@0 | 3482 | while (aSelectorList) { |
michael@0 | 3483 | nsCSSSelector* sel = aSelectorList->mSelectors; |
michael@0 | 3484 | NS_ASSERTION(sel, "Should have *some* selectors"); |
michael@0 | 3485 | NS_ASSERTION(!sel->IsPseudoElement(), "Shouldn't have been called"); |
michael@0 | 3486 | NodeMatchContext nodeContext(EventStates(), false); |
michael@0 | 3487 | if (SelectorMatches(aElement, sel, nodeContext, aTreeMatchContext)) { |
michael@0 | 3488 | nsCSSSelector* next = sel->mNext; |
michael@0 | 3489 | if (!next || |
michael@0 | 3490 | SelectorMatchesTree(aElement, next, aTreeMatchContext, false)) { |
michael@0 | 3491 | return true; |
michael@0 | 3492 | } |
michael@0 | 3493 | } |
michael@0 | 3494 | |
michael@0 | 3495 | aSelectorList = aSelectorList->mNext; |
michael@0 | 3496 | } |
michael@0 | 3497 | |
michael@0 | 3498 | return false; |
michael@0 | 3499 | } |
michael@0 | 3500 | |
michael@0 | 3501 | // TreeMatchContext and AncestorFilter out of line methods |
michael@0 | 3502 | void |
michael@0 | 3503 | TreeMatchContext::InitAncestors(Element *aElement) |
michael@0 | 3504 | { |
michael@0 | 3505 | MOZ_ASSERT(!mAncestorFilter.mFilter); |
michael@0 | 3506 | MOZ_ASSERT(mAncestorFilter.mHashes.IsEmpty()); |
michael@0 | 3507 | MOZ_ASSERT(mStyleScopes.IsEmpty()); |
michael@0 | 3508 | |
michael@0 | 3509 | mAncestorFilter.mFilter = new AncestorFilter::Filter(); |
michael@0 | 3510 | |
michael@0 | 3511 | if (MOZ_LIKELY(aElement)) { |
michael@0 | 3512 | MOZ_ASSERT(aElement->IsInDoc(), |
michael@0 | 3513 | "aElement must be in the document for the assumption that " |
michael@0 | 3514 | "GetParentNode() is non-null on all element ancestors of " |
michael@0 | 3515 | "aElement to be true"); |
michael@0 | 3516 | // Collect up the ancestors |
michael@0 | 3517 | nsAutoTArray<Element*, 50> ancestors; |
michael@0 | 3518 | Element* cur = aElement; |
michael@0 | 3519 | do { |
michael@0 | 3520 | ancestors.AppendElement(cur); |
michael@0 | 3521 | nsINode* parent = cur->GetParentNode(); |
michael@0 | 3522 | if (!parent->IsElement()) { |
michael@0 | 3523 | break; |
michael@0 | 3524 | } |
michael@0 | 3525 | |
michael@0 | 3526 | cur = parent->AsElement(); |
michael@0 | 3527 | } while (true); |
michael@0 | 3528 | |
michael@0 | 3529 | // Now push them in reverse order. |
michael@0 | 3530 | for (uint32_t i = ancestors.Length(); i-- != 0; ) { |
michael@0 | 3531 | mAncestorFilter.PushAncestor(ancestors[i]); |
michael@0 | 3532 | PushStyleScope(ancestors[i]); |
michael@0 | 3533 | } |
michael@0 | 3534 | } |
michael@0 | 3535 | } |
michael@0 | 3536 | |
michael@0 | 3537 | void |
michael@0 | 3538 | TreeMatchContext::InitStyleScopes(Element* aElement) |
michael@0 | 3539 | { |
michael@0 | 3540 | MOZ_ASSERT(mStyleScopes.IsEmpty()); |
michael@0 | 3541 | |
michael@0 | 3542 | if (MOZ_LIKELY(aElement)) { |
michael@0 | 3543 | // Collect up the ancestors |
michael@0 | 3544 | nsAutoTArray<Element*, 50> ancestors; |
michael@0 | 3545 | Element* cur = aElement; |
michael@0 | 3546 | do { |
michael@0 | 3547 | ancestors.AppendElement(cur); |
michael@0 | 3548 | nsINode* parent = cur->GetParentNode(); |
michael@0 | 3549 | if (!parent || !parent->IsElement()) { |
michael@0 | 3550 | break; |
michael@0 | 3551 | } |
michael@0 | 3552 | |
michael@0 | 3553 | cur = parent->AsElement(); |
michael@0 | 3554 | } while (true); |
michael@0 | 3555 | |
michael@0 | 3556 | // Now push them in reverse order. |
michael@0 | 3557 | for (uint32_t i = ancestors.Length(); i-- != 0; ) { |
michael@0 | 3558 | PushStyleScope(ancestors[i]); |
michael@0 | 3559 | } |
michael@0 | 3560 | } |
michael@0 | 3561 | } |
michael@0 | 3562 | |
michael@0 | 3563 | void |
michael@0 | 3564 | AncestorFilter::PushAncestor(Element *aElement) |
michael@0 | 3565 | { |
michael@0 | 3566 | MOZ_ASSERT(mFilter); |
michael@0 | 3567 | |
michael@0 | 3568 | uint32_t oldLength = mHashes.Length(); |
michael@0 | 3569 | |
michael@0 | 3570 | mPopTargets.AppendElement(oldLength); |
michael@0 | 3571 | #ifdef DEBUG |
michael@0 | 3572 | mElements.AppendElement(aElement); |
michael@0 | 3573 | #endif |
michael@0 | 3574 | mHashes.AppendElement(aElement->Tag()->hash()); |
michael@0 | 3575 | nsIAtom *id = aElement->GetID(); |
michael@0 | 3576 | if (id) { |
michael@0 | 3577 | mHashes.AppendElement(id->hash()); |
michael@0 | 3578 | } |
michael@0 | 3579 | const nsAttrValue *classes = aElement->GetClasses(); |
michael@0 | 3580 | if (classes) { |
michael@0 | 3581 | uint32_t classCount = classes->GetAtomCount(); |
michael@0 | 3582 | for (uint32_t i = 0; i < classCount; ++i) { |
michael@0 | 3583 | mHashes.AppendElement(classes->AtomAt(i)->hash()); |
michael@0 | 3584 | } |
michael@0 | 3585 | } |
michael@0 | 3586 | |
michael@0 | 3587 | uint32_t newLength = mHashes.Length(); |
michael@0 | 3588 | for (uint32_t i = oldLength; i < newLength; ++i) { |
michael@0 | 3589 | mFilter->add(mHashes[i]); |
michael@0 | 3590 | } |
michael@0 | 3591 | } |
michael@0 | 3592 | |
michael@0 | 3593 | void |
michael@0 | 3594 | AncestorFilter::PopAncestor() |
michael@0 | 3595 | { |
michael@0 | 3596 | MOZ_ASSERT(!mPopTargets.IsEmpty()); |
michael@0 | 3597 | MOZ_ASSERT(mPopTargets.Length() == mElements.Length()); |
michael@0 | 3598 | |
michael@0 | 3599 | uint32_t popTargetLength = mPopTargets.Length(); |
michael@0 | 3600 | uint32_t newLength = mPopTargets[popTargetLength-1]; |
michael@0 | 3601 | |
michael@0 | 3602 | mPopTargets.TruncateLength(popTargetLength-1); |
michael@0 | 3603 | #ifdef DEBUG |
michael@0 | 3604 | mElements.TruncateLength(popTargetLength-1); |
michael@0 | 3605 | #endif |
michael@0 | 3606 | |
michael@0 | 3607 | uint32_t oldLength = mHashes.Length(); |
michael@0 | 3608 | for (uint32_t i = newLength; i < oldLength; ++i) { |
michael@0 | 3609 | mFilter->remove(mHashes[i]); |
michael@0 | 3610 | } |
michael@0 | 3611 | mHashes.TruncateLength(newLength); |
michael@0 | 3612 | } |
michael@0 | 3613 | |
michael@0 | 3614 | #ifdef DEBUG |
michael@0 | 3615 | void |
michael@0 | 3616 | AncestorFilter::AssertHasAllAncestors(Element *aElement) const |
michael@0 | 3617 | { |
michael@0 | 3618 | nsINode* cur = aElement->GetParentNode(); |
michael@0 | 3619 | while (cur && cur->IsElement()) { |
michael@0 | 3620 | MOZ_ASSERT(mElements.Contains(cur)); |
michael@0 | 3621 | cur = cur->GetParentNode(); |
michael@0 | 3622 | } |
michael@0 | 3623 | } |
michael@0 | 3624 | #endif |