| /* |
| * 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/ComputedStyle.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().push_back(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()->push_back(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.push_back( |
| 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 |