blob: 177cc70d653cac2decf9219af353561abb951b37 [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/dom/StyleEngine.h"
#include "core/dom/shadow/ShadowRoot.h"
#include "core/style/StyleInheritedData.h"
#include <algorithm>
namespace blink {
ElementRuleCollector::ElementRuleCollector(const ElementResolveContext& context,
const SelectorFilter& filter, ComputedStyle* style)
: m_context(context)
, m_selectorFilter(filter)
, m_style(style)
, m_pseudoStyleRequest(NOPSEUDO)
, 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;
}
PassRefPtrWillBeRawPtr<StyleRuleList> ElementRuleCollector::matchedStyleRuleList()
{
ASSERT(m_mode == SelectorChecker::CollectingStyleRules);
return m_styleRuleList.release();
}
PassRefPtrWillBeRawPtr<CSSRuleList> ElementRuleCollector::matchedCSSRuleList()
{
ASSERT(m_mode == SelectorChecker::CollectingCSSRules);
return m_cssRuleList.release();
}
void ElementRuleCollector::clearMatchedRules()
{
m_matchedRules.clear();
}
inline StyleRuleList* ElementRuleCollector::ensureStyleRuleList()
{
#if ENABLE(OILPAN)
if (!m_styleRuleList)
m_styleRuleList = new StyleRuleList();
return m_styleRuleList;
#else
if (!m_styleRuleList)
m_styleRuleList = StyleRuleList::create();
return m_styleRuleList.get();
#endif
}
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->treeScope() == scopingNode->treeScope();
}
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.
const StylePropertySet& properties = rule->properties();
if (properties.isEmpty() && !m_includeEmptyRules)
continue;
SelectorChecker::MatchResult result;
context.selector = &ruleData.selector();
if (!checker.match(context, result)) {
rejected++;
continue;
}
if (m_pseudoStyleRequest.pseudoId != NOPSEUDO && 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);
}
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 (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::STYLE_RULE) {
CSSStyleRule* cssStyleRule = toCSSStyleRule(cssRule);
if (cssStyleRule->styleRule() == styleRule)
result = cssRule;
} else if (cssRuleType == CSSRule::IMPORT_RULE) {
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.
RefPtrWillBeRawPtr<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 != NOPSEUDO && m_pseudoStyleRequest.pseudoId == NOPSEUDO) {
if (m_mode == SelectorChecker::CollectingCSSRules || m_mode == SelectorChecker::CollectingStyleRules)
return;
// FIXME: Matching should not modify the style directly.
if (!m_style || dynamicPseudo >= FIRST_INTERNAL_PSEUDOID)
return;
if ((dynamicPseudo == BEFORE || dynamicPseudo == AFTER) && !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();
}
} // namespace blink