| /* |
| * Copyright (C) 1999 Lars Knoll (knoll@kde.org) |
| * Copyright (C) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011 Apple Inc. |
| * All rights reserved. |
| * |
| * This library is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU Library General Public |
| * License as published by the Free Software Foundation; either |
| * version 2 of the License, or (at your option) any later version. |
| * |
| * This library is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| * Library General Public License for more details. |
| * |
| * You should have received a copy of the GNU Library General Public License |
| * along with this library; see the file COPYING.LIB. If not, write to |
| * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, |
| * Boston, MA 02110-1301, USA. |
| * |
| */ |
| |
| #ifndef THIRD_PARTY_BLINK_RENDERER_CORE_CSS_RULE_SET_H_ |
| #define THIRD_PARTY_BLINK_RENDERER_CORE_CSS_RULE_SET_H_ |
| |
| #include "base/gtest_prod_util.h" |
| #include "base/substring_set_matcher/substring_set_matcher.h" |
| #include "base/types/pass_key.h" |
| #include "third_party/blink/renderer/core/core_export.h" |
| #include "third_party/blink/renderer/core/css/cascade_layer.h" |
| #include "third_party/blink/renderer/core/css/css_keyframes_rule.h" |
| #include "third_party/blink/renderer/core/css/css_position_try_rule.h" |
| #include "third_party/blink/renderer/core/css/media_query_evaluator.h" |
| #include "third_party/blink/renderer/core/css/resolver/media_query_result.h" |
| #include "third_party/blink/renderer/core/css/robin_hood_map.h" |
| #include "third_party/blink/renderer/core/css/rule_feature_set.h" |
| #include "third_party/blink/renderer/core/css/style_rule.h" |
| #include "third_party/blink/renderer/core/css/style_rule_counter_style.h" |
| #include "third_party/blink/renderer/core/css/style_rule_font_feature_values.h" |
| #include "third_party/blink/renderer/core/css/style_rule_font_palette_values.h" |
| #include "third_party/blink/renderer/core/css/style_rule_view_transition.h" |
| #include "third_party/blink/renderer/platform/heap/collection_support/heap_hash_map.h" |
| #include "third_party/blink/renderer/platform/heap/collection_support/heap_hash_set.h" |
| #include "third_party/blink/renderer/platform/heap/collection_support/heap_linked_stack.h" |
| #include "third_party/blink/renderer/platform/wtf/casting.h" |
| #include "third_party/blink/renderer/platform/wtf/forward.h" |
| #include "third_party/blink/renderer/platform/wtf/size_assertions.h" |
| |
| namespace blink { |
| |
| class RuleSet; |
| |
| using AddRuleFlags = unsigned; |
| |
| enum AddRuleFlag { |
| kRuleHasNoSpecialState = 0, |
| kRuleIsVisitedDependent = 1 << 0, |
| kRuleIsStartingStyle = 1 << 1, |
| }; |
| |
| // Some CSS properties do not apply to certain pseudo-elements, and need to be |
| // ignored when resolving styles. Be aware that these values are used in a |
| // bitfield. Make sure that it's large enough to hold new values. |
| // See MatchedProperties::Data::valid_property_filter. |
| enum class ValidPropertyFilter : unsigned { |
| // All properties are valid. This is the common case. |
| kNoFilter, |
| // Defined in a ::cue pseudo-element scope. Only properties listed |
| // in https://w3c.github.io/webvtt/#the-cue-pseudo-element are valid. |
| kCue, |
| // Defined in a ::first-letter pseudo-element scope. Only properties listed in |
| // https://drafts.csswg.org/css-pseudo-4/#first-letter-styling are valid. |
| kFirstLetter, |
| // Defined in a ::first-line pseudo-element scope. Only properties listed in |
| // https://drafts.csswg.org/css-pseudo-4/#first-line-styling are valid. |
| kFirstLine, |
| // Defined in a ::marker pseudo-element scope. Only properties listed in |
| // https://drafts.csswg.org/css-pseudo-4/#marker-pseudo are valid. |
| kMarker, |
| // Defined in a highlight pseudo-element scope like ::selection and |
| // ::target-text. Theoretically only properties listed in |
| // https://drafts.csswg.org/css-pseudo-4/#highlight-styling should be valid, |
| // but for highlight pseudos using originating inheritance instead of |
| // highlight inheritance we allow a different set of rules for |
| // compatibility reasons. |
| kHighlightLegacy, |
| // Defined in a highlight pseudo-element scope like ::selection and |
| // ::target-text. Only properties listed in |
| // https://drafts.csswg.org/css-pseudo-4/#highlight-styling are valid. |
| kHighlight, |
| // Defined in a @position-try rule. Only properties listed in |
| // https://drafts.csswg.org/css-anchor-position-1/#fallback-rule are valid. |
| kPositionTry, |
| // Defined in an @page rule. Will only allow properties and descriptors that |
| // have an effect with PageMarginBoxes disabled (i.e. page size, margins and |
| // orientation). |
| kLimitedPageContext, |
| // Defined in an @page rule. |
| // See https://drafts.csswg.org/css-page-3/#page-property-list |
| kPageContext, |
| }; |
| |
| class CSSSelector; |
| class MediaQueryEvaluator; |
| class StyleSheetContents; |
| |
| // This is a wrapper around a StyleRule, pointing to one of the N complex |
| // selectors in the StyleRule. This allows us to treat each selector |
| // independently but still tie them back to the original StyleRule. If multiple |
| // selectors from a single rule match the same element we can see that as one |
| // match for the rule. It computes some information about the wrapped selector |
| // and makes it accessible cheaply. |
| class CORE_EXPORT RuleData { |
| DISALLOW_NEW(); |
| |
| public: |
| // NOTE: If you move the RuleData to a different RuleSet |
| // (and thus a different bloom_hash_backing from what you |
| // give to the constructor), you will need to call |
| // MovedToDifferentRuleSet() below. Otherwise, |
| // DescendantSelectorIdentifierHashes() will return a slice |
| // into a nonexistent backing (and GetPosition() will return |
| // a bogus value, which cannot be used for Seeker lookups). |
| RuleData(StyleRule*, |
| unsigned selector_index, |
| unsigned position, |
| const StyleScope*, |
| AddRuleFlags, |
| Vector<unsigned>& bloom_hash_backing); |
| |
| unsigned GetPosition() const { return position_; } |
| StyleRule* Rule() const { return rule_.Get(); } |
| const CSSSelector& Selector() const { |
| return rule_->SelectorAt(selector_index_); |
| } |
| CSSSelector& MutableSelector() { |
| return rule_->MutableSelectorAt(selector_index_); |
| } |
| unsigned SelectorIndex() const { return selector_index_; } |
| bool IsEntirelyCoveredByBucketing() const { |
| return is_entirely_covered_by_bucketing_; |
| } |
| void ComputeEntirelyCoveredByBucketing(); |
| void ResetEntirelyCoveredByBucketing(); |
| bool SelectorIsEasy() const { return is_easy_; } |
| bool IsStartingStyle() const { return is_starting_style_; } |
| |
| bool ContainsUncommonAttributeSelector() const { |
| return contains_uncommon_attribute_selector_; |
| } |
| unsigned Specificity() const { return specificity_; } |
| unsigned LinkMatchType() const { return link_match_type_; } |
| ValidPropertyFilter GetValidPropertyFilter( |
| bool is_matching_ua_rules = false) const { |
| return is_matching_ua_rules |
| ? ValidPropertyFilter::kNoFilter |
| : static_cast<ValidPropertyFilter>(valid_property_filter_); |
| } |
| |
| // Member functions related to the descendant Bloom filter. |
| const base::span<const unsigned> DescendantSelectorIdentifierHashes( |
| const Vector<unsigned>& backing) const { |
| return {backing.data() + bloom_hash_pos_, bloom_hash_size_}; |
| } |
| void ComputeBloomFilterHashes(const StyleScope* style_scope, |
| Vector<unsigned>& backing); |
| void MovedToDifferentRuleSet(const Vector<unsigned>& old_backing, |
| Vector<unsigned>& new_backing, |
| unsigned new_position); |
| |
| void Trace(Visitor*) const; |
| |
| // This number is picked fairly arbitrary. If lowered, be aware that there |
| // might be sites and extensions using style rules with selector lists |
| // exceeding the number of simple selectors to fit in this bitfield. |
| // See https://crbug.com/312913 and https://crbug.com/704562 |
| static constexpr size_t kSelectorIndexBits = 13; |
| |
| // This number was picked fairly arbitrarily. We can probably lower it if we |
| // need to. Some simple testing showed <100,000 RuleData's on large sites. |
| static constexpr size_t kPositionBits = 18; |
| |
| private: |
| Member<StyleRule> rule_; |
| unsigned selector_index_ : kSelectorIndexBits; |
| unsigned position_ : kPositionBits; |
| unsigned contains_uncommon_attribute_selector_ : 1; |
| // 32 bits above |
| unsigned specificity_ : 24; |
| unsigned link_match_type_ : 2; |
| unsigned valid_property_filter_ : 3; |
| unsigned is_entirely_covered_by_bucketing_ : 1; |
| unsigned is_easy_ : 1; // See EasySelectorChecker. |
| unsigned is_starting_style_ : 1; // Inside @starting-style {}. |
| // 32 bits above |
| |
| // Reference into a slice of bloom_hash_backing_ in the parent RuleSet. |
| // We can probably steal a couple of bits here if needed, but if you do, |
| // remember to adjust the clamping in ComputeBloomFilterHashes() too. |
| unsigned bloom_hash_size_ : 8; |
| unsigned bloom_hash_pos_ : 24; |
| }; |
| |
| } // namespace blink |
| |
| WTF_ALLOW_MOVE_AND_INIT_WITH_MEM_FUNCTIONS(blink::RuleData) |
| |
| namespace blink { |
| |
| struct SameSizeAsRuleData { |
| DISALLOW_NEW(); |
| Member<void*> a; |
| unsigned b; |
| unsigned c; |
| unsigned d; |
| }; |
| |
| ASSERT_SIZE(RuleData, SameSizeAsRuleData); |
| |
| // A memory-efficient and (fairly) cache-efficient mapping from bucket key |
| // (e.g. CSS class, tag name, attribute key, etc.) to a collection of rules |
| // (RuleData objects). It uses a vector as backing storage, and generally works |
| // in two phases: |
| // |
| // - During RuleSet setup (before compaction), we simply add rules to the |
| // back of the vector, ie., the elements will be in a random order. |
| // - Before rule matching, we need to _compact_ the rule map. This is done |
| // by grouping/sorting the vector by bucket, so that everything that belongs |
| // to the same vector lives together and can easily be picked out. |
| // |
| // The normal flow is that you first add all rules, call Compact(), then call |
| // Find() as many times as you need. (Compact() is a moderately expensive |
| // operation, which is why we don't want to be doing it too much.) However, in |
| // certain cases related to UA stylesheets, we may need to insert new rules |
| // on-the-fly (e.g., when seeing a <video> element for the first time, we |
| // insert additional rules related to it); if so, you need to call Uncompact() |
| // before adding them, then Compact() again. |
| class RuleMap { |
| DISALLOW_NEW(); |
| |
| private: |
| // A collection of rules that are in the same bucket. Before compaction, |
| // they are scattered around in the bucket vector; after compaction, |
| // each bucket will be contiguous. |
| struct Extent { |
| Extent() : bucket_number(0) {} |
| union { |
| // [0..num_buckets). Valid before compaction. |
| unsigned bucket_number; |
| |
| // Into the backing vector. Valid after compaction. |
| unsigned start_index; |
| }; |
| |
| // How many rules are in this bucket. Will naturally not change |
| // by compaction. |
| wtf_size_t length = 0; |
| }; |
| |
| public: |
| // Returns false on failure (which should be very rare). |
| bool Add(const AtomicString& key, const RuleData& rule_data); |
| void AddFilteredRulesFromOtherSet( |
| const RuleMap& other, |
| const HeapHashSet<Member<StyleRule>>& only_include, |
| const RuleSet& old_rule_set, |
| RuleSet& new_rule_set); |
| base::span<const RuleData> Find(const AtomicString& key) const { |
| if (buckets.IsNull()) { |
| return {}; |
| } |
| |
| // Go through all the buckets and check for equality, brute force. |
| // Note that we don't check for IsNull() to get an early abort |
| // on empty buckets; the comparison of AtomicString is so cheap |
| // that it actually benchmarks negatively. This loop typically |
| // gets unrolled and inlined, resulting in a very tight lookup. |
| const RobinHoodMap<AtomicString, Extent>::Bucket* bucket = |
| buckets.Find(key); |
| if (bucket == nullptr) { |
| return {}; |
| } else { |
| return {backing.begin() + bucket->value.start_index, |
| bucket->value.length}; |
| } |
| } |
| bool IsEmpty() const { return backing.empty(); } |
| bool IsCompacted() const { return compacted; } |
| |
| void Compact(); |
| void Uncompact(); |
| |
| void Trace(Visitor* visitor) const { visitor->Trace(backing); } |
| |
| struct ConstIterator { |
| RobinHoodMap<AtomicString, Extent>::const_iterator sub_it; |
| const RuleMap* rule_map; |
| |
| WTF::KeyValuePair<AtomicString, base::span<const RuleData>> operator*() |
| const { |
| return {sub_it->key, rule_map->GetRulesFromExtent(sub_it->value)}; |
| } |
| bool operator==(const ConstIterator& other) const { |
| DCHECK_EQ(rule_map, other.rule_map); |
| return sub_it == other.sub_it; |
| } |
| bool operator!=(const ConstIterator& other) const { |
| DCHECK_EQ(rule_map, other.rule_map); |
| return sub_it != other.sub_it; |
| } |
| ConstIterator& operator++() { |
| ++sub_it; |
| return *this; |
| } |
| }; |
| ConstIterator begin() const { return {buckets.begin(), this}; } |
| ConstIterator end() const { return {buckets.end(), this}; } |
| |
| private: |
| base::span<RuleData> GetRulesFromExtent(Extent extent) { |
| return {backing.begin() + extent.start_index, extent.length}; |
| } |
| base::span<const RuleData> GetRulesFromExtent(Extent extent) const { |
| return {backing.begin() + extent.start_index, extent.length}; |
| } |
| base::span<unsigned> GetBucketNumberFromExtent(Extent extent) { |
| return {bucket_number_.begin() + extent.start_index, extent.length}; |
| } |
| base::span<const unsigned> GetBucketNumberFromExtent(Extent extent) const { |
| return {bucket_number_.begin() + extent.start_index, extent.length}; |
| } |
| |
| RobinHoodMap<AtomicString, Extent> buckets; |
| |
| // Contains all the rules from all the buckets; after compaction, |
| // they will be contiguous in memory and you can do easily lookups |
| // on them through Find(); before, they are identified |
| // by having the group number in bucket_data_. |
| // |
| // The inline size is, perhaps surprisingly, to reduce GC pressure |
| // for _large_ vectors. Setting an inline size (other than zero) |
| // causes Vector, and by extension HeapVector, to grow more |
| // aggressively on push_back(), leading to fewer memory allocations |
| // that need freeing. We call ShrinkToFit() on compaction, so the |
| // excess increase (which would normally be the downside of this |
| // strategy) is not a big problem for us. |
| // |
| // Of course, we also save a few allocations for the rule sets |
| // that are tiny. Most RuleMaps are either ~1–2 entries or in |
| // the hundreds/thousands. |
| HeapVector<RuleData, 4> backing; |
| |
| // Used by RuleMap before compaction, to hold what bucket the corresponding |
| // RuleData (by index) is to be sorted into (this member is 1:1 with backing). |
| // After compaction, the vector is emptied to save memory. |
| Vector<unsigned> bucket_number_; |
| |
| wtf_size_t num_buckets = 0; |
| bool compacted = false; |
| }; |
| |
| // Holds RuleData objects. It partitions them into various indexed groups, |
| // e.g. it stores separately rules that match against id, class, tag, shadow |
| // host, etc. It indexes these by some key where possible, e.g. rules that match |
| // against tag name are indexed by that tag. Rules that don't fall into a |
| // specific group are appended to the "universal" rules. The grouping is done to |
| // optimize finding what rules apply to an element under consideration by |
| // ElementRuleCollector::CollectMatchingRules. |
| class CORE_EXPORT RuleSet final : public GarbageCollected<RuleSet> { |
| public: |
| RuleSet() = default; |
| RuleSet(const RuleSet&) = delete; |
| RuleSet& operator=(const RuleSet&) = delete; |
| |
| void AddRulesFromSheet(StyleSheetContents*, |
| const MediaQueryEvaluator&, |
| CascadeLayer* = nullptr); |
| void AddStyleRule(StyleRule* style_rule, |
| const MediaQueryEvaluator& medium, |
| AddRuleFlags add_rule_flags, |
| const ContainerQuery* container_query = nullptr, |
| CascadeLayer* cascade_layer = nullptr, |
| const StyleScope* style_scope = nullptr); |
| |
| // Adds RuleDatas (and only RuleDatas) from the other set, but only if they |
| // correspond to rules in “only_include”. This is used when creating diff |
| // rulesets for invalidation, and the resulting RuleSets are not usable |
| // for anything else. In particular, cascade layers are not copied |
| // and RuleData offsets are not adjusted (so CascadePriority would be wrong |
| // if merging RuleDatas from different RuleSets). This means that the only |
| // thing you can really do with this RuleSet afterwards is |
| // ElementRuleCollector's CheckIfAnyRuleMatches(); the regular |
| // Collect*Rules() functions are bound to give you trouble. |
| void AddFilteredRulesFromOtherSet( |
| const RuleSet& other, |
| const HeapHashSet<Member<StyleRule>>& only_include); |
| |
| void AddFilteredRulesFromOtherBucket( |
| const RuleSet& other, |
| const HeapVector<RuleData>& src, |
| const HeapHashSet<Member<StyleRule>>& only_include, |
| HeapVector<RuleData>* dst); |
| |
| const RuleFeatureSet& Features() const { return features_; } |
| |
| base::span<const RuleData> IdRules(const AtomicString& key) const { |
| return id_rules_.Find(key); |
| } |
| base::span<const RuleData> ClassRules(const AtomicString& key) const { |
| return class_rules_.Find(key); |
| } |
| bool HasAnyAttrRules() const { return !attr_rules_.IsEmpty(); } |
| base::span<const RuleData> AttrRules(const AtomicString& key) const { |
| return attr_rules_.Find(key); |
| } |
| bool CanIgnoreEntireList(base::span<const RuleData> list, |
| const AtomicString& key, |
| const AtomicString& value) const; |
| base::span<const RuleData> TagRules(const AtomicString& key) const { |
| return tag_rules_.Find(key); |
| } |
| base::span<const RuleData> UAShadowPseudoElementRules( |
| const AtomicString& key) const { |
| return ua_shadow_pseudo_element_rules_.Find(key); |
| } |
| base::span<const RuleData> LinkPseudoClassRules() const { |
| return link_pseudo_class_rules_; |
| } |
| base::span<const RuleData> CuePseudoRules() const { |
| return cue_pseudo_rules_; |
| } |
| base::span<const RuleData> FocusPseudoClassRules() const { |
| return focus_pseudo_class_rules_; |
| } |
| base::span<const RuleData> FocusVisiblePseudoClassRules() const { |
| return focus_visible_pseudo_class_rules_; |
| } |
| base::span<const RuleData> RootElementRules() const { |
| return root_element_rules_; |
| } |
| base::span<const RuleData> UniversalRules() const { return universal_rules_; } |
| base::span<const RuleData> ShadowHostRules() const { |
| return shadow_host_rules_; |
| } |
| base::span<const RuleData> PartPseudoRules() const { |
| return part_pseudo_rules_; |
| } |
| base::span<const RuleData> SelectorFragmentAnchorRules() const { |
| return selector_fragment_anchor_rules_; |
| } |
| const HeapVector<Member<StyleRulePage>>& PageRules() const { |
| return page_rules_; |
| } |
| const HeapVector<Member<StyleRuleFontFace>>& FontFaceRules() const { |
| return font_face_rules_; |
| } |
| const HeapVector<Member<StyleRuleKeyframes>>& KeyframesRules() const { |
| return keyframes_rules_; |
| } |
| const HeapVector<Member<StyleRuleProperty>>& PropertyRules() const { |
| return property_rules_; |
| } |
| const HeapVector<Member<StyleRuleCounterStyle>>& CounterStyleRules() const { |
| return counter_style_rules_; |
| } |
| const HeapVector<Member<StyleRuleFontPaletteValues>>& FontPaletteValuesRules() |
| const { |
| return font_palette_values_rules_; |
| } |
| const HeapVector<Member<StyleRuleFontFeatureValues>>& FontFeatureValuesRules() |
| const { |
| return font_feature_values_rules_; |
| } |
| const HeapVector<Member<StyleRuleViewTransition>>& ViewTransitionRules() |
| const { |
| return view_transition_rules_; |
| } |
| const HeapVector<Member<StyleRulePositionTry>>& PositionTryRules() const { |
| return position_try_rules_; |
| } |
| const HeapVector<Member<StyleRuleFunction>>& FunctionRules() const { |
| return function_rules_; |
| } |
| base::span<const RuleData> SlottedPseudoElementRules() const { |
| return slotted_pseudo_element_rules_; |
| } |
| |
| bool HasCascadeLayers() const { return implicit_outer_layer_ != nullptr; } |
| const CascadeLayer& CascadeLayers() const { |
| DCHECK(implicit_outer_layer_); |
| return *implicit_outer_layer_; |
| } |
| |
| unsigned RuleCount() const { return rule_count_; } |
| |
| void CompactRulesIfNeeded() { |
| if (need_compaction_) { |
| CompactRules(); |
| } |
| } |
| |
| bool HasSlottedRules() const { |
| return !slotted_pseudo_element_rules_.empty(); |
| } |
| |
| bool HasPartPseudoRules() const { return !part_pseudo_rules_.empty(); } |
| |
| bool HasBucketForStyleAttribute() const { return has_bucket_for_style_attr_; } |
| |
| bool MustCheckUniversalBucketForShadowHost() const { |
| return must_check_universal_bucket_for_shadow_host_; |
| } |
| |
| bool HasUAShadowPseudoElementRules() const { |
| return !ua_shadow_pseudo_element_rules_.IsEmpty(); |
| } |
| |
| // If a single @scope rule covers all rules in this RuleSet, |
| // returns the corresponding StyleScope rule, or returns nullptr otherwise. |
| // |
| // This is useful for rejecting entire RuleSets early when implicit @scopes |
| // aren't in scope. |
| // |
| // See ElementRuleCollector::CanRejectScope. |
| const StyleScope* SingleScope() const { |
| if (scope_intervals_.size() == 1u) { |
| const Interval<StyleScope>& interval = scope_intervals_.front(); |
| if (interval.start_position == 0) { |
| return interval.value.Get(); |
| } |
| } |
| return nullptr; |
| } |
| |
| bool DidMediaQueryResultsChange(const MediaQueryEvaluator& evaluator) const; |
| |
| // We use a vector of Interval<T> to represent that rules with positions |
| // between start_position (inclusive) and the next Interval<T>'s |
| // start_position (exclusive) share some property: |
| // |
| // - If T = CascadeLayer, belong to the given layer. |
| // - If T = ContainerQuery, are predicated on the given container query. |
| // - If T = StyleScope, are declared in the given @style scope. |
| // |
| // We do this instead of putting the data directly onto the RuleData, |
| // because most rules don't need these fields and websites can have a large |
| // number of RuleData objects (30k+). Since neighboring rules tend to have the |
| // same values for these (often nullptr), we save memory and cache space at |
| // the cost of a some extra seeking through these lists when matching rules. |
| template <class T> |
| class Interval { |
| DISALLOW_NEW(); |
| |
| public: |
| Interval(const T* passed_value, unsigned passed_position) |
| : value(passed_value), start_position(passed_position) {} |
| const Member<const T> value; |
| const unsigned start_position = 0; |
| |
| void Trace(Visitor*) const; |
| }; |
| |
| const HeapVector<Interval<CascadeLayer>>& LayerIntervals() const { |
| return layer_intervals_; |
| } |
| const HeapVector<Interval<ContainerQuery>>& ContainerQueryIntervals() const { |
| return container_query_intervals_; |
| } |
| const HeapVector<Interval<StyleScope>>& ScopeIntervals() const { |
| return scope_intervals_; |
| } |
| const Vector<unsigned>& BloomHashBacking() const { |
| return bloom_hash_backing_; |
| } |
| |
| #if DCHECK_IS_ON() |
| void Show() const; |
| const HeapVector<RuleData>& AllRulesForTest() const { return all_rules_; } |
| #endif // DCHECK_IS_ON() |
| |
| void Trace(Visitor*) const; |
| |
| private: |
| FRIEND_TEST_ALL_PREFIXES(RuleSetTest, RuleCountNotIncreasedByInvalidRuleData); |
| FRIEND_TEST_ALL_PREFIXES(RuleSetTest, RuleDataPositionLimit); |
| friend class RuleSetCascadeLayerTest; |
| friend class RuleMap; // For scope_intervals_ and |
| // NewlyAddedFromDifferentRuleSet(). |
| |
| using SubstringMatcherMap = |
| HashMap<AtomicString, std::unique_ptr<base::SubstringSetMatcher>>; |
| |
| void AddToRuleSet(const AtomicString& key, RuleMap&, const RuleData&); |
| void AddToRuleSet(HeapVector<RuleData>&, const RuleData&); |
| void AddPageRule(StyleRulePage*); |
| void AddFontFaceRule(StyleRuleFontFace*); |
| void AddKeyframesRule(StyleRuleKeyframes*); |
| void AddPropertyRule(StyleRuleProperty*); |
| void AddCounterStyleRule(StyleRuleCounterStyle*); |
| void AddFontPaletteValuesRule(StyleRuleFontPaletteValues*); |
| void AddFontFeatureValuesRule(StyleRuleFontFeatureValues*); |
| void AddPositionTryRule(StyleRulePositionTry*); |
| void AddFunctionRule(StyleRuleFunction*); |
| void AddViewTransitionRule(StyleRuleViewTransition*); |
| |
| bool MatchMediaForAddRules(const MediaQueryEvaluator& evaluator, |
| const MediaQuerySet* media_queries); |
| void AddChildRules(const HeapVector<Member<StyleRuleBase>>&, |
| const MediaQueryEvaluator& medium, |
| AddRuleFlags, |
| const ContainerQuery*, |
| CascadeLayer*, |
| const StyleScope*); |
| |
| // Determines whether or not CSSSelector::is_covered_by_bucketing_ should |
| // be computed during calls to FindBestRuleSetAndAdd. |
| enum class BucketCoverage { |
| kIgnore, |
| kCompute, |
| }; |
| |
| template <BucketCoverage bucket_coverage> |
| void FindBestRuleSetAndAdd(CSSSelector&, const RuleData&); |
| |
| void AddRule(StyleRule*, |
| unsigned selector_index, |
| AddRuleFlags, |
| const ContainerQuery*, |
| const CascadeLayer*, |
| const StyleScope*); |
| |
| // Must be called when a RuleData has been added to this RuleSet |
| // through some form that does not go through AddRule(); |
| // used during creation of diff rulesets (AddFilteredRulesFromOtherSet()). |
| // In particular, it will adjust the position of new_rule_data, |
| // add it to the necessary intervals for diff rulesets, and adjust |
| // rule_count_. |
| // |
| // Used only by RuleSet itself, and RuleMap (through a friend declaration). |
| void NewlyAddedFromDifferentRuleSet(const RuleData& old_rule_data, |
| const StyleScope* style_scope, |
| const RuleSet& old_rule_set, |
| RuleData& new_rule_data); |
| |
| void SortKeyframesRulesIfNeeded(); |
| |
| void CompactRules(); |
| static void CreateSubstringMatchers( |
| RuleMap& attr_map, |
| SubstringMatcherMap& substring_matcher_map); |
| |
| #if DCHECK_IS_ON() |
| void AssertRuleListsSorted() const; |
| #endif |
| |
| CascadeLayer* EnsureImplicitOuterLayer() { |
| if (!implicit_outer_layer_) { |
| implicit_outer_layer_ = MakeGarbageCollected<CascadeLayer>(); |
| } |
| return implicit_outer_layer_.Get(); |
| } |
| |
| CascadeLayer* GetOrAddSubLayer(CascadeLayer*, |
| const StyleRuleBase::LayerName&); |
| void AddRuleToLayerIntervals(const CascadeLayer*, unsigned position); |
| |
| // May return nullptr for the implicit outer layer. |
| const CascadeLayer* GetLayerForTest(const RuleData&) const; |
| |
| RuleMap id_rules_; |
| RuleMap class_rules_; |
| RuleMap attr_rules_; |
| // A structure for quickly rejecting an entire attribute rule set |
| // (from attr_rules_). If we have many rules in the same bucket, |
| // we build up a case-insensitive substring-matching structure of all |
| // the values we can match on (all attribute selectors are either substring, |
| // or something stricter than substring). We can then use that structure |
| // to see in linear time (of the length of the attribute value in the DOM) |
| // whether we can have any matches at all. |
| // |
| // If we find any matches, we need to recheck each rule, because the rule in |
| // question may actually be case-sensitive, or we might want e.g. a prefix |
| // match instead of a substring match. (We could solve prefix/suffix by |
| // means of inserting special start-of-string and end-of-string tokens, |
| // but we keep it simple for now.) Also, the way we use the |
| // SubstringSetMatcher, we don't actually get back which rules matched. |
| // |
| // This element does not exist, if there are few enough rules that we don't |
| // deem this step worth it, or if the build of the tree failed. (In |
| // particular, if there is only a single rule in this bucket, it's |
| // pointless to run the entire Aho-Corasick algorithm instead of just |
| // doing a simple match.) Check GetMinimumRulesetSizeForSubstringMatcher() |
| // before looking up for a cheaper test. |
| SubstringMatcherMap attr_substring_matchers_; |
| RuleMap tag_rules_; |
| RuleMap ua_shadow_pseudo_element_rules_; |
| HeapVector<RuleData> link_pseudo_class_rules_; |
| HeapVector<RuleData> cue_pseudo_rules_; |
| HeapVector<RuleData> focus_pseudo_class_rules_; |
| HeapVector<RuleData> focus_visible_pseudo_class_rules_; |
| HeapVector<RuleData> universal_rules_; |
| HeapVector<RuleData> shadow_host_rules_; |
| HeapVector<RuleData> part_pseudo_rules_; |
| HeapVector<RuleData> slotted_pseudo_element_rules_; |
| HeapVector<RuleData> selector_fragment_anchor_rules_; |
| HeapVector<RuleData> root_element_rules_; |
| RuleFeatureSet features_; |
| HeapVector<Member<StyleRulePage>> page_rules_; |
| HeapVector<Member<StyleRuleFontFace>> font_face_rules_; |
| HeapVector<Member<StyleRuleFontPaletteValues>> font_palette_values_rules_; |
| HeapVector<Member<StyleRuleFontFeatureValues>> font_feature_values_rules_; |
| HeapVector<Member<StyleRuleViewTransition>> view_transition_rules_; |
| HeapVector<Member<StyleRuleKeyframes>> keyframes_rules_; |
| HeapVector<Member<StyleRuleProperty>> property_rules_; |
| HeapVector<Member<StyleRuleCounterStyle>> counter_style_rules_; |
| HeapVector<Member<StyleRulePositionTry>> position_try_rules_; |
| HeapVector<MediaQuerySetResult> media_query_set_results_; |
| HeapVector<Member<StyleRuleFunction>> function_rules_; |
| |
| // Whether there is a ruleset bucket for rules with a selector on |
| // the style attribute (which is rare, but allowed). If so, the caller |
| // may need to take extra steps to synchronize the style attribute on |
| // an element before looking for appropriate buckets. |
| bool has_bucket_for_style_attr_ = false; |
| |
| // Whether we need to check the universal bucket for rules when calculating |
| // style for the shadow host. There are two reasons why this may need to |
| // be checked: |
| // |
| // 1. Since the :scope pseudo-class can match a shadow host when that host |
| // is the scoping root, |
| // ElementRuleCollector::CollectMatchingShadowHostRules also needs to |
| // collect rules from the universal bucket, but this is only required when |
| // :scope is actually present. |
| // |
| // 2. Combination rules such as :is(:host, .foo) will be bucketed into the |
| // universal bucket and not into the bucket for :host. If this happens, |
| // we will need to check the universal bucket, too. |
| // |
| // Nothing else in the universal bucket can match the host from inside |
| // the shadow tree. |
| bool must_check_universal_bucket_for_shadow_host_ = false; |
| |
| unsigned rule_count_ = 0; |
| bool need_compaction_ = false; |
| |
| // nullptr if the stylesheet doesn't explicitly declare any layer. |
| Member<CascadeLayer> implicit_outer_layer_; |
| // Empty vector if the stylesheet doesn't explicitly declare any layer. |
| HeapVector<Interval<CascadeLayer>> layer_intervals_; |
| // Empty vector if the stylesheet doesn't use any container queries. |
| HeapVector<Interval<ContainerQuery>> container_query_intervals_; |
| // Empty vector if the stylesheet doesn't use any @scopes. |
| HeapVector<Interval<StyleScope>> scope_intervals_; |
| |
| // Backing store for the Bloom filter hashes for each RuleData. |
| // It is stored here so that we can have a variable number of them |
| // (without the overhead of a Vector in each RuleData). |
| // |
| // Note that we only really use the bottom 24 bits of each hash, |
| // so we could in theory save some more bytes here by storing 3-byte |
| // instead of 4-byte ints. However, even for sites using a fair bit |
| // of descendant selectors, we typically see <50 kB potential savings |
| // here, so we haven't gone down that route yet. (Perhaps it could |
| // in theory help with cache efficiency.) |
| Vector<unsigned> bloom_hash_backing_; |
| |
| #if DCHECK_IS_ON() |
| HeapVector<RuleData> all_rules_; |
| |
| // If true, we don't DCHECK that these are unsorted, since they |
| // came from merged+filtered rulesets, which only happens when |
| // making diff rulesets for invalidation. Those do not care |
| // about the ordering, since they do not use the CascadeLayerSeeker. |
| bool allow_unsorted_ = false; |
| #endif // DCHECK_IS_ON() |
| }; |
| |
| } // namespace blink |
| |
| WTF_ALLOW_CLEAR_UNUSED_SLOTS_WITH_MEM_FUNCTIONS( |
| blink::RuleSet::Interval<blink::CascadeLayer>) |
| WTF_ALLOW_CLEAR_UNUSED_SLOTS_WITH_MEM_FUNCTIONS( |
| blink::RuleSet::Interval<blink::ContainerQuery>) |
| WTF_ALLOW_CLEAR_UNUSED_SLOTS_WITH_MEM_FUNCTIONS( |
| blink::RuleSet::Interval<blink::StyleScope>) |
| |
| #endif // THIRD_PARTY_BLINK_RENDERER_CORE_CSS_RULE_SET_H_ |