Home | History | Annotate | Download | only in css
      1 /*
      2  * Copyright (C) 1999 Lars Knoll (knoll (at) kde.org)
      3  *           (C) 2004-2005 Allan Sandfeld Jensen (kde (at) carewolf.com)
      4  * Copyright (C) 2006, 2007 Nicholas Shanks (webkit (at) nickshanks.com)
      5  * Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 Apple Inc. All rights reserved.
      6  * Copyright (C) 2007 Alexey Proskuryakov <ap (at) webkit.org>
      7  * Copyright (C) 2007, 2008 Eric Seidel <eric (at) webkit.org>
      8  * Copyright (C) 2008, 2009 Torch Mobile Inc. All rights reserved. (http://www.torchmobile.com/)
      9  * Copyright (c) 2011, Code Aurora Forum. All rights reserved.
     10  * Copyright (C) Research In Motion Limited 2011. All rights reserved.
     11  * Copyright (C) 2012 Google Inc. All rights reserved.
     12  *
     13  * This library is free software; you can redistribute it and/or
     14  * modify it under the terms of the GNU Library General Public
     15  * License as published by the Free Software Foundation; either
     16  * version 2 of the License, or (at your option) any later version.
     17  *
     18  * This library is distributed in the hope that it will be useful,
     19  * but WITHOUT ANY WARRANTY; without even the implied warranty of
     20  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
     21  * Library General Public License for more details.
     22  *
     23  * You should have received a copy of the GNU Library General Public License
     24  * along with this library; see the file COPYING.LIB.  If not, write to
     25  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
     26  * Boston, MA 02110-1301, USA.
     27  */
     28 
     29 #include "config.h"
     30 #include "core/css/ElementRuleCollector.h"
     31 
     32 #include "core/css/CSSImportRule.h"
     33 #include "core/css/CSSKeyframesRule.h"
     34 #include "core/css/CSSMediaRule.h"
     35 #include "core/css/CSSRuleList.h"
     36 #include "core/css/CSSSelector.h"
     37 #include "core/css/CSSStyleRule.h"
     38 #include "core/css/CSSStyleSheet.h"
     39 #include "core/css/CSSSupportsRule.h"
     40 #include "core/css/SiblingTraversalStrategies.h"
     41 #include "core/css/StylePropertySet.h"
     42 #include "core/css/resolver/StyleResolver.h"
     43 #include "core/dom/shadow/ShadowRoot.h"
     44 #include "core/rendering/style/StyleInheritedData.h"
     45 
     46 namespace WebCore {
     47 
     48 ElementRuleCollector::ElementRuleCollector(const ElementResolveContext& context,
     49     const SelectorFilter& filter, RenderStyle* style)
     50     : m_context(context)
     51     , m_selectorFilter(filter)
     52     , m_style(style)
     53     , m_pseudoStyleRequest(NOPSEUDO)
     54     , m_mode(SelectorChecker::ResolvingStyle)
     55     , m_canUseFastReject(m_selectorFilter.parentStackIsConsistent(context.parentNode()))
     56     , m_sameOriginOnly(false)
     57     , m_matchingUARules(false)
     58 { }
     59 
     60 ElementRuleCollector::~ElementRuleCollector()
     61 {
     62 }
     63 
     64 MatchResult& ElementRuleCollector::matchedResult()
     65 {
     66     return m_result;
     67 }
     68 
     69 PassRefPtrWillBeRawPtr<StyleRuleList> ElementRuleCollector::matchedStyleRuleList()
     70 {
     71     ASSERT(m_mode == SelectorChecker::CollectingStyleRules);
     72     return m_styleRuleList.release();
     73 }
     74 
     75 PassRefPtrWillBeRawPtr<CSSRuleList> ElementRuleCollector::matchedCSSRuleList()
     76 {
     77     ASSERT(m_mode == SelectorChecker::CollectingCSSRules);
     78     return m_cssRuleList.release();
     79 }
     80 
     81 inline void ElementRuleCollector::addMatchedRule(const RuleData* rule, unsigned specificity, CascadeScope cascadeScope, CascadeOrder cascadeOrder, unsigned styleSheetIndex, const CSSStyleSheet* parentStyleSheet)
     82 {
     83     if (!m_matchedRules)
     84         m_matchedRules = adoptPtrWillBeNoop(new WillBeHeapVector<MatchedRule, 32>);
     85     m_matchedRules->append(MatchedRule(rule, specificity, cascadeScope, cascadeOrder, styleSheetIndex, parentStyleSheet));
     86 }
     87 
     88 void ElementRuleCollector::clearMatchedRules()
     89 {
     90     if (!m_matchedRules)
     91         return;
     92     m_matchedRules->clear();
     93 }
     94 
     95 inline StyleRuleList* ElementRuleCollector::ensureStyleRuleList()
     96 {
     97     if (!m_styleRuleList)
     98         m_styleRuleList = StyleRuleList::create();
     99     return m_styleRuleList.get();
    100 }
    101 
    102 inline StaticCSSRuleList* ElementRuleCollector::ensureRuleList()
    103 {
    104     if (!m_cssRuleList)
    105         m_cssRuleList = StaticCSSRuleList::create();
    106     return m_cssRuleList.get();
    107 }
    108 
    109 void ElementRuleCollector::addElementStyleProperties(const StylePropertySet* propertySet, bool isCacheable)
    110 {
    111     if (!propertySet)
    112         return;
    113     m_result.ranges.lastAuthorRule = m_result.matchedProperties.size();
    114     if (m_result.ranges.firstAuthorRule == -1)
    115         m_result.ranges.firstAuthorRule = m_result.ranges.lastAuthorRule;
    116     m_result.addMatchedProperties(propertySet);
    117     if (!isCacheable)
    118         m_result.isCacheable = false;
    119 }
    120 
    121 static bool rulesApplicableInCurrentTreeScope(const Element* element, const ContainerNode* scopingNode, SelectorChecker::BehaviorAtBoundary behaviorAtBoundary, bool elementApplyAuthorStyles)
    122 {
    123     TreeScope& treeScope = element->treeScope();
    124 
    125     // [skipped, because already checked] a) it's a UA rule
    126     // b) element is allowed to apply author rules
    127     if (elementApplyAuthorStyles)
    128         return true;
    129     // c) the rules comes from a scoped style sheet within the same tree scope
    130     if (!scopingNode || treeScope == scopingNode->treeScope())
    131         return true;
    132     // d) the rules comes from a scoped style sheet within an active shadow root whose host is the given element
    133     if (SelectorChecker::isHostInItsShadowTree(*element, behaviorAtBoundary, scopingNode))
    134         return true;
    135     return false;
    136 }
    137 
    138 void ElementRuleCollector::collectMatchingRules(const MatchRequest& matchRequest, RuleRange& ruleRange, SelectorChecker::BehaviorAtBoundary behaviorAtBoundary, CascadeScope cascadeScope, CascadeOrder cascadeOrder)
    139 {
    140     ASSERT(matchRequest.ruleSet);
    141     ASSERT(m_context.element());
    142 
    143     Element& element = *m_context.element();
    144     const AtomicString& pseudoId = element.shadowPseudoId();
    145     if (!pseudoId.isEmpty()) {
    146         ASSERT(element.isStyledElement());
    147         collectMatchingRulesForList(matchRequest.ruleSet->shadowPseudoElementRules(pseudoId), behaviorAtBoundary, ignoreCascadeScope, cascadeOrder, matchRequest, ruleRange);
    148     }
    149 
    150     if (element.isVTTElement())
    151         collectMatchingRulesForList(matchRequest.ruleSet->cuePseudoRules(), behaviorAtBoundary, cascadeScope, cascadeOrder, matchRequest, ruleRange);
    152     // Check whether other types of rules are applicable in the current tree scope. Criteria for this:
    153     // a) it's a UA rule
    154     // b) the tree scope allows author rules
    155     // c) the rules comes from a scoped style sheet within the same tree scope
    156     // d) the rules comes from a scoped style sheet within an active shadow root whose host is the given element
    157     // e) the rules can cross boundaries
    158     // b)-e) is checked in rulesApplicableInCurrentTreeScope.
    159     if (!m_matchingUARules && !rulesApplicableInCurrentTreeScope(&element, matchRequest.scope, behaviorAtBoundary, matchRequest.elementApplyAuthorStyles))
    160         return;
    161 
    162     // We need to collect the rules for id, class, tag, and everything else into a buffer and
    163     // then sort the buffer.
    164     if (element.hasID())
    165         collectMatchingRulesForList(matchRequest.ruleSet->idRules(element.idForStyleResolution()), behaviorAtBoundary, cascadeScope, cascadeOrder, matchRequest, ruleRange);
    166     if (element.isStyledElement() && element.hasClass()) {
    167         for (size_t i = 0; i < element.classNames().size(); ++i)
    168             collectMatchingRulesForList(matchRequest.ruleSet->classRules(element.classNames()[i]), behaviorAtBoundary, cascadeScope, cascadeOrder, matchRequest, ruleRange);
    169     }
    170 
    171     if (element.isLink())
    172         collectMatchingRulesForList(matchRequest.ruleSet->linkPseudoClassRules(), behaviorAtBoundary, cascadeScope, cascadeOrder, matchRequest, ruleRange);
    173     if (SelectorChecker::matchesFocusPseudoClass(element))
    174         collectMatchingRulesForList(matchRequest.ruleSet->focusPseudoClassRules(), behaviorAtBoundary, cascadeScope, cascadeOrder, matchRequest, ruleRange);
    175     collectMatchingRulesForList(matchRequest.ruleSet->tagRules(element.localName()), behaviorAtBoundary, cascadeScope, cascadeOrder, matchRequest, ruleRange);
    176     collectMatchingRulesForList(matchRequest.ruleSet->universalRules(), behaviorAtBoundary, cascadeScope, cascadeOrder, matchRequest, ruleRange);
    177 }
    178 
    179 CSSRuleList* ElementRuleCollector::nestedRuleList(CSSRule* rule)
    180 {
    181     switch (rule->type()) {
    182     case CSSRule::MEDIA_RULE:
    183         return toCSSMediaRule(rule)->cssRules();
    184     case CSSRule::KEYFRAMES_RULE:
    185         return toCSSKeyframesRule(rule)->cssRules();
    186     case CSSRule::SUPPORTS_RULE:
    187         return toCSSSupportsRule(rule)->cssRules();
    188     default:
    189         return 0;
    190     }
    191 }
    192 
    193 template<class CSSRuleCollection>
    194 CSSRule* ElementRuleCollector::findStyleRule(CSSRuleCollection* cssRules, StyleRule* styleRule)
    195 {
    196     if (!cssRules)
    197         return 0;
    198     CSSRule* result = 0;
    199     for (unsigned i = 0; i < cssRules->length() && !result; ++i) {
    200         CSSRule* cssRule = cssRules->item(i);
    201         CSSRule::Type cssRuleType = cssRule->type();
    202         if (cssRuleType == CSSRule::STYLE_RULE) {
    203             CSSStyleRule* cssStyleRule = toCSSStyleRule(cssRule);
    204             if (cssStyleRule->styleRule() == styleRule)
    205                 result = cssRule;
    206         } else if (cssRuleType == CSSRule::IMPORT_RULE) {
    207             CSSImportRule* cssImportRule = toCSSImportRule(cssRule);
    208             result = findStyleRule(cssImportRule->styleSheet(), styleRule);
    209         } else {
    210             result = findStyleRule(nestedRuleList(cssRule), styleRule);
    211         }
    212     }
    213     return result;
    214 }
    215 
    216 void ElementRuleCollector::appendCSSOMWrapperForRule(CSSStyleSheet* parentStyleSheet, StyleRule* rule)
    217 {
    218     // |parentStyleSheet| is 0 if and only if the |rule| is coming from User Agent. In this case,
    219     // it is safe to create CSSOM wrappers without parentStyleSheets as they will be used only
    220     // by inspector which will not try to edit them.
    221     RefPtrWillBeRawPtr<CSSRule> cssRule = nullptr;
    222     if (parentStyleSheet)
    223         cssRule = findStyleRule(parentStyleSheet, rule);
    224     else
    225         cssRule = rule->createCSSOMWrapper();
    226     ASSERT(!parentStyleSheet || cssRule);
    227     ensureRuleList()->rules().append(cssRule);
    228 }
    229 
    230 void ElementRuleCollector::sortAndTransferMatchedRules()
    231 {
    232     if (!m_matchedRules || m_matchedRules->isEmpty())
    233         return;
    234 
    235     sortMatchedRules();
    236 
    237     WillBeHeapVector<MatchedRule, 32>& matchedRules = *m_matchedRules;
    238     if (m_mode == SelectorChecker::CollectingStyleRules) {
    239         for (unsigned i = 0; i < matchedRules.size(); ++i)
    240             ensureStyleRuleList()->m_list.append(matchedRules[i].ruleData()->rule());
    241         return;
    242     }
    243 
    244     if (m_mode == SelectorChecker::CollectingCSSRules) {
    245         for (unsigned i = 0; i < matchedRules.size(); ++i)
    246             appendCSSOMWrapperForRule(const_cast<CSSStyleSheet*>(matchedRules[i].parentStyleSheet()), matchedRules[i].ruleData()->rule());
    247         return;
    248     }
    249 
    250     // Now transfer the set of matched rules over to our list of declarations.
    251     for (unsigned i = 0; i < matchedRules.size(); i++) {
    252         // FIXME: Matching should not modify the style directly.
    253         const RuleData* ruleData = matchedRules[i].ruleData();
    254         if (m_style && ruleData->containsUncommonAttributeSelector())
    255             m_style->setUnique();
    256         m_result.addMatchedProperties(&ruleData->rule()->properties(), ruleData->rule(), ruleData->linkMatchType(), ruleData->propertyWhitelistType(m_matchingUARules));
    257     }
    258 }
    259 
    260 inline bool ElementRuleCollector::ruleMatches(const RuleData& ruleData, const ContainerNode* scope, SelectorChecker::BehaviorAtBoundary behaviorAtBoundary, SelectorChecker::MatchResult* result)
    261 {
    262     SelectorChecker selectorChecker(m_context.element()->document(), m_mode);
    263     SelectorChecker::SelectorCheckingContext context(ruleData.selector(), m_context.element(), SelectorChecker::VisitedMatchEnabled);
    264     context.elementStyle = m_style.get();
    265     context.scope = scope;
    266     context.pseudoId = m_pseudoStyleRequest.pseudoId;
    267     context.scrollbar = m_pseudoStyleRequest.scrollbar;
    268     context.scrollbarPart = m_pseudoStyleRequest.scrollbarPart;
    269     context.behaviorAtBoundary = behaviorAtBoundary;
    270     SelectorChecker::Match match = selectorChecker.match(context, DOMSiblingTraversalStrategy(), result);
    271     if (match != SelectorChecker::SelectorMatches)
    272         return false;
    273     if (m_pseudoStyleRequest.pseudoId != NOPSEUDO && m_pseudoStyleRequest.pseudoId != result->dynamicPseudo)
    274         return false;
    275     return true;
    276 }
    277 
    278 void ElementRuleCollector::collectRuleIfMatches(const RuleData& ruleData, SelectorChecker::BehaviorAtBoundary behaviorAtBoundary, CascadeScope cascadeScope, CascadeOrder cascadeOrder, const MatchRequest& matchRequest, RuleRange& ruleRange)
    279 {
    280     if (m_canUseFastReject && m_selectorFilter.fastRejectSelector<RuleData::maximumIdentifierCount>(ruleData.descendantSelectorIdentifierHashes()))
    281         return;
    282 
    283     StyleRule* rule = ruleData.rule();
    284     SelectorChecker::MatchResult result;
    285     if (ruleMatches(ruleData, matchRequest.scope, behaviorAtBoundary, &result)) {
    286         // If the rule has no properties to apply, then ignore it in the non-debug mode.
    287         const StylePropertySet& properties = rule->properties();
    288         if (properties.isEmpty() && !matchRequest.includeEmptyRules)
    289             return;
    290         // FIXME: Exposing the non-standard getMatchedCSSRules API to web is the only reason this is needed.
    291         if (m_sameOriginOnly && !ruleData.hasDocumentSecurityOrigin())
    292             return;
    293 
    294         PseudoId dynamicPseudo = result.dynamicPseudo;
    295         // If we're matching normal rules, set a pseudo bit if
    296         // we really just matched a pseudo-element.
    297         if (dynamicPseudo != NOPSEUDO && m_pseudoStyleRequest.pseudoId == NOPSEUDO) {
    298             if (m_mode == SelectorChecker::CollectingCSSRules || m_mode == SelectorChecker::CollectingStyleRules)
    299                 return;
    300             // FIXME: Matching should not modify the style directly.
    301             if (m_style && dynamicPseudo < FIRST_INTERNAL_PSEUDOID)
    302                 m_style->setHasPseudoStyle(dynamicPseudo);
    303         } else {
    304             // Update our first/last rule indices in the matched rules array.
    305             ++ruleRange.lastRuleIndex;
    306             if (ruleRange.firstRuleIndex == -1)
    307                 ruleRange.firstRuleIndex = ruleRange.lastRuleIndex;
    308 
    309             // Add this rule to our list of matched rules.
    310             addMatchedRule(&ruleData, result.specificity, cascadeScope, cascadeOrder, matchRequest.styleSheetIndex, matchRequest.styleSheet);
    311             return;
    312         }
    313     }
    314 }
    315 
    316 static inline bool compareRules(const MatchedRule& matchedRule1, const MatchedRule& matchedRule2)
    317 {
    318     if (matchedRule1.cascadeScope() != matchedRule2.cascadeScope())
    319         return matchedRule1.cascadeScope() > matchedRule2.cascadeScope();
    320 
    321     unsigned specificity1 = matchedRule1.specificity();
    322     unsigned specificity2 = matchedRule2.specificity();
    323     if (specificity1 != specificity2)
    324         return specificity1 < specificity2;
    325 
    326     return matchedRule1.position() < matchedRule2.position();
    327 }
    328 
    329 void ElementRuleCollector::sortMatchedRules()
    330 {
    331     ASSERT(m_matchedRules);
    332     std::sort(m_matchedRules->begin(), m_matchedRules->end(), compareRules);
    333 }
    334 
    335 bool ElementRuleCollector::hasAnyMatchingRules(RuleSet* ruleSet)
    336 {
    337     clearMatchedRules();
    338 
    339     m_mode = SelectorChecker::SharingRules;
    340     // To check whether a given RuleSet has any rule matching a given element,
    341     // should not see the element's treescope. Because RuleSet has no
    342     // information about "scope".
    343     int firstRuleIndex = -1, lastRuleIndex = -1;
    344     RuleRange ruleRange(firstRuleIndex, lastRuleIndex);
    345     // FIXME: Verify whether it's ok to ignore CascadeScope here.
    346     collectMatchingRules(MatchRequest(ruleSet), ruleRange, SelectorChecker::StaysWithinTreeScope);
    347 
    348     return m_matchedRules && !m_matchedRules->isEmpty();
    349 }
    350 
    351 } // namespace WebCore
    352