⚠ Archived content — this site is no longer maintained.   Current WebKit documentation is at docs.webkit.org.

Changeset 286598 in webkit


Ignore:
Timestamp:
Dec 7, 2021, 9:49:42 AM (5 years ago)
Author:
Antti Koivisto
Message:

Use more specific keys for pseudo-class invalidation
https://bugs.webkit.org/show_bug.cgi?id=233883

Reviewed by Simon Fraser.

Currently pseudo-class invalidation is keyed with pseudo-class type only. If we have rule like

.foo:hover { ... }

we end up considering this rule for every element whenever :hover state changes, whether the element has class 'foo' or not.

We can improve this by keying pseudo-class invalidation more narrowly with type/class, type/id or type/tag pairs.

  • css/CSSSelector.cpp:

(WebCore::CSSSelector::CSSSelector):
(WebCore::CSSSelector::firstInCompound const):

Add a function to find the start of a compound selector.

  • css/CSSSelector.h:

(WebCore::CSSSelector::tagHistory const):
(WebCore::CSSSelector::isFirstInTagHistory const):
(WebCore::CSSSelector::setNotFirstInTagHistory):

Add isFirstInTagHistory bit, similar to the isLastInTagHistory bit, to enable firstInCompound.

(WebCore::CSSSelector::CSSSelector):

  • css/CSSSelectorList.cpp:

(WebCore::CSSSelectorList::CSSSelectorList):

Set the bit.

  • style/PseudoClassChangeInvalidation.cpp:

(WebCore::Style::makePseudoClassInvalidationKeys):

Collect the id, tag and classes from the element and make keys out of them.

(WebCore::Style::PseudoClassChangeInvalidation::computeInvalidation):
(WebCore::Style::PseudoClassChangeInvalidation::collectRuleSets):

Use the keys to look up for matching invalidation rulesets.

  • style/PseudoClassChangeInvalidation.h:
  • style/RuleFeature.cpp:

(WebCore::Style::RuleFeatureSet::recursivelyCollectFeaturesFromSelector):
(WebCore::Style::makePseudoClassInvalidationKey):

Make a key out of a selector.
Search the compound for id, classes or tag.

(WebCore::Style::RuleFeatureSet::collectFeatures):
(WebCore::Style::RuleFeatureSet::add):
(WebCore::Style::RuleFeatureSet::clear):

  • style/RuleFeature.h:
  • style/StyleScopeRuleSets.cpp:

(WebCore::Style::ScopeRuleSets::pseudoClassInvalidationRuleSets const):

  • style/StyleScopeRuleSets.h:
Location:
trunk/Source/WebCore
Files:
11 edited

Legend:

Unmodified
Added
Removed
  • trunk/Source/WebCore/ChangeLog

    r286596 r286598  
     12021-12-07  Antti Koivisto  <antti@apple.com>
     2
     3        Use more specific keys for pseudo-class invalidation
     4        https://bugs.webkit.org/show_bug.cgi?id=233883
     5
     6        Reviewed by Simon Fraser.
     7
     8        Currently pseudo-class invalidation is keyed with pseudo-class type only. If we have rule like
     9
     10        .foo:hover { ... }
     11
     12        we end up considering this rule for every element whenever :hover state changes, whether the element has class 'foo' or not.
     13
     14        We can improve this by keying pseudo-class invalidation more narrowly with type/class, type/id or type/tag pairs.
     15
     16        * css/CSSSelector.cpp:
     17        (WebCore::CSSSelector::CSSSelector):
     18        (WebCore::CSSSelector::firstInCompound const):
     19
     20        Add a function to find the start of a compound selector.
     21
     22        * css/CSSSelector.h:
     23        (WebCore::CSSSelector::tagHistory const):
     24        (WebCore::CSSSelector::isFirstInTagHistory const):
     25        (WebCore::CSSSelector::setNotFirstInTagHistory):
     26
     27        Add isFirstInTagHistory bit, similar to the isLastInTagHistory bit, to enable firstInCompound.
     28
     29        (WebCore::CSSSelector::CSSSelector):
     30        * css/CSSSelectorList.cpp:
     31        (WebCore::CSSSelectorList::CSSSelectorList):
     32
     33        Set the bit.
     34
     35        * style/PseudoClassChangeInvalidation.cpp:
     36        (WebCore::Style::makePseudoClassInvalidationKeys):
     37
     38        Collect the id, tag and classes from the element and make keys out of them.
     39
     40        (WebCore::Style::PseudoClassChangeInvalidation::computeInvalidation):
     41        (WebCore::Style::PseudoClassChangeInvalidation::collectRuleSets):
     42
     43        Use the keys to look up for matching invalidation rulesets.
     44
     45        * style/PseudoClassChangeInvalidation.h:
     46        * style/RuleFeature.cpp:
     47        (WebCore::Style::RuleFeatureSet::recursivelyCollectFeaturesFromSelector):
     48        (WebCore::Style::makePseudoClassInvalidationKey):
     49
     50        Make a key out of a selector.
     51        Search the compound for id, classes or tag.
     52
     53        (WebCore::Style::RuleFeatureSet::collectFeatures):
     54        (WebCore::Style::RuleFeatureSet::add):
     55        (WebCore::Style::RuleFeatureSet::clear):
     56        * style/RuleFeature.h:
     57        * style/StyleScopeRuleSets.cpp:
     58        (WebCore::Style::ScopeRuleSets::pseudoClassInvalidationRuleSets const):
     59        * style/StyleScopeRuleSets.h:
     60
    1612021-12-07  Kimmo Kinnunen  <kkinnunen@apple.com>
    262
  • trunk/Source/WebCore/css/CSSSelector.cpp

    r285610 r286598  
    5757    , m_pseudoType(0)
    5858    , m_isLastInSelectorList(false)
     59    , m_isFirstInTagHistory(true)
    5960    , m_isLastInTagHistory(true)
    6061    , m_hasRareData(false)
     
    289290}
    290291
     292const CSSSelector* CSSSelector::firstInCompound() const
     293{
     294    auto* selector = this;
     295    while (!selector->isFirstInTagHistory()) {
     296        auto* previousSelector = selector - 1;
     297        if (previousSelector->relation() != Subselector)
     298            break;
     299        selector = previousSelector;
     300    }
     301    return selector;
     302}
     303
    291304bool CSSSelector::operator==(const CSSSelector& other) const
    292305{
  • trunk/Source/WebCore/css/CSSSelector.h

    r285610 r286598  
    254254        // Selectors are kept in an array by CSSSelectorList. The next component of the selector is
    255255        // the next item in the array.
    256         const CSSSelector* tagHistory() const { return m_isLastInTagHistory ? 0 : const_cast<CSSSelector*>(this + 1); }
     256        const CSSSelector* tagHistory() const { return m_isLastInTagHistory ? nullptr : this + 1; }
     257        const CSSSelector* firstInCompound() const;
    257258
    258259        const QualifiedName& tagQName() const;
     
    340341        bool isLastInSelectorList() const { return m_isLastInSelectorList; }
    341342        void setLastInSelectorList() { m_isLastInSelectorList = true; }
     343        bool isFirstInTagHistory() const { return m_isFirstInTagHistory; }
    342344        bool isLastInTagHistory() const { return m_isLastInTagHistory; }
     345        void setNotFirstInTagHistory() { m_isFirstInTagHistory = false; }
    343346        void setNotLastInTagHistory() { m_isLastInTagHistory = false; }
    344347
     
    351354        mutable unsigned m_pseudoType    : 8; // PseudoType.
    352355        unsigned m_isLastInSelectorList  : 1;
     356        unsigned m_isFirstInTagHistory   : 1;
    353357        unsigned m_isLastInTagHistory    : 1;
    354358        unsigned m_hasRareData           : 1;
     
    469473}
    470474
     475inline bool isLogicalCombinationPseudoClass(CSSSelector::PseudoClassType pseudoClassType)
     476{
     477    switch (pseudoClassType) {
     478    case CSSSelector::PseudoClassIs:
     479    case CSSSelector::PseudoClassWhere:
     480    case CSSSelector::PseudoClassNot:
     481    case CSSSelector::PseudoClassAny:
     482    case CSSSelector::PseudoClassMatches:
     483    case CSSSelector::PseudoClassHas:
     484        return true;
     485    default:
     486        return false;
     487    }
     488}
     489
    471490inline bool CSSSelector::isSiblingSelector() const
    472491{
     
    512531    , m_pseudoType(0)
    513532    , m_isLastInSelectorList(false)
     533    , m_isFirstInTagHistory(true)
    514534    , m_isLastInTagHistory(true)
    515535    , m_hasRareData(false)
     
    529549    , m_pseudoType(o.m_pseudoType)
    530550    , m_isLastInSelectorList(o.m_isLastInSelectorList)
     551    , m_isFirstInTagHistory(o.m_isFirstInTagHistory)
    531552    , m_isLastInTagHistory(o.m_isLastInTagHistory)
    532553    , m_hasRareData(o.m_hasRareData)
  • trunk/Source/WebCore/css/CSSSelectorList.cpp

    r277967 r286598  
    5656    size_t arrayIndex = 0;
    5757    for (size_t i = 0; i < selectorVector.size(); ++i) {
    58         CSSParserSelector* current = selectorVector[i].get();
     58        CSSParserSelector* first = selectorVector[i].get();
     59        CSSParserSelector* current = first;
    5960        while (current) {
    6061            {
     
    6667                operator delete (currentSelector);
    6768            }
     69            if (current != first)
     70                m_selectorArray[arrayIndex].setNotFirstInTagHistory();
    6871            current = current->tagHistory();
    6972            ASSERT(!m_selectorArray[arrayIndex].isLastInSelectorList());
  • trunk/Source/WebCore/css/parser/CSSSelectorParser.cpp

    r285054 r286598  
    321321}
    322322
    323 static bool isLogicalCombinationPseudoClass(CSSSelector::PseudoClassType pseudo)
    324 {
    325     switch (pseudo) {
    326     case CSSSelector::PseudoClassIs:
    327     case CSSSelector::PseudoClassWhere:
    328     case CSSSelector::PseudoClassNot:
    329     case CSSSelector::PseudoClassAny:
    330     case CSSSelector::PseudoClassMatches:
    331     case CSSSelector::PseudoClassHas:
    332         return true;
    333     default:
    334         return false;
    335     }
    336 }
    337 
    338323static bool isPseudoClassValidAfterPseudoElement(CSSSelector::PseudoClassType pseudoClass, CSSSelector::PseudoElementType compoundPseudoElement)
    339324{
  • trunk/Source/WebCore/style/PseudoClassChangeInvalidation.cpp

    r271930 r286598  
    3333namespace Style {
    3434
     35static Vector<PseudoClassInvalidationKey, 4> makePseudoClassInvalidationKeys(CSSSelector::PseudoClassType pseudoClass, const Element& element)
     36{
     37    Vector<PseudoClassInvalidationKey, 4> keys;
     38
     39    if (!element.idForStyleResolution().isEmpty())
     40        keys.append(makePseudoClassInvalidationKey(pseudoClass, InvalidationKeyType::Id, element.idForStyleResolution()));
     41
     42    if (element.hasClass()) {
     43        auto classCount = element.classNames().size();
     44        for (size_t i = 0; i < classCount; ++i)
     45            keys.append(makePseudoClassInvalidationKey(pseudoClass, InvalidationKeyType::Class, element.classNames()[i]));
     46    }
     47
     48    keys.append(makePseudoClassInvalidationKey(pseudoClass, InvalidationKeyType::Tag, element.localName().convertToASCIILowercase()));
     49    keys.append(makePseudoClassInvalidationKey(pseudoClass, InvalidationKeyType::Universal));
     50
     51    return keys;
     52};
     53
    3554void PseudoClassChangeInvalidation::computeInvalidation(CSSSelector::PseudoClassType pseudoClass, InvalidationScope invalidationScope)
    3655{
     
    3958
    4059    traverseRuleFeatures(m_element, [&] (const RuleFeatureSet& features, bool mayAffectShadowTree) {
    41         if (mayAffectShadowTree && features.pseudoClassRules.contains(pseudoClass))
     60        if (mayAffectShadowTree && features.pseudoClassTypes.contains(pseudoClass))
    4261            mayAffectStyleInShadowTree = true;
    4362        if (m_element.shadowRoot() && features.pseudoClassesAffectingHost.contains(pseudoClass))
     
    5372        m_element.invalidateStyle();
    5473
     74    for (auto& key : makePseudoClassInvalidationKeys(pseudoClass, m_element))
     75        collectRuleSets(key, invalidationScope);
     76}
     77
     78void PseudoClassChangeInvalidation::collectRuleSets(const PseudoClassInvalidationKey& key, InvalidationScope invalidationScope)
     79{
    5580    auto& ruleSets = m_element.styleResolver().ruleSets();
    56     if (auto* invalidationRuleSets = ruleSets.pseudoClassInvalidationRuleSets(pseudoClass)) {
    57         for (auto& invalidationRuleSet : *invalidationRuleSets) {
    58             // For focus/hover we flip the whole ancestor chain. We only need to do deep invalidation traversal in the change root.
    59             auto shouldInvalidate = [&] {
    60                 switch (invalidationScope) {
    61                 case InvalidationScope::All:
    62                     return true;
    63                 case InvalidationScope::SelfChildrenAndSiblings:
    64                     return invalidationRuleSet.matchElement != MatchElement::Ancestor;
    65                 case InvalidationScope::Descendants:
    66                     return invalidationRuleSet.matchElement == MatchElement::Ancestor;
    67                 }
    68                 ASSERT_NOT_REACHED();
     81    auto* invalidationRuleSets = ruleSets.pseudoClassInvalidationRuleSets(key);
     82    if (!invalidationRuleSets)
     83        return;
     84
     85    for (auto& invalidationRuleSet : *invalidationRuleSets) {
     86        // For focus/hover we flip the whole ancestor chain. We only need to do deep invalidation traversal in the change root.
     87        auto shouldInvalidate = [&] {
     88            bool invalidatesAllDescendants = invalidationRuleSet.matchElement == MatchElement::Ancestor && isUniversalInvalidation(key);
     89            switch (invalidationScope) {
     90            case InvalidationScope::All:
    6991                return true;
    70             }();
    71             if (!shouldInvalidate)
    72                 continue;
    73             Invalidator::addToMatchElementRuleSets(m_matchElementRuleSets, invalidationRuleSet);
    74         }
     92            case InvalidationScope::SelfChildrenAndSiblings:
     93                return !invalidatesAllDescendants;
     94            case InvalidationScope::Descendants:
     95                return invalidatesAllDescendants;
     96            }
     97            ASSERT_NOT_REACHED();
     98            return true;
     99        }();
     100        if (!shouldInvalidate)
     101            continue;
     102
     103        Invalidator::addToMatchElementRuleSets(m_matchElementRuleSets, invalidationRuleSet);
    75104    }
    76105}
  • trunk/Source/WebCore/style/PseudoClassChangeInvalidation.h

    r271930 r286598  
    4141private:
    4242    void computeInvalidation(CSSSelector::PseudoClassType, Style::InvalidationScope);
     43    void collectRuleSets(const PseudoClassInvalidationKey&, InvalidationScope);
    4344    void invalidateStyleWithRuleSets();
    4445
  • trunk/Source/WebCore/style/RuleFeature.cpp

    r286433 r286598  
    227227                break;
    228228            }
    229         } else if (selector->match() == CSSSelector::PseudoClass)
    230             selectorFeatures.pseudoClasses.append(std::make_pair(selector->pseudoClassType(), matchElement));
     229        } else if (selector->match() == CSSSelector::PseudoClass) {
     230            if (!isLogicalCombinationPseudoClass(selector->pseudoClassType()))
     231                selectorFeatures.pseudoClasses.append(std::make_pair(selector, matchElement));
     232        }
    231233
    232234        if (!selectorFeatures.hasSiblingSelector && selector->isSiblingSelector())
     
    247249    } while (selector);
    248250}
     251
     252PseudoClassInvalidationKey makePseudoClassInvalidationKey(CSSSelector::PseudoClassType pseudoClass, InvalidationKeyType keyType, const AtomString& keyString)
     253{
     254    ASSERT(keyType != InvalidationKeyType::Universal || keyString == starAtom());
     255    return {
     256        pseudoClass,
     257        static_cast<uint8_t>(keyType),
     258        keyString
     259    };
     260};
     261
     262static PseudoClassInvalidationKey makePseudoClassInvalidationKey(const CSSSelector& selector)
     263{
     264    ASSERT(selector.match() == CSSSelector::PseudoClass);
     265
     266    auto pseudoClassType = selector.pseudoClassType();
     267
     268    AtomString className;
     269    AtomString tagName;
     270    for (auto* simpleSelector = selector.firstInCompound(); simpleSelector; simpleSelector = simpleSelector->tagHistory()) {
     271        if (simpleSelector->match() == CSSSelector::Id)
     272            return makePseudoClassInvalidationKey(pseudoClassType, InvalidationKeyType::Id, simpleSelector->value());
     273
     274        if (simpleSelector->match() == CSSSelector::Class && className.isNull())
     275            className = simpleSelector->value();
     276
     277        if (simpleSelector->match() == CSSSelector::Tag)
     278            tagName = simpleSelector->tagLowercaseLocalName();
     279
     280        if (simpleSelector->relation() != CSSSelector::Subselector)
     281            break;
     282    }
     283    if (!className.isEmpty())
     284        return makePseudoClassInvalidationKey(pseudoClassType, InvalidationKeyType::Class, className);
     285
     286    if (!tagName.isEmpty() && tagName != starAtom())
     287        return makePseudoClassInvalidationKey(pseudoClassType, InvalidationKeyType::Tag, tagName);
     288
     289    return makePseudoClassInvalidationKey(selector.pseudoClassType(), InvalidationKeyType::Universal);
     290};
    249291
    250292void RuleFeatureSet::collectFeatures(const RuleData& ruleData)
     
    288330    }
    289331
    290     addToMap(pseudoClassRules, selectorFeatures.pseudoClasses, &pseudoClassesAffectingHost);
     332    for (auto& selectorAndMatch : selectorFeatures.pseudoClasses) {
     333        auto* selector = selectorAndMatch.first;
     334        auto matchElement = selectorAndMatch.second;
     335        pseudoClassRules.ensure(makePseudoClassInvalidationKey(*selector), [] {
     336            return makeUnique<Vector<RuleFeature>>();
     337        }).iterator->value->append({ ruleData, matchElement });
     338
     339        if (matchElement == MatchElement::Host)
     340            pseudoClassesAffectingHost.add(selector->pseudoClassType());
     341        pseudoClassTypes.add(selector->pseudoClassType());
     342
     343        setUsesMatchElement(matchElement);
     344    }
    291345}
    292346
     
    320374    addMap(pseudoClassRules, other.pseudoClassRules);
    321375    pseudoClassesAffectingHost.add(other.pseudoClassesAffectingHost.begin(), other.pseudoClassesAffectingHost.end());
     376    pseudoClassTypes.add(other.pseudoClassTypes.begin(), other.pseudoClassTypes.end());
    322377
    323378    for (size_t i = 0; i < usedMatchElements.size(); ++i)
     
    352407    pseudoClassRules.clear();
    353408    pseudoClassesAffectingHost.clear();
     409    pseudoClassTypes.clear();
    354410    usesFirstLineRules = false;
    355411    usesFirstLetterRules = false;
  • trunk/Source/WebCore/style/RuleFeature.h

    r286433 r286598  
    7575};
    7676
     77using PseudoClassInvalidationKey = std::tuple<unsigned, uint8_t, AtomString>;
     78
    7779using RuleFeatureVector = Vector<RuleFeature>;
    7880
     
    99101    HashMap<AtomString, std::unique_ptr<RuleFeatureVector>> classRules;
    100102    HashMap<AtomString, std::unique_ptr<Vector<RuleFeatureWithInvalidationSelector>>> attributeRules;
    101     HashMap<CSSSelector::PseudoClassType, std::unique_ptr<RuleFeatureVector>, IntHash<CSSSelector::PseudoClassType>, WTF::StrongEnumHashTraits<CSSSelector::PseudoClassType>> pseudoClassRules;
     103    HashMap<PseudoClassInvalidationKey, std::unique_ptr<RuleFeatureVector>> pseudoClassRules;
    102104    HashSet<AtomString> classesAffectingHost;
    103105    HashSet<AtomString> attributesAffectingHost;
    104106    HashSet<CSSSelector::PseudoClassType, IntHash<CSSSelector::PseudoClassType>, WTF::StrongEnumHashTraits<CSSSelector::PseudoClassType>> pseudoClassesAffectingHost;
     107    HashSet<CSSSelector::PseudoClassType, IntHash<CSSSelector::PseudoClassType>, WTF::StrongEnumHashTraits<CSSSelector::PseudoClassType>> pseudoClassTypes;
    105108
    106109    std::array<bool, matchElementCount> usedMatchElements { };
     
    117120        Vector<std::pair<AtomString, MatchElement>, 32> classes;
    118121        Vector<std::pair<const CSSSelector*, MatchElement>, 32> attributes;
    119         Vector<std::pair<CSSSelector::PseudoClassType, MatchElement>, 32> pseudoClasses;
     122        Vector<std::pair<const CSSSelector*, MatchElement>, 32> pseudoClasses;
    120123    };
    121124    void recursivelyCollectFeaturesFromSelector(SelectorFeatures&, const CSSSelector&, MatchElement = MatchElement::Subject);
     
    125128MatchElement computeHasPseudoClassMatchElement(const CSSSelector&);
    126129
     130enum class InvalidationKeyType : uint8_t { Universal = 1, Class, Id, Tag };
     131PseudoClassInvalidationKey makePseudoClassInvalidationKey(CSSSelector::PseudoClassType, InvalidationKeyType, const AtomString& = starAtom());
     132
     133inline bool isUniversalInvalidation(const PseudoClassInvalidationKey& key)
     134{
     135    return static_cast<InvalidationKeyType>(std::get<1>(key)) == InvalidationKeyType::Universal;
     136}
     137
    127138} // namespace Style
    128139} // namespace WebCore
  • trunk/Source/WebCore/style/StyleScopeRuleSets.cpp

    r286226 r286598  
    296296}
    297297
    298 const Vector<InvalidationRuleSet>* ScopeRuleSets::pseudoClassInvalidationRuleSets(CSSSelector::PseudoClassType pseudoClass) const
    299 {
    300     return ensureInvalidationRuleSets(pseudoClass, m_pseudoClassInvalidationRuleSets, m_features.pseudoClassRules);
     298const Vector<InvalidationRuleSet>* ScopeRuleSets::pseudoClassInvalidationRuleSets(const PseudoClassInvalidationKey& pseudoClassKey) const
     299{
     300    return ensureInvalidationRuleSets(pseudoClassKey, m_pseudoClassInvalidationRuleSets, m_features.pseudoClassRules);
    301301}
    302302
  • trunk/Source/WebCore/style/StyleScopeRuleSets.h

    r286226 r286598  
    6868    const Vector<InvalidationRuleSet>* classInvalidationRuleSets(const AtomString&) const;
    6969    const Vector<InvalidationRuleSet>* attributeInvalidationRuleSets(const AtomString&) const;
    70     const Vector<InvalidationRuleSet>* pseudoClassInvalidationRuleSets(CSSSelector::PseudoClassType) const;
     70    const Vector<InvalidationRuleSet>* pseudoClassInvalidationRuleSets(const PseudoClassInvalidationKey&) const;
    7171
    7272    const Vector<InvalidationRuleSet>* invalidationRuleSetsForChildChange(const Element&);
     
    107107    mutable HashMap<AtomString, std::unique_ptr<Vector<InvalidationRuleSet>>> m_classInvalidationRuleSets;
    108108    mutable HashMap<AtomString, std::unique_ptr<Vector<InvalidationRuleSet>>> m_attributeInvalidationRuleSets;
    109     mutable HashMap<CSSSelector::PseudoClassType, std::unique_ptr<Vector<InvalidationRuleSet>>, IntHash<CSSSelector::PseudoClassType>, WTF::StrongEnumHashTraits<CSSSelector::PseudoClassType>> m_pseudoClassInvalidationRuleSets;
     109    mutable HashMap<PseudoClassInvalidationKey, std::unique_ptr<Vector<InvalidationRuleSet>>> m_pseudoClassInvalidationRuleSets;
    110110
    111111    mutable std::optional<bool> m_cachedHasComplexSelectorsForStyleAttribute;
Note: See TracChangeset for help on using the changeset viewer.