blob: aa8c8e418c2cce763e0062119bce7d2afdc7f63a [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, 2013 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/ElementRuleCollector.h"
#include "core/css/CSSImportRule.h"
#include "core/css/CSSKeyframesRule.h"
#include "core/css/CSSMediaRule.h"
#include "core/css/CSSRuleList.h"
#include "core/css/CSSSelector.h"
#include "core/css/CSSStyleRule.h"
#include "core/css/CSSStyleSheet.h"
#include "core/css/CSSSupportsRule.h"
#include "core/css/StylePropertySet.h"
#include "core/css/resolver/StyleResolver.h"
#include "core/css/resolver/StyleResolverStats.h"
#include "core/css/resolver/StyleRuleUsageTracker.h"
#include "core/dom/StyleEngine.h"
#include "core/dom/shadow/ShadowRoot.h"
#include "core/style/StyleInheritedData.h"
namespace blink {
ElementRuleCollector::ElementRuleCollector(const ElementResolveContext& context,
const SelectorFilter& filter,
ComputedStyle* style)
: m_context(context),
m_selectorFilter(filter),
m_style(style),
m_pseudoStyleRequest(PseudoIdNone),
m_mode(SelectorChecker::ResolvingStyle),
m_canUseFastReject(
m_selectorFilter.parentStackIsConsistent(context.parentNode())),
m_sameOriginOnly(false),
m_matchingUARules(false),
m_includeEmptyRules(false) {}
ElementRuleCollector::~ElementRuleCollector() {}
const MatchResult& ElementRuleCollector::matchedResult() const {
return m_result;
}
StyleRuleList* ElementRuleCollector::matchedStyleRuleList() {
ASSERT(m_mode == SelectorChecker::CollectingStyleRules);
return m_styleRuleList.release();
}
CSSRuleList* ElementRuleCollector::matchedCSSRuleList() {
ASSERT(m_mode == SelectorChecker::CollectingCSSRules);
return m_cssRuleList.release();
}
void ElementRuleCollector::clearMatchedRules() {
m_matchedRules.clear();
}
inline StyleRuleList* ElementRuleCollector::ensureStyleRuleList() {
if (!m_styleRuleList)
m_styleRuleList = new StyleRuleList();
return m_styleRuleList;
}
inline StaticCSSRuleList* ElementRuleCollector::ensureRuleList() {
if (!m_cssRuleList)
m_cssRuleList = StaticCSSRuleList::create();
return m_cssRuleList.get();
}
void ElementRuleCollector::addElementStyleProperties(
const StylePropertySet* propertySet,
bool isCacheable) {
if (!propertySet)
return;
m_result.addMatchedProperties(propertySet);
if (!isCacheable)
m_result.setIsCacheable(false);
}
static bool rulesApplicableInCurrentTreeScope(
const Element* element,
const ContainerNode* scopingNode) {
// Check if the rules come from a shadow style sheet in the same tree scope.
return !scopingNode ||
element->containingTreeScope() == scopingNode->containingTreeScope();
}
template <typename RuleDataListType>
void ElementRuleCollector::collectMatchingRulesForList(
const RuleDataListType* rules,
CascadeOrder cascadeOrder,
const MatchRequest& matchRequest) {
if (!rules)
return;
SelectorChecker::Init init;
init.mode = m_mode;
init.isUARule = m_matchingUARules;
init.elementStyle = m_style.get();
init.scrollbar = m_pseudoStyleRequest.scrollbar;
init.scrollbarPart = m_pseudoStyleRequest.scrollbarPart;
SelectorChecker checker(init);
SelectorChecker::SelectorCheckingContext context(
m_context.element(), SelectorChecker::VisitedMatchEnabled);
context.scope = matchRequest.scope;
context.pseudoId = m_pseudoStyleRequest.pseudoId;
unsigned rejected = 0;
unsigned fastRejected = 0;
unsigned matched = 0;
for (const auto& ruleData : *rules) {
if (m_canUseFastReject &&
m_selectorFilter.fastRejectSelector<RuleData::maximumIdentifierCount>(
ruleData.descendantSelectorIdentifierHashes())) {
fastRejected++;
continue;
}
// FIXME: Exposing the non-standard getMatchedCSSRules API to web is the
// only reason this is needed.
if (m_sameOriginOnly && !ruleData.hasDocumentSecurityOrigin())
continue;
StyleRule* rule = ruleData.rule();
// If the rule has no properties to apply, then ignore it in the non-debug
// mode.
if (!rule->shouldConsiderForMatchingRules(m_includeEmptyRules))
continue;
SelectorChecker::MatchResult result;
context.selector = &ruleData.selector();
if (!checker.match(context, result)) {
rejected++;
continue;
}
if (m_pseudoStyleRequest.pseudoId != PseudoIdNone &&
m_pseudoStyleRequest.pseudoId != result.dynamicPseudo) {
rejected++;
continue;
}
matched++;
didMatchRule(ruleData, result, cascadeOrder, matchRequest);
}
StyleEngine& styleEngine = m_context.element()->document().styleEngine();
if (!styleEngine.stats())
return;
INCREMENT_STYLE_STATS_COUNTER(styleEngine, rulesRejected, rejected);
INCREMENT_STYLE_STATS_COUNTER(styleEngine, rulesFastRejected, fastRejected);
INCREMENT_STYLE_STATS_COUNTER(styleEngine, rulesMatched, matched);
}
DISABLE_CFI_PERF
void ElementRuleCollector::collectMatchingRules(
const MatchRequest& matchRequest,
CascadeOrder cascadeOrder,
bool matchingTreeBoundaryRules) {
ASSERT(matchRequest.ruleSet);
ASSERT(m_context.element());
Element& element = *m_context.element();
const AtomicString& pseudoId = element.shadowPseudoId();
if (!pseudoId.isEmpty()) {
ASSERT(element.isStyledElement());
collectMatchingRulesForList(
matchRequest.ruleSet->shadowPseudoElementRules(pseudoId), cascadeOrder,
matchRequest);
if (pseudoId == "-webkit-input-placeholder") {
collectMatchingRulesForList(
matchRequest.ruleSet->placeholderPseudoRules(), cascadeOrder,
matchRequest);
}
}
if (element.isVTTElement())
collectMatchingRulesForList(matchRequest.ruleSet->cuePseudoRules(),
cascadeOrder, matchRequest);
// Check whether other types of rules are applicable in the current tree
// scope. Criteria for this:
// a) the rules are UA rules.
// b) matching tree boundary crossing rules.
// c) the rules come from a shadow style sheet in the same tree scope as the
// given element.
// c) is checked in rulesApplicableInCurrentTreeScope.
if (!m_matchingUARules && !matchingTreeBoundaryRules &&
!rulesApplicableInCurrentTreeScope(&element, matchRequest.scope))
return;
// We need to collect the rules for id, class, tag, and everything else into a
// buffer and then sort the buffer.
if (element.hasID())
collectMatchingRulesForList(
matchRequest.ruleSet->idRules(element.idForStyleResolution()),
cascadeOrder, matchRequest);
if (element.isStyledElement() && element.hasClass()) {
for (size_t i = 0; i < element.classNames().size(); ++i)
collectMatchingRulesForList(
matchRequest.ruleSet->classRules(element.classNames()[i]),
cascadeOrder, matchRequest);
}
if (element.isLink())
collectMatchingRulesForList(matchRequest.ruleSet->linkPseudoClassRules(),
cascadeOrder, matchRequest);
if (SelectorChecker::matchesFocusPseudoClass(element))
collectMatchingRulesForList(matchRequest.ruleSet->focusPseudoClassRules(),
cascadeOrder, matchRequest);
collectMatchingRulesForList(
matchRequest.ruleSet->tagRules(element.localNameForSelectorMatching()),
cascadeOrder, matchRequest);
collectMatchingRulesForList(matchRequest.ruleSet->universalRules(),
cascadeOrder, matchRequest);
}
void ElementRuleCollector::collectMatchingShadowHostRules(
const MatchRequest& matchRequest,
CascadeOrder cascadeOrder) {
collectMatchingRulesForList(matchRequest.ruleSet->shadowHostRules(),
cascadeOrder, matchRequest);
}
template <class CSSRuleCollection>
CSSRule* ElementRuleCollector::findStyleRule(CSSRuleCollection* cssRules,
StyleRule* styleRule) {
if (!cssRules)
return nullptr;
CSSRule* result = 0;
for (unsigned i = 0; i < cssRules->length() && !result; ++i) {
CSSRule* cssRule = cssRules->item(i);
CSSRule::Type cssRuleType = cssRule->type();
if (cssRuleType == CSSRule::kStyleRule) {
CSSStyleRule* cssStyleRule = toCSSStyleRule(cssRule);
if (cssStyleRule->styleRule() == styleRule)
result = cssRule;
} else if (cssRuleType == CSSRule::kImportRule) {
CSSImportRule* cssImportRule = toCSSImportRule(cssRule);
result = findStyleRule(cssImportRule->styleSheet(), styleRule);
} else {
result = findStyleRule(cssRule->cssRules(), styleRule);
}
}
return result;
}
void ElementRuleCollector::appendCSSOMWrapperForRule(
CSSStyleSheet* parentStyleSheet,
StyleRule* rule) {
// |parentStyleSheet| is 0 if and only if the |rule| is coming from User
// Agent. In this case, it is safe to create CSSOM wrappers without
// parentStyleSheets as they will be used only by inspector which will not try
// to edit them.
CSSRule* cssRule = nullptr;
if (parentStyleSheet)
cssRule = findStyleRule(parentStyleSheet, rule);
else
cssRule = rule->createCSSOMWrapper();
ASSERT(!parentStyleSheet || cssRule);
ensureRuleList()->rules().append(cssRule);
}
void ElementRuleCollector::sortAndTransferMatchedRules() {
if (m_matchedRules.isEmpty())
return;
sortMatchedRules();
if (m_mode == SelectorChecker::CollectingStyleRules) {
for (unsigned i = 0; i < m_matchedRules.size(); ++i)
ensureStyleRuleList()->append(m_matchedRules[i].ruleData()->rule());
return;
}
if (m_mode == SelectorChecker::CollectingCSSRules) {
for (unsigned i = 0; i < m_matchedRules.size(); ++i)
appendCSSOMWrapperForRule(
const_cast<CSSStyleSheet*>(m_matchedRules[i].parentStyleSheet()),
m_matchedRules[i].ruleData()->rule());
return;
}
// Now transfer the set of matched rules over to our list of declarations.
for (unsigned i = 0; i < m_matchedRules.size(); i++) {
const RuleData* ruleData = m_matchedRules[i].ruleData();
m_result.addMatchedProperties(
&ruleData->rule()->properties(), ruleData->linkMatchType(),
ruleData->propertyWhitelist(m_matchingUARules));
}
}
void ElementRuleCollector::didMatchRule(
const RuleData& ruleData,
const SelectorChecker::MatchResult& result,
CascadeOrder cascadeOrder,
const MatchRequest& matchRequest) {
PseudoId dynamicPseudo = result.dynamicPseudo;
// If we're matching normal rules, set a pseudo bit if we really just matched
// a pseudo-element.
if (dynamicPseudo != PseudoIdNone &&
m_pseudoStyleRequest.pseudoId == PseudoIdNone) {
if (m_mode == SelectorChecker::CollectingCSSRules ||
m_mode == SelectorChecker::CollectingStyleRules)
return;
// FIXME: Matching should not modify the style directly.
if (!m_style || dynamicPseudo >= FirstInternalPseudoId)
return;
if ((dynamicPseudo == PseudoIdBefore || dynamicPseudo == PseudoIdAfter) &&
!ruleData.rule()->properties().hasProperty(CSSPropertyContent))
return;
m_style->setHasPseudoStyle(dynamicPseudo);
} else {
if (m_style && ruleData.containsUncommonAttributeSelector())
m_style->setUnique();
m_matchedRules.append(
MatchedRule(&ruleData, result.specificity, cascadeOrder,
matchRequest.styleSheetIndex, matchRequest.styleSheet));
}
}
static inline bool compareRules(const MatchedRule& matchedRule1,
const MatchedRule& matchedRule2) {
unsigned specificity1 = matchedRule1.specificity();
unsigned specificity2 = matchedRule2.specificity();
if (specificity1 != specificity2)
return specificity1 < specificity2;
return matchedRule1.position() < matchedRule2.position();
}
void ElementRuleCollector::sortMatchedRules() {
std::sort(m_matchedRules.begin(), m_matchedRules.end(), compareRules);
}
bool ElementRuleCollector::hasAnyMatchingRules(RuleSet* ruleSet) {
clearMatchedRules();
m_mode = SelectorChecker::SharingRules;
// To check whether a given RuleSet has any rule matching a given element,
// should not see the element's treescope. Because RuleSet has no information
// about "scope".
MatchRequest matchRequest(ruleSet);
collectMatchingRules(matchRequest);
collectMatchingShadowHostRules(matchRequest);
return !m_matchedRules.isEmpty();
}
void ElementRuleCollector::addMatchedRulesToTracker(
StyleRuleUsageTracker* tracker) const {
for (auto matchedRule : m_matchedRules)
tracker->track(matchedRule.ruleData()->rule());
}
} // namespace blink