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/CSSRuleList.h"
     33 #include "core/css/CSSSelector.h"
     34 #include "core/css/SelectorCheckerFastPath.h"
     35 #include "core/css/SiblingTraversalStrategies.h"
     36 #include "core/css/StylePropertySet.h"
     37 #include "core/css/resolver/StyleResolver.h"
     38 #include "core/dom/shadow/ShadowRoot.h"
     39 #include "core/rendering/RenderRegion.h"
     40 
     41 namespace WebCore {
     42 
     43 ElementRuleCollector::ElementRuleCollector(const ElementResolveContext& context,
     44     const SelectorFilter& filter, RenderStyle* style)
     45     : m_context(context)
     46     , m_selectorFilter(filter)
     47     , m_style(style)
     48     , m_regionForStyling(0)
     49     , m_pseudoStyleRequest(NOPSEUDO)
     50     , m_mode(SelectorChecker::ResolvingStyle)
     51     , m_behaviorAtBoundary(SelectorChecker::DoesNotCrossBoundary)
     52     , m_canUseFastReject(m_selectorFilter.parentStackIsConsistent(context.parentNode()))
     53     , m_sameOriginOnly(false)
     54     , m_matchingUARules(false)
     55 { }
     56 
     57 MatchResult& ElementRuleCollector::matchedResult()
     58 {
     59     return m_result;
     60 }
     61 
     62 PassRefPtr<CSSRuleList> ElementRuleCollector::matchedRuleList()
     63 {
     64     ASSERT(m_mode == SelectorChecker::CollectingRules);
     65     return m_ruleList.release();
     66 }
     67 
     68 inline void ElementRuleCollector::addMatchedRule(const RuleData* rule, CascadeScope cascadeScope, CascadeOrder cascadeOrder)
     69 {
     70     if (!m_matchedRules)
     71         m_matchedRules = adoptPtr(new Vector<MatchedRule, 32>);
     72     m_matchedRules->append(MatchedRule(rule, cascadeScope, cascadeOrder));
     73 }
     74 
     75 void ElementRuleCollector::clearMatchedRules()
     76 {
     77     if (!m_matchedRules)
     78         return;
     79     m_matchedRules->clear();
     80 }
     81 
     82 inline StaticCSSRuleList* ElementRuleCollector::ensureRuleList()
     83 {
     84     if (!m_ruleList)
     85         m_ruleList = StaticCSSRuleList::create();
     86     return m_ruleList.get();
     87 }
     88 
     89 void ElementRuleCollector::addElementStyleProperties(const StylePropertySet* propertySet, bool isCacheable)
     90 {
     91     if (!propertySet)
     92         return;
     93     m_result.ranges.lastAuthorRule = m_result.matchedProperties.size();
     94     if (m_result.ranges.firstAuthorRule == -1)
     95         m_result.ranges.firstAuthorRule = m_result.ranges.lastAuthorRule;
     96     m_result.addMatchedProperties(propertySet);
     97     if (!isCacheable)
     98         m_result.isCacheable = false;
     99 }
    100 
    101 static bool rulesApplicableInCurrentTreeScope(const Element* element, const ContainerNode* scopingNode, SelectorChecker::BehaviorAtBoundary behaviorAtBoundary, bool elementApplyAuthorStyles)
    102 {
    103     TreeScope* treeScope = element->treeScope();
    104 
    105     // [skipped, because already checked] a) it's a UA rule
    106     // b) element is allowed to apply author rules
    107     if (elementApplyAuthorStyles)
    108         return true;
    109     // c) the rules comes from a scoped style sheet within the same tree scope
    110     if (!scopingNode || treeScope == scopingNode->treeScope())
    111         return true;
    112     // d) the rules comes from a scoped style sheet within an active shadow root whose host is the given element
    113     if (element->isInShadowTree() && (behaviorAtBoundary & SelectorChecker::ScopeIsShadowHost) && scopingNode == element->containingShadowRoot()->host())
    114         return true;
    115     // e) the rules can cross boundaries
    116     if ((behaviorAtBoundary & SelectorChecker::BoundaryBehaviorMask) == SelectorChecker::CrossesBoundary)
    117         return true;
    118     return false;
    119 }
    120 
    121 void ElementRuleCollector::collectMatchingRules(const MatchRequest& matchRequest, RuleRange& ruleRange, CascadeScope cascadeScope, CascadeOrder cascadeOrder)
    122 {
    123     ASSERT(matchRequest.ruleSet);
    124     ASSERT(m_context.element());
    125 
    126     Element* element = m_context.element();
    127     const AtomicString& pseudoId = element->shadowPseudoId();
    128     if (!pseudoId.isEmpty()) {
    129         ASSERT(element->isStyledElement());
    130         collectMatchingRulesForList(matchRequest.ruleSet->shadowPseudoElementRules(pseudoId.impl()), cascadeScope, cascadeOrder, matchRequest, ruleRange);
    131     }
    132 
    133     if (element->isWebVTTElement())
    134         collectMatchingRulesForList(matchRequest.ruleSet->cuePseudoRules(), cascadeScope, cascadeOrder, matchRequest, ruleRange);
    135     // Check whether other types of rules are applicable in the current tree scope. Criteria for this:
    136     // a) it's a UA rule
    137     // b) the tree scope allows author rules
    138     // c) the rules comes from a scoped style sheet within the same tree scope
    139     // d) the rules comes from a scoped style sheet within an active shadow root whose host is the given element
    140     // e) the rules can cross boundaries
    141     // b)-e) is checked in rulesApplicableInCurrentTreeScope.
    142     if (!m_matchingUARules && !rulesApplicableInCurrentTreeScope(element, matchRequest.scope, m_behaviorAtBoundary, matchRequest.elementApplyAuthorStyles))
    143         return;
    144 
    145     // We need to collect the rules for id, class, tag, and everything else into a buffer and
    146     // then sort the buffer.
    147     if (element->hasID())
    148         collectMatchingRulesForList(matchRequest.ruleSet->idRules(element->idForStyleResolution().impl()), cascadeScope, cascadeOrder, matchRequest, ruleRange);
    149     if (element->isStyledElement() && element->hasClass()) {
    150         for (size_t i = 0; i < element->classNames().size(); ++i)
    151             collectMatchingRulesForList(matchRequest.ruleSet->classRules(element->classNames()[i].impl()), cascadeScope, cascadeOrder, matchRequest, ruleRange);
    152     }
    153 
    154     if (element->isLink())
    155         collectMatchingRulesForList(matchRequest.ruleSet->linkPseudoClassRules(), cascadeScope, cascadeOrder, matchRequest, ruleRange);
    156     if (SelectorChecker::matchesFocusPseudoClass(element))
    157         collectMatchingRulesForList(matchRequest.ruleSet->focusPseudoClassRules(), cascadeScope, cascadeOrder, matchRequest, ruleRange);
    158     collectMatchingRulesForList(matchRequest.ruleSet->tagRules(element->localName().impl()), cascadeScope, cascadeOrder, matchRequest, ruleRange);
    159     collectMatchingRulesForList(matchRequest.ruleSet->universalRules(), cascadeScope, cascadeOrder, matchRequest, ruleRange);
    160 }
    161 
    162 void ElementRuleCollector::collectMatchingRulesForRegion(const MatchRequest& matchRequest, RuleRange& ruleRange, CascadeScope cascadeScope, CascadeOrder cascadeOrder)
    163 {
    164     if (!m_regionForStyling)
    165         return;
    166 
    167     unsigned size = matchRequest.ruleSet->m_regionSelectorsAndRuleSets.size();
    168     for (unsigned i = 0; i < size; ++i) {
    169         const CSSSelector* regionSelector = matchRequest.ruleSet->m_regionSelectorsAndRuleSets.at(i).selector;
    170         if (checkRegionSelector(regionSelector, toElement(m_regionForStyling->node()))) {
    171             RuleSet* regionRules = matchRequest.ruleSet->m_regionSelectorsAndRuleSets.at(i).ruleSet.get();
    172             ASSERT(regionRules);
    173             collectMatchingRules(MatchRequest(regionRules, matchRequest.includeEmptyRules, matchRequest.scope), ruleRange, cascadeScope, cascadeOrder);
    174         }
    175     }
    176 }
    177 
    178 void ElementRuleCollector::sortAndTransferMatchedRules()
    179 {
    180     if (!m_matchedRules || m_matchedRules->isEmpty())
    181         return;
    182 
    183     sortMatchedRules();
    184 
    185     Vector<MatchedRule, 32>& matchedRules = *m_matchedRules;
    186     if (m_mode == SelectorChecker::CollectingRules) {
    187         for (unsigned i = 0; i < matchedRules.size(); ++i)
    188             ensureRuleList()->rules().append(matchedRules[i].ruleData()->rule()->createCSSOMWrapper());
    189         return;
    190     }
    191 
    192     // Now transfer the set of matched rules over to our list of declarations.
    193     for (unsigned i = 0; i < matchedRules.size(); i++) {
    194         // FIXME: Matching should not modify the style directly.
    195         const RuleData* ruleData = matchedRules[i].ruleData();
    196         if (m_style && ruleData->containsUncommonAttributeSelector())
    197             m_style->setUnique();
    198         m_result.addMatchedProperties(ruleData->rule()->properties(), ruleData->rule(), ruleData->linkMatchType(), ruleData->propertyWhitelistType(m_matchingUARules));
    199     }
    200 }
    201 
    202 inline bool ElementRuleCollector::ruleMatches(const RuleData& ruleData, const ContainerNode* scope, PseudoId& dynamicPseudo)
    203 {
    204     if (ruleData.hasFastCheckableSelector()) {
    205         // We know this selector does not include any pseudo elements.
    206         if (m_pseudoStyleRequest.pseudoId != NOPSEUDO)
    207             return false;
    208         // We know a sufficiently simple single part selector matches simply because we found it from the rule hash.
    209         // This is limited to HTML only so we don't need to check the namespace.
    210         if (ruleData.hasRightmostSelectorMatchingHTMLBasedOnRuleHash() && m_context.element()->isHTMLElement()) {
    211             if (!ruleData.hasMultipartSelector())
    212                 return true;
    213         }
    214         if (ruleData.selector()->m_match == CSSSelector::Tag && !SelectorChecker::tagMatches(m_context.element(), ruleData.selector()->tagQName()))
    215             return false;
    216         SelectorCheckerFastPath selectorCheckerFastPath(ruleData.selector(), m_context.element());
    217         if (!selectorCheckerFastPath.matchesRightmostAttributeSelector())
    218             return false;
    219 
    220         return selectorCheckerFastPath.matches();
    221     }
    222 
    223     // Slow path.
    224     SelectorChecker selectorChecker(document(), m_mode);
    225     SelectorChecker::SelectorCheckingContext context(ruleData.selector(), m_context.element(), SelectorChecker::VisitedMatchEnabled);
    226     context.elementStyle = m_style.get();
    227     context.scope = scope;
    228     context.pseudoId = m_pseudoStyleRequest.pseudoId;
    229     context.scrollbar = m_pseudoStyleRequest.scrollbar;
    230     context.scrollbarPart = m_pseudoStyleRequest.scrollbarPart;
    231     context.behaviorAtBoundary = m_behaviorAtBoundary;
    232     SelectorChecker::Match match = selectorChecker.match(context, dynamicPseudo, DOMSiblingTraversalStrategy());
    233     if (match != SelectorChecker::SelectorMatches)
    234         return false;
    235     if (m_pseudoStyleRequest.pseudoId != NOPSEUDO && m_pseudoStyleRequest.pseudoId != dynamicPseudo)
    236         return false;
    237     return true;
    238 }
    239 
    240 void ElementRuleCollector::collectRuleIfMatches(const RuleData& ruleData, CascadeScope cascadeScope, CascadeOrder cascadeOrder, const MatchRequest& matchRequest, RuleRange& ruleRange)
    241 {
    242     if (m_canUseFastReject && m_selectorFilter.fastRejectSelector<RuleData::maximumIdentifierCount>(ruleData.descendantSelectorIdentifierHashes()))
    243         return;
    244 
    245     StyleRule* rule = ruleData.rule();
    246     PseudoId dynamicPseudo = NOPSEUDO;
    247     if (ruleMatches(ruleData, matchRequest.scope, dynamicPseudo)) {
    248         // If the rule has no properties to apply, then ignore it in the non-debug mode.
    249         const StylePropertySet* properties = rule->properties();
    250         if (!properties || (properties->isEmpty() && !matchRequest.includeEmptyRules))
    251             return;
    252         // FIXME: Exposing the non-standard getMatchedCSSRules API to web is the only reason this is needed.
    253         if (m_sameOriginOnly && !ruleData.hasDocumentSecurityOrigin())
    254             return;
    255         // If we're matching normal rules, set a pseudo bit if
    256         // we really just matched a pseudo-element.
    257         if (dynamicPseudo != NOPSEUDO && m_pseudoStyleRequest.pseudoId == NOPSEUDO) {
    258             if (m_mode == SelectorChecker::CollectingRules)
    259                 return;
    260             // FIXME: Matching should not modify the style directly.
    261             if (dynamicPseudo < FIRST_INTERNAL_PSEUDOID)
    262                 m_style->setHasPseudoStyle(dynamicPseudo);
    263         } else {
    264             // Update our first/last rule indices in the matched rules array.
    265             ++ruleRange.lastRuleIndex;
    266             if (ruleRange.firstRuleIndex == -1)
    267                 ruleRange.firstRuleIndex = ruleRange.lastRuleIndex;
    268 
    269             // Add this rule to our list of matched rules.
    270             addMatchedRule(&ruleData, cascadeScope, cascadeOrder);
    271             return;
    272         }
    273     }
    274 }
    275 
    276 void ElementRuleCollector::collectMatchingRulesForList(const RuleData* rules, CascadeScope cascadeScope, CascadeOrder cascadeOrder, const MatchRequest& matchRequest, RuleRange& ruleRange)
    277 {
    278     if (!rules)
    279         return;
    280     while (!rules->isLastInArray())
    281         collectRuleIfMatches(*rules++, cascadeScope, cascadeOrder, matchRequest, ruleRange);
    282     collectRuleIfMatches(*rules, cascadeScope, cascadeOrder, matchRequest, ruleRange);
    283 }
    284 
    285 void ElementRuleCollector::collectMatchingRulesForList(const Vector<RuleData>* rules, CascadeScope cascadeScope, CascadeOrder cascadeOrder, const MatchRequest& matchRequest, RuleRange& ruleRange)
    286 {
    287     if (!rules)
    288         return;
    289     unsigned size = rules->size();
    290     for (unsigned i = 0; i < size; ++i)
    291         collectRuleIfMatches(rules->at(i), cascadeScope, cascadeOrder, matchRequest, ruleRange);
    292 }
    293 
    294 static inline bool compareRules(const MatchedRule& matchedRule1, const MatchedRule& matchedRule2)
    295 {
    296     unsigned specificity1 = matchedRule1.ruleData()->specificity();
    297     unsigned specificity2 = matchedRule2.ruleData()->specificity();
    298     return matchedRule1.cascadeScope() == matchedRule2.cascadeScope() ?
    299         ((specificity1 == specificity2) ? matchedRule1.position() < matchedRule2.position() : specificity1 < specificity2) :
    300         matchedRule1.cascadeScope() < matchedRule2.cascadeScope();
    301 }
    302 
    303 void ElementRuleCollector::sortMatchedRules()
    304 {
    305     ASSERT(m_matchedRules);
    306     std::sort(m_matchedRules->begin(), m_matchedRules->end(), compareRules);
    307 }
    308 
    309 bool ElementRuleCollector::hasAnyMatchingRules(RuleSet* ruleSet)
    310 {
    311     clearMatchedRules();
    312 
    313     m_mode = SelectorChecker::SharingRules;
    314     // To check whether a given RuleSet has any rule matching a given element,
    315     // should not see the element's treescope. Because RuleSet has no
    316     // information about "scope".
    317     m_behaviorAtBoundary = SelectorChecker::StaysWithinTreeScope;
    318     int firstRuleIndex = -1, lastRuleIndex = -1;
    319     RuleRange ruleRange(firstRuleIndex, lastRuleIndex);
    320     // FIXME: Verify whether it's ok to ignore CascadeScope here.
    321     collectMatchingRules(MatchRequest(ruleSet), ruleRange);
    322 
    323     return m_matchedRules && !m_matchedRules->isEmpty();
    324 }
    325 
    326 } // namespace WebCore
    327