blob: d50f6bf02c016ae5d2a0f60eee9f337ef30ea3d2 [file] [log] [blame]
/*
* Copyright (C) 1999 Lars Knoll (knoll@kde.org)
* (C) 2004-2005 Allan Sandfeld Jensen (kde@carewolf.com)
* Copyright (C) 2006, 2007 Nicholas Shanks (webkit@nickshanks.com)
* Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012 Apple Inc. All
* rights reserved.
* Copyright (C) 2007 Alexey Proskuryakov <ap@webkit.org>
* Copyright (C) 2007, 2008 Eric Seidel <eric@webkit.org>
* Copyright (C) 2008, 2009 Torch Mobile Inc. All rights reserved.
* (http://www.torchmobile.com/)
* Copyright (c) 2011, Code Aurora Forum. All rights reserved.
* Copyright (C) Research In Motion Limited 2011. All rights reserved.
* Copyright (C) 2012 Google 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.
*/
#include "core/css/RuleFeature.h"
#include "core/HTMLNames.h"
#include "core/css/CSSCustomIdentValue.h"
#include "core/css/CSSFunctionValue.h"
#include "core/css/CSSSelector.h"
#include "core/css/CSSSelectorList.h"
#include "core/css/CSSValueList.h"
#include "core/css/RuleSet.h"
#include "core/css/StylePropertySet.h"
#include "core/css/StyleRule.h"
#include "core/css/invalidation/InvalidationSet.h"
#include "core/dom/Element.h"
#include "core/dom/Node.h"
#include "core/inspector/InspectorTraceEvents.h"
#include "wtf/BitVector.h"
namespace blink {
namespace {
bool supportsInvalidation(CSSSelector::MatchType match) {
switch (match) {
case CSSSelector::Tag:
case CSSSelector::Id:
case CSSSelector::Class:
case CSSSelector::AttributeExact:
case CSSSelector::AttributeSet:
case CSSSelector::AttributeHyphen:
case CSSSelector::AttributeList:
case CSSSelector::AttributeContain:
case CSSSelector::AttributeBegin:
case CSSSelector::AttributeEnd:
return true;
case CSSSelector::Unknown:
case CSSSelector::PagePseudoClass:
// These should not appear in StyleRule selectors.
NOTREACHED();
return false;
default:
// New match type added. Figure out if it needs a subtree invalidation or
// not.
NOTREACHED();
return false;
}
}
bool supportsInvalidation(CSSSelector::PseudoType type) {
switch (type) {
case CSSSelector::PseudoEmpty:
case CSSSelector::PseudoFirstChild:
case CSSSelector::PseudoFirstOfType:
case CSSSelector::PseudoLastChild:
case CSSSelector::PseudoLastOfType:
case CSSSelector::PseudoOnlyChild:
case CSSSelector::PseudoOnlyOfType:
case CSSSelector::PseudoNthChild:
case CSSSelector::PseudoNthOfType:
case CSSSelector::PseudoNthLastChild:
case CSSSelector::PseudoNthLastOfType:
case CSSSelector::PseudoLink:
case CSSSelector::PseudoVisited:
case CSSSelector::PseudoAny:
case CSSSelector::PseudoAnyLink:
case CSSSelector::PseudoAutofill:
case CSSSelector::PseudoHover:
case CSSSelector::PseudoDrag:
case CSSSelector::PseudoFocus:
case CSSSelector::PseudoActive:
case CSSSelector::PseudoChecked:
case CSSSelector::PseudoEnabled:
case CSSSelector::PseudoFullPageMedia:
case CSSSelector::PseudoDefault:
case CSSSelector::PseudoDisabled:
case CSSSelector::PseudoOptional:
case CSSSelector::PseudoPlaceholderShown:
case CSSSelector::PseudoRequired:
case CSSSelector::PseudoReadOnly:
case CSSSelector::PseudoReadWrite:
case CSSSelector::PseudoValid:
case CSSSelector::PseudoInvalid:
case CSSSelector::PseudoIndeterminate:
case CSSSelector::PseudoTarget:
case CSSSelector::PseudoBefore:
case CSSSelector::PseudoAfter:
case CSSSelector::PseudoBackdrop:
case CSSSelector::PseudoLang:
case CSSSelector::PseudoNot:
case CSSSelector::PseudoPlaceholder:
case CSSSelector::PseudoResizer:
case CSSSelector::PseudoRoot:
case CSSSelector::PseudoScope:
case CSSSelector::PseudoScrollbar:
case CSSSelector::PseudoScrollbarButton:
case CSSSelector::PseudoScrollbarCorner:
case CSSSelector::PseudoScrollbarThumb:
case CSSSelector::PseudoScrollbarTrack:
case CSSSelector::PseudoScrollbarTrackPiece:
case CSSSelector::PseudoWindowInactive:
case CSSSelector::PseudoSelection:
case CSSSelector::PseudoCornerPresent:
case CSSSelector::PseudoDecrement:
case CSSSelector::PseudoIncrement:
case CSSSelector::PseudoHorizontal:
case CSSSelector::PseudoVertical:
case CSSSelector::PseudoStart:
case CSSSelector::PseudoEnd:
case CSSSelector::PseudoDoubleButton:
case CSSSelector::PseudoSingleButton:
case CSSSelector::PseudoNoButton:
case CSSSelector::PseudoFullScreen:
case CSSSelector::PseudoFullScreenAncestor:
case CSSSelector::PseudoInRange:
case CSSSelector::PseudoOutOfRange:
case CSSSelector::PseudoWebKitCustomElement:
case CSSSelector::PseudoBlinkInternalElement:
case CSSSelector::PseudoCue:
case CSSSelector::PseudoFutureCue:
case CSSSelector::PseudoPastCue:
case CSSSelector::PseudoUnresolved:
case CSSSelector::PseudoDefined:
case CSSSelector::PseudoContent:
case CSSSelector::PseudoHost:
case CSSSelector::PseudoShadow:
case CSSSelector::PseudoSpatialNavigationFocus:
case CSSSelector::PseudoListBox:
case CSSSelector::PseudoHostHasAppearance:
case CSSSelector::PseudoSlotted:
return true;
case CSSSelector::PseudoUnknown:
case CSSSelector::PseudoLeftPage:
case CSSSelector::PseudoRightPage:
case CSSSelector::PseudoFirstPage:
// These should not appear in StyleRule selectors.
NOTREACHED();
return false;
default:
// New pseudo type added. Figure out if it needs a subtree invalidation or
// not.
NOTREACHED();
return false;
}
}
bool supportsInvalidationWithSelectorList(CSSSelector::PseudoType pseudo) {
return pseudo == CSSSelector::PseudoAny || pseudo == CSSSelector::PseudoCue ||
pseudo == CSSSelector::PseudoHost ||
pseudo == CSSSelector::PseudoHostContext ||
pseudo == CSSSelector::PseudoNot ||
pseudo == CSSSelector::PseudoSlotted;
}
bool requiresSubtreeInvalidation(const CSSSelector& selector) {
if (selector.match() != CSSSelector::PseudoElement &&
selector.match() != CSSSelector::PseudoClass) {
DCHECK(supportsInvalidation(selector.match()));
return false;
}
switch (selector.getPseudoType()) {
case CSSSelector::PseudoFirstLine:
case CSSSelector::PseudoFirstLetter:
// FIXME: Most pseudo classes/elements above can be supported and moved
// to assertSupportedPseudo(). Move on a case-by-case basis. If they
// require subtree invalidation, document why.
case CSSSelector::PseudoHostContext:
// :host-context matches a shadow host, yet the simple selectors inside
// :host-context matches an ancestor of the shadow host.
return true;
default:
DCHECK(supportsInvalidation(selector.getPseudoType()));
return false;
}
}
InvalidationSet& storedInvalidationSet(RefPtr<InvalidationSet>& invalidationSet,
InvalidationType type) {
if (!invalidationSet) {
if (type == InvalidateDescendants)
invalidationSet = DescendantInvalidationSet::create();
else
invalidationSet = SiblingInvalidationSet::create(nullptr);
return *invalidationSet;
}
if (invalidationSet->type() == type)
return *invalidationSet;
if (type == InvalidateDescendants)
return toSiblingInvalidationSet(*invalidationSet).ensureDescendants();
RefPtr<InvalidationSet> descendants = invalidationSet;
invalidationSet = SiblingInvalidationSet::create(
toDescendantInvalidationSet(descendants.get()));
return *invalidationSet;
}
InvalidationSet& ensureInvalidationSet(
HashMap<AtomicString, RefPtr<InvalidationSet>>& map,
const AtomicString& key,
InvalidationType type) {
RefPtr<InvalidationSet>& invalidationSet =
map.add(key, nullptr).storedValue->value;
return storedInvalidationSet(invalidationSet, type);
}
InvalidationSet& ensureInvalidationSet(
HashMap<CSSSelector::PseudoType,
RefPtr<InvalidationSet>,
WTF::IntHash<unsigned>,
WTF::UnsignedWithZeroKeyHashTraits<unsigned>>& map,
CSSSelector::PseudoType key,
InvalidationType type) {
RefPtr<InvalidationSet>& invalidationSet =
map.add(key, nullptr).storedValue->value;
return storedInvalidationSet(invalidationSet, type);
}
void extractInvalidationSets(InvalidationSet* invalidationSet,
DescendantInvalidationSet*& descendants,
SiblingInvalidationSet*& siblings) {
if (invalidationSet->type() == InvalidateDescendants) {
descendants = toDescendantInvalidationSet(invalidationSet);
siblings = nullptr;
return;
}
siblings = toSiblingInvalidationSet(invalidationSet);
descendants = siblings->descendants();
}
} // anonymous namespace
RuleFeature::RuleFeature(StyleRule* rule,
unsigned selectorIndex,
bool hasDocumentSecurityOrigin)
: rule(rule),
selectorIndex(selectorIndex),
hasDocumentSecurityOrigin(hasDocumentSecurityOrigin) {}
DEFINE_TRACE(RuleFeature) {
visitor->trace(rule);
}
ALWAYS_INLINE InvalidationSet& RuleFeatureSet::ensureClassInvalidationSet(
const AtomicString& className,
InvalidationType type) {
DCHECK(!className.isEmpty());
return ensureInvalidationSet(m_classInvalidationSets, className, type);
}
ALWAYS_INLINE InvalidationSet& RuleFeatureSet::ensureAttributeInvalidationSet(
const AtomicString& attributeName,
InvalidationType type) {
DCHECK(!attributeName.isEmpty());
return ensureInvalidationSet(m_attributeInvalidationSets, attributeName,
type);
}
ALWAYS_INLINE InvalidationSet& RuleFeatureSet::ensureIdInvalidationSet(
const AtomicString& id,
InvalidationType type) {
DCHECK(!id.isEmpty());
return ensureInvalidationSet(m_idInvalidationSets, id, type);
}
ALWAYS_INLINE InvalidationSet& RuleFeatureSet::ensurePseudoInvalidationSet(
CSSSelector::PseudoType pseudoType,
InvalidationType type) {
DCHECK(pseudoType != CSSSelector::PseudoUnknown);
return ensureInvalidationSet(m_pseudoInvalidationSets, pseudoType, type);
}
void RuleFeatureSet::updateFeaturesFromCombinator(
const CSSSelector& lastInCompound,
const CSSSelector* lastCompoundInAdjacentChain,
InvalidationSetFeatures& lastCompoundInAdjacentChainFeatures,
InvalidationSetFeatures*& siblingFeatures,
InvalidationSetFeatures& descendantFeatures) {
if (lastInCompound.isAdjacentSelector()) {
if (!siblingFeatures) {
siblingFeatures = &lastCompoundInAdjacentChainFeatures;
if (lastCompoundInAdjacentChain) {
extractInvalidationSetFeaturesFromCompound(
*lastCompoundInAdjacentChain, lastCompoundInAdjacentChainFeatures,
Ancestor);
if (!lastCompoundInAdjacentChainFeatures.hasFeatures())
lastCompoundInAdjacentChainFeatures.forceSubtree = true;
}
}
if (siblingFeatures->maxDirectAdjacentSelectors == UINT_MAX)
return;
if (lastInCompound.relation() == CSSSelector::DirectAdjacent)
++siblingFeatures->maxDirectAdjacentSelectors;
else
siblingFeatures->maxDirectAdjacentSelectors = UINT_MAX;
return;
}
if (siblingFeatures &&
lastCompoundInAdjacentChainFeatures.maxDirectAdjacentSelectors)
lastCompoundInAdjacentChainFeatures = InvalidationSetFeatures();
siblingFeatures = nullptr;
if (lastInCompound.isShadowSelector())
descendantFeatures.treeBoundaryCrossing = true;
if (lastInCompound.relation() == CSSSelector::ShadowSlot ||
lastInCompound.relationIsAffectedByPseudoContent())
descendantFeatures.insertionPointCrossing = true;
if (lastInCompound.relationIsAffectedByPseudoContent())
descendantFeatures.contentPseudoCrossing = true;
}
void RuleFeatureSet::extractInvalidationSetFeaturesFromSimpleSelector(
const CSSSelector& selector,
InvalidationSetFeatures& features) {
if (selector.match() == CSSSelector::Tag &&
selector.tagQName().localName() != starAtom) {
features.tagNames.append(selector.tagQName().localName());
return;
}
if (selector.match() == CSSSelector::Id) {
features.ids.append(selector.value());
return;
}
if (selector.match() == CSSSelector::Class) {
features.classes.append(selector.value());
return;
}
if (selector.isAttributeSelector()) {
features.attributes.append(selector.attribute().localName());
return;
}
switch (selector.getPseudoType()) {
case CSSSelector::PseudoWebKitCustomElement:
case CSSSelector::PseudoBlinkInternalElement:
features.customPseudoElement = true;
return;
case CSSSelector::PseudoBefore:
case CSSSelector::PseudoAfter:
features.hasBeforeOrAfter = true;
return;
case CSSSelector::PseudoSlotted:
features.invalidatesSlotted = true;
return;
default:
return;
}
}
InvalidationSet* RuleFeatureSet::invalidationSetForSimpleSelector(
const CSSSelector& selector,
InvalidationType type) {
if (selector.match() == CSSSelector::Class)
return &ensureClassInvalidationSet(selector.value(), type);
if (selector.isAttributeSelector())
return &ensureAttributeInvalidationSet(selector.attribute().localName(),
type);
if (selector.match() == CSSSelector::Id)
return &ensureIdInvalidationSet(selector.value(), type);
if (selector.match() == CSSSelector::PseudoClass) {
switch (selector.getPseudoType()) {
case CSSSelector::PseudoEmpty:
case CSSSelector::PseudoFirstChild:
case CSSSelector::PseudoLastChild:
case CSSSelector::PseudoOnlyChild:
case CSSSelector::PseudoLink:
case CSSSelector::PseudoVisited:
case CSSSelector::PseudoAnyLink:
case CSSSelector::PseudoAutofill:
case CSSSelector::PseudoHover:
case CSSSelector::PseudoDrag:
case CSSSelector::PseudoFocus:
case CSSSelector::PseudoActive:
case CSSSelector::PseudoChecked:
case CSSSelector::PseudoEnabled:
case CSSSelector::PseudoDefault:
case CSSSelector::PseudoDisabled:
case CSSSelector::PseudoOptional:
case CSSSelector::PseudoPlaceholderShown:
case CSSSelector::PseudoRequired:
case CSSSelector::PseudoReadOnly:
case CSSSelector::PseudoReadWrite:
case CSSSelector::PseudoValid:
case CSSSelector::PseudoInvalid:
case CSSSelector::PseudoIndeterminate:
case CSSSelector::PseudoTarget:
case CSSSelector::PseudoLang:
case CSSSelector::PseudoFullScreen:
case CSSSelector::PseudoFullScreenAncestor:
case CSSSelector::PseudoInRange:
case CSSSelector::PseudoOutOfRange:
case CSSSelector::PseudoUnresolved:
case CSSSelector::PseudoDefined:
return &ensurePseudoInvalidationSet(selector.getPseudoType(), type);
case CSSSelector::PseudoFirstOfType:
case CSSSelector::PseudoLastOfType:
case CSSSelector::PseudoOnlyOfType:
case CSSSelector::PseudoNthChild:
case CSSSelector::PseudoNthOfType:
case CSSSelector::PseudoNthLastChild:
case CSSSelector::PseudoNthLastOfType:
return &ensureNthInvalidationSet();
default:
break;
}
}
return nullptr;
}
void RuleFeatureSet::updateInvalidationSets(const RuleData& ruleData) {
// Given a rule, update the descendant invalidation sets for the features
// found in its selector. The first step is to extract the features from the
// rightmost compound selector (extractInvalidationSetFeaturesFromCompound).
// Secondly, add those features to the invalidation sets for the features
// found in the other compound selectors (addFeaturesToInvalidationSets). If
// we find a feature in the right-most compound selector that requires a
// subtree recalc, nextCompound will be the rightmost compound and we will
// addFeaturesToInvalidationSets for that one as well.
InvalidationSetFeatures features;
InvalidationSetFeatures* siblingFeatures = nullptr;
const CSSSelector* lastInCompound =
extractInvalidationSetFeaturesFromCompound(ruleData.selector(), features,
Subject);
if (features.forceSubtree)
features.hasFeaturesForRuleSetInvalidation = false;
else if (!features.hasFeatures())
features.forceSubtree = true;
if (features.hasNthPseudo)
addFeaturesToInvalidationSet(ensureNthInvalidationSet(), features);
if (features.hasBeforeOrAfter)
updateInvalidationSetsForContentAttribute(ruleData);
const CSSSelector* nextCompound =
lastInCompound ? lastInCompound->tagHistory() : &ruleData.selector();
if (!nextCompound) {
if (!features.hasFeaturesForRuleSetInvalidation)
m_metadata.needsFullRecalcForRuleSetInvalidation = true;
return;
}
if (lastInCompound)
updateFeaturesFromCombinator(*lastInCompound, nullptr, features,
siblingFeatures, features);
addFeaturesToInvalidationSets(*nextCompound, siblingFeatures, features);
if (!features.hasFeaturesForRuleSetInvalidation)
m_metadata.needsFullRecalcForRuleSetInvalidation = true;
}
void RuleFeatureSet::updateInvalidationSetsForContentAttribute(
const RuleData& ruleData) {
// If any ::before and ::after rules specify 'content: attr(...)', we
// need to create invalidation sets for those attributes to have content
// changes applied through style recalc.
const StylePropertySet& propertySet = ruleData.rule()->properties();
int propertyIndex = propertySet.findPropertyIndex(CSSPropertyContent);
if (propertyIndex == -1)
return;
StylePropertySet::PropertyReference contentProperty =
propertySet.propertyAt(propertyIndex);
const CSSValue& contentValue = contentProperty.value();
if (!contentValue.isValueList())
return;
for (auto& item : toCSSValueList(contentValue)) {
if (!item->isFunctionValue())
continue;
const CSSFunctionValue* functionValue = toCSSFunctionValue(item.get());
if (functionValue->functionType() != CSSValueAttr)
continue;
ensureAttributeInvalidationSet(
AtomicString(toCSSCustomIdentValue(functionValue->item(0)).value()),
InvalidateDescendants)
.setInvalidatesSelf();
}
}
RuleFeatureSet::FeatureInvalidationType
RuleFeatureSet::extractInvalidationSetFeaturesFromSelectorList(
const CSSSelector& simpleSelector,
InvalidationSetFeatures& features,
PositionType position) {
const CSSSelectorList* selectorList = simpleSelector.selectorList();
if (!selectorList)
return NormalInvalidation;
DCHECK(supportsInvalidationWithSelectorList(simpleSelector.getPseudoType()));
const CSSSelector* subSelector = selectorList->first();
bool allSubSelectorsHaveFeatures = true;
InvalidationSetFeatures anyFeatures;
for (; subSelector; subSelector = CSSSelectorList::next(*subSelector)) {
InvalidationSetFeatures compoundFeatures;
if (!extractInvalidationSetFeaturesFromCompound(
*subSelector, compoundFeatures, position,
simpleSelector.getPseudoType())) {
// A null selector return means the sub-selector contained a
// selector which requiresSubtreeInvalidation().
DCHECK(compoundFeatures.forceSubtree);
features.forceSubtree = true;
return RequiresSubtreeInvalidation;
}
if (compoundFeatures.hasNthPseudo)
features.hasNthPseudo = true;
if (!allSubSelectorsHaveFeatures)
continue;
if (compoundFeatures.hasFeatures())
anyFeatures.add(compoundFeatures);
else
allSubSelectorsHaveFeatures = false;
}
// Don't add any features if one of the sub-selectors of does not contain
// any invalidation set features. E.g. :-webkit-any(*, span).
if (allSubSelectorsHaveFeatures)
features.add(anyFeatures);
return NormalInvalidation;
}
const CSSSelector* RuleFeatureSet::extractInvalidationSetFeaturesFromCompound(
const CSSSelector& compound,
InvalidationSetFeatures& features,
PositionType position,
CSSSelector::PseudoType pseudo) {
// Extract invalidation set features and return a pointer to the the last
// simple selector of the compound, or nullptr if one of the selectors
// requiresSubtreeInvalidation().
const CSSSelector* simpleSelector = &compound;
for (;; simpleSelector = simpleSelector->tagHistory()) {
// Fall back to use subtree invalidations, even for features in the
// rightmost compound selector. Returning nullptr here will make
// addFeaturesToInvalidationSets start marking invalidation sets for
// subtree recalc for features in the rightmost compound selector.
if (requiresSubtreeInvalidation(*simpleSelector)) {
features.forceSubtree = true;
return nullptr;
}
// When inside a :not(), we should not use the found features for
// invalidation because we should invalidate elements _without_ that
// feature. On the other hand, we should still have invalidation sets
// for the features since we are able to detect when they change.
// That is, ".a" should not have ".b" in its invalidation set for
// ".a :not(.b)", but there should be an invalidation set for ".a" in
// ":not(.a) .b".
if (pseudo != CSSSelector::PseudoNot)
extractInvalidationSetFeaturesFromSimpleSelector(*simpleSelector,
features);
// Initialize the entry in the invalidation set map for self-
// invalidation, if supported.
if (InvalidationSet* invalidationSet = invalidationSetForSimpleSelector(
*simpleSelector, InvalidateDescendants)) {
if (invalidationSet == m_nthInvalidationSet)
features.hasNthPseudo = true;
else if (position == Subject)
invalidationSet->setInvalidatesSelf();
}
if (extractInvalidationSetFeaturesFromSelectorList(*simpleSelector,
features, position) ==
RequiresSubtreeInvalidation) {
DCHECK(features.forceSubtree);
return nullptr;
}
if (!simpleSelector->tagHistory() ||
simpleSelector->relation() != CSSSelector::SubSelector) {
features.hasFeaturesForRuleSetInvalidation =
features.hasTagIdClassOrAttribute();
return simpleSelector;
}
}
}
// Add features extracted from the rightmost compound selector to descendant
// invalidation sets for features found in other compound selectors.
//
// We use descendant invalidation for descendants, sibling invalidation for
// siblings and their subtrees.
//
// As we encounter a descendant type of combinator, the features only need to be
// checked against descendants in the same subtree only. features.adjacent is
// set to false, and we start adding features to the descendant invalidation
// set.
void RuleFeatureSet::addFeaturesToInvalidationSet(
InvalidationSet& invalidationSet,
const InvalidationSetFeatures& features) {
if (features.treeBoundaryCrossing)
invalidationSet.setTreeBoundaryCrossing();
if (features.insertionPointCrossing)
invalidationSet.setInsertionPointCrossing();
if (features.invalidatesSlotted)
invalidationSet.setInvalidatesSlotted();
if (features.forceSubtree)
invalidationSet.setWholeSubtreeInvalid();
if (features.contentPseudoCrossing || features.forceSubtree)
return;
for (const auto& id : features.ids)
invalidationSet.addId(id);
for (const auto& tagName : features.tagNames)
invalidationSet.addTagName(tagName);
for (const auto& className : features.classes)
invalidationSet.addClass(className);
for (const auto& attribute : features.attributes)
invalidationSet.addAttribute(attribute);
if (features.customPseudoElement)
invalidationSet.setCustomPseudoInvalid();
}
void RuleFeatureSet::addFeaturesToInvalidationSetsForSelectorList(
const CSSSelector& simpleSelector,
InvalidationSetFeatures* siblingFeatures,
InvalidationSetFeatures& descendantFeatures) {
if (!simpleSelector.selectorList())
return;
DCHECK(supportsInvalidationWithSelectorList(simpleSelector.getPseudoType()));
bool hadFeaturesForRuleSetInvalidation =
descendantFeatures.hasFeaturesForRuleSetInvalidation;
bool selectorListContainsUniversal =
simpleSelector.getPseudoType() == CSSSelector::PseudoNot ||
simpleSelector.getPseudoType() == CSSSelector::PseudoHostContext;
for (const CSSSelector* subSelector = simpleSelector.selectorList()->first();
subSelector; subSelector = CSSSelectorList::next(*subSelector)) {
descendantFeatures.hasFeaturesForRuleSetInvalidation = false;
addFeaturesToInvalidationSetsForCompoundSelector(
*subSelector, siblingFeatures, descendantFeatures);
if (!descendantFeatures.hasFeaturesForRuleSetInvalidation)
selectorListContainsUniversal = true;
}
descendantFeatures.hasFeaturesForRuleSetInvalidation =
hadFeaturesForRuleSetInvalidation || !selectorListContainsUniversal;
}
void RuleFeatureSet::addFeaturesToInvalidationSetsForSimpleSelector(
const CSSSelector& simpleSelector,
InvalidationSetFeatures* siblingFeatures,
InvalidationSetFeatures& descendantFeatures) {
if (InvalidationSet* invalidationSet = invalidationSetForSimpleSelector(
simpleSelector,
siblingFeatures ? InvalidateSiblings : InvalidateDescendants)) {
if (!siblingFeatures || invalidationSet == m_nthInvalidationSet) {
addFeaturesToInvalidationSet(*invalidationSet, descendantFeatures);
return;
}
SiblingInvalidationSet* siblingInvalidationSet =
toSiblingInvalidationSet(invalidationSet);
siblingInvalidationSet->updateMaxDirectAdjacentSelectors(
siblingFeatures->maxDirectAdjacentSelectors);
addFeaturesToInvalidationSet(*invalidationSet, *siblingFeatures);
if (siblingFeatures == &descendantFeatures)
siblingInvalidationSet->setInvalidatesSelf();
else
addFeaturesToInvalidationSet(
siblingInvalidationSet->ensureSiblingDescendants(),
descendantFeatures);
return;
}
if (simpleSelector.isHostPseudoClass())
descendantFeatures.treeBoundaryCrossing = true;
if (simpleSelector.isInsertionPointCrossing())
descendantFeatures.insertionPointCrossing = true;
addFeaturesToInvalidationSetsForSelectorList(simpleSelector, siblingFeatures,
descendantFeatures);
}
const CSSSelector*
RuleFeatureSet::addFeaturesToInvalidationSetsForCompoundSelector(
const CSSSelector& compound,
InvalidationSetFeatures* siblingFeatures,
InvalidationSetFeatures& descendantFeatures) {
bool compoundHasIdClassOrAttribute = false;
const CSSSelector* simpleSelector = &compound;
for (; simpleSelector; simpleSelector = simpleSelector->tagHistory()) {
addFeaturesToInvalidationSetsForSimpleSelector(
*simpleSelector, siblingFeatures, descendantFeatures);
if (simpleSelector->isIdClassOrAttributeSelector())
compoundHasIdClassOrAttribute = true;
if (simpleSelector->relation() != CSSSelector::SubSelector)
break;
if (!simpleSelector->tagHistory())
break;
}
if (compoundHasIdClassOrAttribute)
descendantFeatures.hasFeaturesForRuleSetInvalidation = true;
else if (siblingFeatures)
addFeaturesToUniversalSiblingInvalidationSet(*siblingFeatures,
descendantFeatures);
return simpleSelector;
}
void RuleFeatureSet::addFeaturesToInvalidationSets(
const CSSSelector& selector,
InvalidationSetFeatures* siblingFeatures,
InvalidationSetFeatures& descendantFeatures) {
// selector is the selector immediately to the left of the rightmost
// combinator. descendantFeatures has the features of the rightmost compound
// selector.
InvalidationSetFeatures lastCompoundInSiblingChainFeatures;
const CSSSelector* compound = &selector;
while (compound) {
const CSSSelector* lastInCompound =
addFeaturesToInvalidationSetsForCompoundSelector(
*compound, siblingFeatures, descendantFeatures);
DCHECK(lastInCompound);
updateFeaturesFromCombinator(*lastInCompound, compound,
lastCompoundInSiblingChainFeatures,
siblingFeatures, descendantFeatures);
compound = lastInCompound->tagHistory();
}
}
RuleFeatureSet::SelectorPreMatch RuleFeatureSet::collectFeaturesFromRuleData(
const RuleData& ruleData) {
FeatureMetadata metadata;
if (collectFeaturesFromSelector(ruleData.selector(), metadata) ==
SelectorNeverMatches)
return SelectorNeverMatches;
m_metadata.add(metadata);
if (metadata.foundSiblingSelector) {
m_siblingRules.append(RuleFeature(ruleData.rule(), ruleData.selectorIndex(),
ruleData.hasDocumentSecurityOrigin()));
}
if (ruleData.containsUncommonAttributeSelector()) {
m_uncommonAttributeRules.append(
RuleFeature(ruleData.rule(), ruleData.selectorIndex(),
ruleData.hasDocumentSecurityOrigin()));
}
updateInvalidationSets(ruleData);
return SelectorMayMatch;
}
RuleFeatureSet::SelectorPreMatch RuleFeatureSet::collectFeaturesFromSelector(
const CSSSelector& selector,
RuleFeatureSet::FeatureMetadata& metadata) {
unsigned maxDirectAdjacentSelectors = 0;
CSSSelector::RelationType relation = CSSSelector::Descendant;
bool foundHostPseudo = false;
for (const CSSSelector* current = &selector; current;
current = current->tagHistory()) {
switch (current->getPseudoType()) {
case CSSSelector::PseudoFirstLine:
metadata.usesFirstLineRules = true;
break;
case CSSSelector::PseudoWindowInactive:
metadata.usesWindowInactiveSelector = true;
break;
case CSSSelector::PseudoEmpty:
case CSSSelector::PseudoFirstChild:
case CSSSelector::PseudoFirstOfType:
case CSSSelector::PseudoLastChild:
case CSSSelector::PseudoLastOfType:
case CSSSelector::PseudoOnlyChild:
case CSSSelector::PseudoOnlyOfType:
case CSSSelector::PseudoNthChild:
case CSSSelector::PseudoNthOfType:
case CSSSelector::PseudoNthLastChild:
case CSSSelector::PseudoNthLastOfType:
if (!metadata.foundInsertionPointCrossing)
metadata.foundSiblingSelector = true;
break;
case CSSSelector::PseudoHost:
case CSSSelector::PseudoHostContext:
if (!foundHostPseudo && relation == CSSSelector::SubSelector)
return SelectorNeverMatches;
if (!current->isLastInTagHistory() &&
current->tagHistory()->match() != CSSSelector::PseudoElement &&
!current->tagHistory()->isHostPseudoClass()) {
return SelectorNeverMatches;
}
foundHostPseudo = true;
// fall through.
default:
if (const CSSSelectorList* selectorList = current->selectorList()) {
for (const CSSSelector* subSelector = selectorList->first();
subSelector; subSelector = CSSSelectorList::next(*subSelector))
collectFeaturesFromSelector(*subSelector, metadata);
}
break;
}
if (current->relationIsAffectedByPseudoContent() ||
current->getPseudoType() == CSSSelector::PseudoSlotted)
metadata.foundInsertionPointCrossing = true;
relation = current->relation();
if (foundHostPseudo && relation != CSSSelector::SubSelector)
return SelectorNeverMatches;
if (relation == CSSSelector::DirectAdjacent) {
maxDirectAdjacentSelectors++;
} else if (maxDirectAdjacentSelectors &&
((relation != CSSSelector::SubSelector) ||
current->isLastInTagHistory())) {
if (maxDirectAdjacentSelectors > metadata.maxDirectAdjacentSelectors)
metadata.maxDirectAdjacentSelectors = maxDirectAdjacentSelectors;
maxDirectAdjacentSelectors = 0;
}
if (!metadata.foundInsertionPointCrossing && current->isAdjacentSelector())
metadata.foundSiblingSelector = true;
}
DCHECK(!maxDirectAdjacentSelectors);
return SelectorMayMatch;
}
void RuleFeatureSet::FeatureMetadata::add(const FeatureMetadata& other) {
usesFirstLineRules |= other.usesFirstLineRules;
usesWindowInactiveSelector |= other.usesWindowInactiveSelector;
maxDirectAdjacentSelectors =
std::max(maxDirectAdjacentSelectors, other.maxDirectAdjacentSelectors);
}
void RuleFeatureSet::FeatureMetadata::clear() {
usesFirstLineRules = false;
usesWindowInactiveSelector = false;
foundSiblingSelector = false;
foundInsertionPointCrossing = false;
needsFullRecalcForRuleSetInvalidation = false;
maxDirectAdjacentSelectors = 0;
}
void RuleFeatureSet::add(const RuleFeatureSet& other) {
DCHECK(&other != this);
for (const auto& entry : other.m_classInvalidationSets)
ensureInvalidationSet(m_classInvalidationSets, entry.key,
entry.value->type())
.combine(*entry.value);
for (const auto& entry : other.m_attributeInvalidationSets)
ensureInvalidationSet(m_attributeInvalidationSets, entry.key,
entry.value->type())
.combine(*entry.value);
for (const auto& entry : other.m_idInvalidationSets)
ensureInvalidationSet(m_idInvalidationSets, entry.key, entry.value->type())
.combine(*entry.value);
for (const auto& entry : other.m_pseudoInvalidationSets)
ensureInvalidationSet(m_pseudoInvalidationSets,
static_cast<CSSSelector::PseudoType>(entry.key),
entry.value->type())
.combine(*entry.value);
if (other.m_universalSiblingInvalidationSet)
ensureUniversalSiblingInvalidationSet().combine(
*other.m_universalSiblingInvalidationSet);
if (other.m_nthInvalidationSet)
ensureNthInvalidationSet().combine(*other.m_nthInvalidationSet);
m_metadata.add(other.m_metadata);
m_siblingRules.appendVector(other.m_siblingRules);
m_uncommonAttributeRules.appendVector(other.m_uncommonAttributeRules);
m_viewportDependentMediaQueryResults.appendVector(
other.m_viewportDependentMediaQueryResults);
m_deviceDependentMediaQueryResults.appendVector(
other.m_deviceDependentMediaQueryResults);
}
void RuleFeatureSet::clear() {
m_siblingRules.clear();
m_uncommonAttributeRules.clear();
m_metadata.clear();
m_classInvalidationSets.clear();
m_attributeInvalidationSets.clear();
m_idInvalidationSets.clear();
m_pseudoInvalidationSets.clear();
m_universalSiblingInvalidationSet.clear();
m_nthInvalidationSet.clear();
m_viewportDependentMediaQueryResults.clear();
m_deviceDependentMediaQueryResults.clear();
}
void RuleFeatureSet::collectInvalidationSetsForClass(
InvalidationLists& invalidationLists,
Element& element,
const AtomicString& className) const {
InvalidationSetMap::const_iterator it =
m_classInvalidationSets.find(className);
if (it == m_classInvalidationSets.end())
return;
DescendantInvalidationSet* descendants;
SiblingInvalidationSet* siblings;
extractInvalidationSets(it->value.get(), descendants, siblings);
if (descendants) {
TRACE_SCHEDULE_STYLE_INVALIDATION(element, *descendants, classChange,
className);
invalidationLists.descendants.append(descendants);
}
if (siblings) {
TRACE_SCHEDULE_STYLE_INVALIDATION(element, *siblings, classChange,
className);
invalidationLists.siblings.append(siblings);
}
}
void RuleFeatureSet::collectSiblingInvalidationSetForClass(
InvalidationLists& invalidationLists,
Element& element,
const AtomicString& className,
unsigned minDirectAdjacent) const {
InvalidationSetMap::const_iterator it =
m_classInvalidationSets.find(className);
if (it == m_classInvalidationSets.end())
return;
InvalidationSet* invalidationSet = it->value.get();
if (invalidationSet->type() == InvalidateDescendants)
return;
SiblingInvalidationSet* siblingSet =
toSiblingInvalidationSet(invalidationSet);
if (siblingSet->maxDirectAdjacentSelectors() < minDirectAdjacent)
return;
TRACE_SCHEDULE_STYLE_INVALIDATION(element, *siblingSet, classChange,
className);
invalidationLists.siblings.append(siblingSet);
}
void RuleFeatureSet::collectInvalidationSetsForId(
InvalidationLists& invalidationLists,
Element& element,
const AtomicString& id) const {
InvalidationSetMap::const_iterator it = m_idInvalidationSets.find(id);
if (it == m_idInvalidationSets.end())
return;
DescendantInvalidationSet* descendants;
SiblingInvalidationSet* siblings;
extractInvalidationSets(it->value.get(), descendants, siblings);
if (descendants) {
TRACE_SCHEDULE_STYLE_INVALIDATION(element, *descendants, idChange, id);
invalidationLists.descendants.append(descendants);
}
if (siblings) {
TRACE_SCHEDULE_STYLE_INVALIDATION(element, *siblings, idChange, id);
invalidationLists.siblings.append(siblings);
}
}
void RuleFeatureSet::collectSiblingInvalidationSetForId(
InvalidationLists& invalidationLists,
Element& element,
const AtomicString& id,
unsigned minDirectAdjacent) const {
InvalidationSetMap::const_iterator it = m_idInvalidationSets.find(id);
if (it == m_idInvalidationSets.end())
return;
InvalidationSet* invalidationSet = it->value.get();
if (invalidationSet->type() == InvalidateDescendants)
return;
SiblingInvalidationSet* siblingSet =
toSiblingInvalidationSet(invalidationSet);
if (siblingSet->maxDirectAdjacentSelectors() < minDirectAdjacent)
return;
TRACE_SCHEDULE_STYLE_INVALIDATION(element, *siblingSet, idChange, id);
invalidationLists.siblings.append(siblingSet);
}
void RuleFeatureSet::collectInvalidationSetsForAttribute(
InvalidationLists& invalidationLists,
Element& element,
const QualifiedName& attributeName) const {
InvalidationSetMap::const_iterator it =
m_attributeInvalidationSets.find(attributeName.localName());
if (it == m_attributeInvalidationSets.end())
return;
DescendantInvalidationSet* descendants;
SiblingInvalidationSet* siblings;
extractInvalidationSets(it->value.get(), descendants, siblings);
if (descendants) {
TRACE_SCHEDULE_STYLE_INVALIDATION(element, *descendants, attributeChange,
attributeName);
invalidationLists.descendants.append(descendants);
}
if (siblings) {
TRACE_SCHEDULE_STYLE_INVALIDATION(element, *siblings, attributeChange,
attributeName);
invalidationLists.siblings.append(siblings);
}
}
void RuleFeatureSet::collectSiblingInvalidationSetForAttribute(
InvalidationLists& invalidationLists,
Element& element,
const QualifiedName& attributeName,
unsigned minDirectAdjacent) const {
InvalidationSetMap::const_iterator it =
m_attributeInvalidationSets.find(attributeName.localName());
if (it == m_attributeInvalidationSets.end())
return;
InvalidationSet* invalidationSet = it->value.get();
if (invalidationSet->type() == InvalidateDescendants)
return;
SiblingInvalidationSet* siblingSet =
toSiblingInvalidationSet(invalidationSet);
if (siblingSet->maxDirectAdjacentSelectors() < minDirectAdjacent)
return;
TRACE_SCHEDULE_STYLE_INVALIDATION(element, *siblingSet, attributeChange,
attributeName);
invalidationLists.siblings.append(siblingSet);
}
void RuleFeatureSet::collectInvalidationSetsForPseudoClass(
InvalidationLists& invalidationLists,
Element& element,
CSSSelector::PseudoType pseudo) const {
PseudoTypeInvalidationSetMap::const_iterator it =
m_pseudoInvalidationSets.find(pseudo);
if (it == m_pseudoInvalidationSets.end())
return;
DescendantInvalidationSet* descendants;
SiblingInvalidationSet* siblings;
extractInvalidationSets(it->value.get(), descendants, siblings);
if (descendants) {
TRACE_SCHEDULE_STYLE_INVALIDATION(element, *descendants, pseudoChange,
pseudo);
invalidationLists.descendants.append(descendants);
}
if (siblings) {
TRACE_SCHEDULE_STYLE_INVALIDATION(element, *siblings, pseudoChange, pseudo);
invalidationLists.siblings.append(siblings);
}
}
void RuleFeatureSet::collectUniversalSiblingInvalidationSet(
InvalidationLists& invalidationLists,
unsigned minDirectAdjacent) const {
if (m_universalSiblingInvalidationSet &&
m_universalSiblingInvalidationSet->maxDirectAdjacentSelectors() >=
minDirectAdjacent)
invalidationLists.siblings.append(m_universalSiblingInvalidationSet);
}
SiblingInvalidationSet&
RuleFeatureSet::ensureUniversalSiblingInvalidationSet() {
if (!m_universalSiblingInvalidationSet)
m_universalSiblingInvalidationSet = SiblingInvalidationSet::create(nullptr);
return *m_universalSiblingInvalidationSet;
}
void RuleFeatureSet::collectNthInvalidationSet(
InvalidationLists& invalidationLists) const {
if (m_nthInvalidationSet)
invalidationLists.descendants.append(m_nthInvalidationSet);
}
DescendantInvalidationSet& RuleFeatureSet::ensureNthInvalidationSet() {
if (!m_nthInvalidationSet)
m_nthInvalidationSet = DescendantInvalidationSet::create();
return *m_nthInvalidationSet;
}
void RuleFeatureSet::addFeaturesToUniversalSiblingInvalidationSet(
const InvalidationSetFeatures& siblingFeatures,
const InvalidationSetFeatures& descendantFeatures) {
SiblingInvalidationSet& universalSet =
ensureUniversalSiblingInvalidationSet();
addFeaturesToInvalidationSet(universalSet, siblingFeatures);
universalSet.updateMaxDirectAdjacentSelectors(
siblingFeatures.maxDirectAdjacentSelectors);
if (&siblingFeatures == &descendantFeatures)
universalSet.setInvalidatesSelf();
else
addFeaturesToInvalidationSet(universalSet.ensureSiblingDescendants(),
descendantFeatures);
}
DEFINE_TRACE(RuleFeatureSet) {
visitor->trace(m_siblingRules);
visitor->trace(m_uncommonAttributeRules);
visitor->trace(m_viewportDependentMediaQueryResults);
visitor->trace(m_deviceDependentMediaQueryResults);
}
void RuleFeatureSet::InvalidationSetFeatures::add(
const InvalidationSetFeatures& other) {
classes.appendVector(other.classes);
attributes.appendVector(other.attributes);
ids.appendVector(other.ids);
tagNames.appendVector(other.tagNames);
maxDirectAdjacentSelectors =
std::max(maxDirectAdjacentSelectors, other.maxDirectAdjacentSelectors);
customPseudoElement |= other.customPseudoElement;
hasBeforeOrAfter |= other.hasBeforeOrAfter;
treeBoundaryCrossing |= other.treeBoundaryCrossing;
insertionPointCrossing |= other.insertionPointCrossing;
forceSubtree |= other.forceSubtree;
contentPseudoCrossing |= other.contentPseudoCrossing;
invalidatesSlotted |= other.invalidatesSlotted;
hasNthPseudo |= other.hasNthPseudo;
}
bool RuleFeatureSet::InvalidationSetFeatures::hasFeatures() const {
return !classes.isEmpty() || !attributes.isEmpty() || !ids.isEmpty() ||
!tagNames.isEmpty() || customPseudoElement;
}
bool RuleFeatureSet::InvalidationSetFeatures::hasTagIdClassOrAttribute() const {
return !classes.isEmpty() || !attributes.isEmpty() || !ids.isEmpty() ||
!tagNames.isEmpty();
}
} // namespace blink