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/CSSStyleRule.h"
     35 #include "core/css/CSSStyleSheet.h"
     36 #include "core/css/SelectorCheckerFastPath.h"
     37 #include "core/css/SiblingTraversalStrategies.h"
     38 #include "core/css/StylePropertySet.h"
     39 #include "core/css/resolver/StyleResolver.h"
     40 #include "core/dom/shadow/ShadowRoot.h"
     41 #include "core/rendering/RenderRegion.h"
     42 
     43 namespace WebCore {
     44 
     45 ElementRuleCollector::ElementRuleCollector(const ElementResolveContext& context,
     46     const SelectorFilter& filter, RenderStyle* style, ShouldIncludeStyleSheetInCSSOMWrapper includeStyleSheet)
     47     : m_context(context)
     48     , m_selectorFilter(filter)
     49     , m_style(style)
     50     , m_regionForStyling(0)
     51     , m_pseudoStyleRequest(NOPSEUDO)
     52     , m_mode(SelectorChecker::ResolvingStyle)
     53     , m_canUseFastReject(m_selectorFilter.parentStackIsConsistent(context.parentNode()))
     54     , m_sameOriginOnly(false)
     55     , m_matchingUARules(false)
     56     , m_includeStyleSheet(includeStyleSheet)
     57 { }
     58 
     59 ElementRuleCollector::~ElementRuleCollector()
     60 {
     61 }
     62 
     63 MatchResult& ElementRuleCollector::matchedResult()
     64 {
     65     return m_result;
     66 }
     67 
     68 PassRefPtr<StyleRuleList> ElementRuleCollector::matchedStyleRuleList()
     69 {
     70     ASSERT(m_mode == SelectorChecker::CollectingStyleRules);
     71     return m_styleRuleList.release();
     72 }
     73 
     74 PassRefPtr<CSSRuleList> ElementRuleCollector::matchedCSSRuleList()
     75 {
     76     ASSERT(m_mode == SelectorChecker::CollectingCSSRules);
     77     return m_cssRuleList.release();
     78 }
     79 
     80 inline void ElementRuleCollector::addMatchedRule(const RuleData* rule, unsigned specificity, CascadeScope cascadeScope, CascadeOrder cascadeOrder, unsigned styleSheetIndex)
     81 {
     82     if (!m_matchedRules)
     83         m_matchedRules = adoptPtr(new Vector<MatchedRule, 32>);
     84     m_matchedRules->append(MatchedRule(rule, specificity, cascadeScope, cascadeOrder, styleSheetIndex));
     85 }
     86 
     87 void ElementRuleCollector::clearMatchedRules()
     88 {
     89     if (!m_matchedRules)
     90         return;
     91     m_matchedRules->clear();
     92 }
     93 
     94 inline StyleRuleList* ElementRuleCollector::ensureStyleRuleList()
     95 {
     96     if (!m_styleRuleList)
     97         m_styleRuleList = StyleRuleList::create();
     98     return m_styleRuleList.get();
     99 }
    100 
    101 inline StaticCSSRuleList* ElementRuleCollector::ensureRuleList()
    102 {
    103     if (!m_cssRuleList)
    104         m_cssRuleList = StaticCSSRuleList::create();
    105     return m_cssRuleList.get();
    106 }
    107 
    108 void ElementRuleCollector::addElementStyleProperties(const StylePropertySet* propertySet, bool isCacheable)
    109 {
    110     if (!propertySet)
    111         return;
    112     m_result.ranges.lastAuthorRule = m_result.matchedProperties.size();
    113     if (m_result.ranges.firstAuthorRule == -1)
    114         m_result.ranges.firstAuthorRule = m_result.ranges.lastAuthorRule;
    115     m_result.addMatchedProperties(propertySet);
    116     if (!isCacheable)
    117         m_result.isCacheable = false;
    118 }
    119 
    120 static bool rulesApplicableInCurrentTreeScope(const Element* element, const ContainerNode* scopingNode, SelectorChecker::BehaviorAtBoundary behaviorAtBoundary, bool elementApplyAuthorStyles)
    121 {
    122     TreeScope& treeScope = element->treeScope();
    123 
    124     // [skipped, because already checked] a) it's a UA rule
    125     // b) element is allowed to apply author rules
    126     if (elementApplyAuthorStyles)
    127         return true;
    128     // c) the rules comes from a scoped style sheet within the same tree scope
    129     if (!scopingNode || treeScope == scopingNode->treeScope())
    130         return true;
    131     // d) the rules comes from a scoped style sheet within an active shadow root whose host is the given element
    132     if (element->isInShadowTree() && (behaviorAtBoundary & SelectorChecker::ScopeIsShadowHost) && scopingNode == element->containingShadowRoot()->host())
    133         return true;
    134     // e) the rules can cross boundaries
    135     if ((behaviorAtBoundary & SelectorChecker::BoundaryBehaviorMask) == SelectorChecker::CrossesBoundary)
    136         return true;
    137     return false;
    138 }
    139 
    140 void ElementRuleCollector::collectMatchingRules(const MatchRequest& matchRequest, RuleRange& ruleRange, SelectorChecker::BehaviorAtBoundary behaviorAtBoundary, CascadeScope cascadeScope, CascadeOrder cascadeOrder)
    141 {
    142     ASSERT(matchRequest.ruleSet);
    143     ASSERT(m_context.element());
    144 
    145     Element& element = *m_context.element();
    146     const AtomicString& pseudoId = element.shadowPseudoId();
    147     if (!pseudoId.isEmpty()) {
    148         ASSERT(element.isStyledElement());
    149         collectMatchingRulesForList(matchRequest.ruleSet->shadowPseudoElementRules(pseudoId.impl()), behaviorAtBoundary, ignoreCascadeScope, cascadeOrder, matchRequest, ruleRange);
    150     }
    151 
    152     if (element.isVTTElement())
    153         collectMatchingRulesForList(matchRequest.ruleSet->cuePseudoRules(), behaviorAtBoundary, cascadeScope, cascadeOrder, matchRequest, ruleRange);
    154     // Check whether other types of rules are applicable in the current tree scope. Criteria for this:
    155     // a) it's a UA rule
    156     // b) the tree scope allows author rules
    157     // c) the rules comes from a scoped style sheet within the same tree scope
    158     // d) the rules comes from a scoped style sheet within an active shadow root whose host is the given element
    159     // e) the rules can cross boundaries
    160     // b)-e) is checked in rulesApplicableInCurrentTreeScope.
    161     if (!m_matchingUARules && !rulesApplicableInCurrentTreeScope(&element, matchRequest.scope, behaviorAtBoundary, matchRequest.elementApplyAuthorStyles))
    162         return;
    163 
    164     // We need to collect the rules for id, class, tag, and everything else into a buffer and
    165     // then sort the buffer.
    166     if (element.hasID())
    167         collectMatchingRulesForList(matchRequest.ruleSet->idRules(element.idForStyleResolution().impl()), behaviorAtBoundary, cascadeScope, cascadeOrder, matchRequest, ruleRange);
    168     if (element.isStyledElement() && element.hasClass()) {
    169         for (size_t i = 0; i < element.classNames().size(); ++i)
    170             collectMatchingRulesForList(matchRequest.ruleSet->classRules(element.classNames()[i].impl()), behaviorAtBoundary, cascadeScope, cascadeOrder, matchRequest, ruleRange);
    171     }
    172 
    173     if (element.isLink())
    174         collectMatchingRulesForList(matchRequest.ruleSet->linkPseudoClassRules(), behaviorAtBoundary, cascadeScope, cascadeOrder, matchRequest, ruleRange);
    175     if (SelectorChecker::matchesFocusPseudoClass(element))
    176         collectMatchingRulesForList(matchRequest.ruleSet->focusPseudoClassRules(), behaviorAtBoundary, cascadeScope, cascadeOrder, matchRequest, ruleRange);
    177     collectMatchingRulesForList(matchRequest.ruleSet->tagRules(element.localName().impl()), behaviorAtBoundary, cascadeScope, cascadeOrder, matchRequest, ruleRange);
    178     collectMatchingRulesForList(matchRequest.ruleSet->universalRules(), behaviorAtBoundary, cascadeScope, cascadeOrder, matchRequest, ruleRange);
    179 }
    180 
    181 void ElementRuleCollector::collectMatchingRulesForRegion(const MatchRequest& matchRequest, RuleRange& ruleRange, SelectorChecker::BehaviorAtBoundary behaviorAtBoundary, CascadeScope cascadeScope, CascadeOrder cascadeOrder)
    182 {
    183     if (!m_regionForStyling)
    184         return;
    185 
    186     unsigned size = matchRequest.ruleSet->m_regionSelectorsAndRuleSets.size();
    187     for (unsigned i = 0; i < size; ++i) {
    188         const CSSSelector* regionSelector = matchRequest.ruleSet->m_regionSelectorsAndRuleSets.at(i).selector;
    189         if (checkRegionSelector(regionSelector, toElement(m_regionForStyling->nodeForRegion()))) {
    190             RuleSet* regionRules = matchRequest.ruleSet->m_regionSelectorsAndRuleSets.at(i).ruleSet.get();
    191             ASSERT(regionRules);
    192             collectMatchingRules(MatchRequest(regionRules, matchRequest.includeEmptyRules, matchRequest.scope), ruleRange, behaviorAtBoundary, cascadeScope, cascadeOrder);
    193         }
    194     }
    195 }
    196 
    197 
    198 static CSSStyleSheet* findStyleSheet(StyleEngine* styleEngine, StyleRule* rule)
    199 {
    200     // FIXME: StyleEngine has a bunch of different accessors for StyleSheet lists, is this the only one we need to care about?
    201     const Vector<RefPtr<CSSStyleSheet> >& stylesheets = styleEngine->activeAuthorStyleSheets();
    202     for (size_t i = 0; i < stylesheets.size(); ++i) {
    203         CSSStyleSheet* sheet = stylesheets[i].get();
    204         for (unsigned j = 0; j < sheet->length(); ++j) {
    205             CSSRule* cssRule = sheet->item(j);
    206             if (cssRule->type() != CSSRule::STYLE_RULE)
    207                 continue;
    208             CSSStyleRule* cssStyleRule = toCSSStyleRule(cssRule);
    209             if (cssStyleRule->styleRule() == rule)
    210                 return sheet;
    211         }
    212     }
    213     return 0;
    214 }
    215 
    216 void ElementRuleCollector::appendCSSOMWrapperForRule(StyleRule* rule)
    217 {
    218     // FIXME: There should be no codepath that creates a CSSOMWrapper without a parent stylesheet or rule because
    219     // then that codepath can lead to the CSSStyleSheet contents not getting correctly copied when the rule is modified
    220     // through the wrapper (e.g. rule.selectorText="div"). Right now, the inspector uses the pointers for identity though,
    221     // so calling CSSStyleSheet->willMutateRules breaks the inspector.
    222     CSSStyleSheet* sheet = m_includeStyleSheet == IncludeStyleSheetInCSSOMWrapper ? findStyleSheet(m_context.element()->document().styleEngine(), rule) : 0;
    223     RefPtr<CSSRule> cssRule = rule->createCSSOMWrapper(sheet);
    224     if (sheet)
    225         sheet->registerExtraChildRuleCSSOMWrapper(cssRule);
    226     ensureRuleList()->rules().append(cssRule);
    227 }
    228 
    229 void ElementRuleCollector::sortAndTransferMatchedRules()
    230 {
    231     if (!m_matchedRules || m_matchedRules->isEmpty())
    232         return;
    233 
    234     sortMatchedRules();
    235 
    236     Vector<MatchedRule, 32>& matchedRules = *m_matchedRules;
    237     if (m_mode == SelectorChecker::CollectingStyleRules) {
    238         for (unsigned i = 0; i < matchedRules.size(); ++i)
    239             ensureStyleRuleList()->m_list.append(matchedRules[i].ruleData()->rule());
    240         return;
    241     }
    242 
    243     if (m_mode == SelectorChecker::CollectingCSSRules) {
    244         for (unsigned i = 0; i < matchedRules.size(); ++i)
    245             appendCSSOMWrapperForRule(matchedRules[i].ruleData()->rule());
    246         return;
    247     }
    248 
    249     // Now transfer the set of matched rules over to our list of declarations.
    250     for (unsigned i = 0; i < matchedRules.size(); i++) {
    251         // FIXME: Matching should not modify the style directly.
    252         const RuleData* ruleData = matchedRules[i].ruleData();
    253         if (m_style && ruleData->containsUncommonAttributeSelector())
    254             m_style->setUnique();
    255         m_result.addMatchedProperties(ruleData->rule()->properties(), ruleData->rule(), ruleData->linkMatchType(), ruleData->propertyWhitelistType(m_matchingUARules));
    256     }
    257 }
    258 
    259 inline bool ElementRuleCollector::ruleMatches(const RuleData& ruleData, const ContainerNode* scope, SelectorChecker::BehaviorAtBoundary behaviorAtBoundary, SelectorChecker::MatchResult* result)
    260 {
    261     // Scoped rules can't match because the fast path uses a pool of tag/class/ids, collected from
    262     // elements in that tree and those will never match the host, since it's in a different pool.
    263     if (ruleData.hasFastCheckableSelector() && !scope) {
    264         // We know this selector does not include any pseudo elements.
    265         if (m_pseudoStyleRequest.pseudoId != NOPSEUDO)
    266             return false;
    267         // We know a sufficiently simple single part selector matches simply because we found it from the rule hash.
    268         // This is limited to HTML only so we don't need to check the namespace.
    269         ASSERT(m_context.element());
    270         if (ruleData.hasRightmostSelectorMatchingHTMLBasedOnRuleHash() && m_context.element()->isHTMLElement()) {
    271             if (!ruleData.hasMultipartSelector())
    272                 return true;
    273         }
    274         if (ruleData.selector()->m_match == CSSSelector::Tag && !SelectorChecker::tagMatches(*m_context.element(), ruleData.selector()->tagQName()))
    275             return false;
    276         SelectorCheckerFastPath selectorCheckerFastPath(ruleData.selector(), *m_context.element());
    277         if (!selectorCheckerFastPath.matchesRightmostAttributeSelector())
    278             return false;
    279 
    280         return selectorCheckerFastPath.matches();
    281     }
    282 
    283     // Slow path.
    284     SelectorChecker selectorChecker(m_context.element()->document(), m_mode);
    285     SelectorChecker::SelectorCheckingContext context(ruleData.selector(), m_context.element(), SelectorChecker::VisitedMatchEnabled);
    286     context.elementStyle = m_style.get();
    287     context.scope = scope;
    288     context.pseudoId = m_pseudoStyleRequest.pseudoId;
    289     context.scrollbar = m_pseudoStyleRequest.scrollbar;
    290     context.scrollbarPart = m_pseudoStyleRequest.scrollbarPart;
    291     context.behaviorAtBoundary = behaviorAtBoundary;
    292     SelectorChecker::Match match = selectorChecker.match(context, DOMSiblingTraversalStrategy(), result);
    293     if (match != SelectorChecker::SelectorMatches)
    294         return false;
    295     if (m_pseudoStyleRequest.pseudoId != NOPSEUDO && m_pseudoStyleRequest.pseudoId != result->dynamicPseudo)
    296         return false;
    297     return true;
    298 }
    299 
    300 void ElementRuleCollector::collectRuleIfMatches(const RuleData& ruleData, SelectorChecker::BehaviorAtBoundary behaviorAtBoundary, CascadeScope cascadeScope, CascadeOrder cascadeOrder, const MatchRequest& matchRequest, RuleRange& ruleRange)
    301 {
    302     if (m_canUseFastReject && m_selectorFilter.fastRejectSelector<RuleData::maximumIdentifierCount>(ruleData.descendantSelectorIdentifierHashes()))
    303         return;
    304 
    305     StyleRule* rule = ruleData.rule();
    306     SelectorChecker::MatchResult result;
    307     if (ruleMatches(ruleData, matchRequest.scope, behaviorAtBoundary, &result)) {
    308         // If the rule has no properties to apply, then ignore it in the non-debug mode.
    309         const StylePropertySet* properties = rule->properties();
    310         if (!properties || (properties->isEmpty() && !matchRequest.includeEmptyRules))
    311             return;
    312         // FIXME: Exposing the non-standard getMatchedCSSRules API to web is the only reason this is needed.
    313         if (m_sameOriginOnly && !ruleData.hasDocumentSecurityOrigin())
    314             return;
    315 
    316         PseudoId dynamicPseudo = result.dynamicPseudo;
    317         // If we're matching normal rules, set a pseudo bit if
    318         // we really just matched a pseudo-element.
    319         if (dynamicPseudo != NOPSEUDO && m_pseudoStyleRequest.pseudoId == NOPSEUDO) {
    320             if (m_mode == SelectorChecker::CollectingCSSRules || m_mode == SelectorChecker::CollectingStyleRules)
    321                 return;
    322             // FIXME: Matching should not modify the style directly.
    323             if (m_style && dynamicPseudo < FIRST_INTERNAL_PSEUDOID)
    324                 m_style->setHasPseudoStyle(dynamicPseudo);
    325         } else {
    326             // Update our first/last rule indices in the matched rules array.
    327             ++ruleRange.lastRuleIndex;
    328             if (ruleRange.firstRuleIndex == -1)
    329                 ruleRange.firstRuleIndex = ruleRange.lastRuleIndex;
    330 
    331             // Add this rule to our list of matched rules.
    332             addMatchedRule(&ruleData, result.specificity, cascadeScope, cascadeOrder, matchRequest.styleSheetIndex);
    333             return;
    334         }
    335     }
    336 }
    337 
    338 void ElementRuleCollector::collectMatchingRulesForList(const RuleData* rules, SelectorChecker::BehaviorAtBoundary behaviorAtBoundary, CascadeScope cascadeScope, CascadeOrder cascadeOrder, const MatchRequest& matchRequest, RuleRange& ruleRange)
    339 {
    340     if (!rules)
    341         return;
    342     while (!rules->isLastInArray())
    343         collectRuleIfMatches(*rules++, behaviorAtBoundary, cascadeScope, cascadeOrder, matchRequest, ruleRange);
    344     collectRuleIfMatches(*rules, behaviorAtBoundary, cascadeScope, cascadeOrder, matchRequest, ruleRange);
    345 }
    346 
    347 void ElementRuleCollector::collectMatchingRulesForList(const Vector<RuleData>* rules, SelectorChecker::BehaviorAtBoundary behaviorAtBoundary, CascadeScope cascadeScope, CascadeOrder cascadeOrder, const MatchRequest& matchRequest, RuleRange& ruleRange)
    348 {
    349     if (!rules)
    350         return;
    351     unsigned size = rules->size();
    352     for (unsigned i = 0; i < size; ++i)
    353         collectRuleIfMatches(rules->at(i), behaviorAtBoundary, cascadeScope, cascadeOrder, matchRequest, ruleRange);
    354 }
    355 
    356 static inline bool compareRules(const MatchedRule& matchedRule1, const MatchedRule& matchedRule2)
    357 {
    358     if (matchedRule1.cascadeScope() != matchedRule2.cascadeScope())
    359         return matchedRule1.cascadeScope() > matchedRule2.cascadeScope();
    360 
    361     unsigned specificity1 = matchedRule1.specificity();
    362     unsigned specificity2 = matchedRule2.specificity();
    363     if (specificity1 != specificity2)
    364         return specificity1 < specificity2;
    365 
    366     if (matchedRule1.styleSheetIndex() != matchedRule2.styleSheetIndex())
    367         return matchedRule1.styleSheetIndex() < matchedRule2.styleSheetIndex();
    368 
    369     return matchedRule1.position() < matchedRule2.position();
    370 }
    371 
    372 void ElementRuleCollector::sortMatchedRules()
    373 {
    374     ASSERT(m_matchedRules);
    375     std::sort(m_matchedRules->begin(), m_matchedRules->end(), compareRules);
    376 }
    377 
    378 bool ElementRuleCollector::hasAnyMatchingRules(RuleSet* ruleSet)
    379 {
    380     clearMatchedRules();
    381 
    382     m_mode = SelectorChecker::SharingRules;
    383     // To check whether a given RuleSet has any rule matching a given element,
    384     // should not see the element's treescope. Because RuleSet has no
    385     // information about "scope".
    386     int firstRuleIndex = -1, lastRuleIndex = -1;
    387     RuleRange ruleRange(firstRuleIndex, lastRuleIndex);
    388     // FIXME: Verify whether it's ok to ignore CascadeScope here.
    389     collectMatchingRules(MatchRequest(ruleSet), ruleRange, SelectorChecker::StaysWithinTreeScope);
    390 
    391     return m_matchedRules && !m_matchedRules->isEmpty();
    392 }
    393 
    394 } // namespace WebCore
    395