1/*
2 * Copyright (C) 1999 Lars Knoll (knoll@kde.org)
3 *           (C) 2004-2005 Allan Sandfeld Jensen (kde@carewolf.com)
4 * Copyright (C) 2006, 2007 Nicholas Shanks (webkit@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@webkit.org>
7 * Copyright (C) 2007, 2008 Eric Seidel <eric@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
46namespace blink {
47
48ElementRuleCollector::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
60ElementRuleCollector::~ElementRuleCollector()
61{
62}
63
64MatchResult& ElementRuleCollector::matchedResult()
65{
66    return m_result;
67}
68
69PassRefPtrWillBeRawPtr<StyleRuleList> ElementRuleCollector::matchedStyleRuleList()
70{
71    ASSERT(m_mode == SelectorChecker::CollectingStyleRules);
72    return m_styleRuleList.release();
73}
74
75PassRefPtrWillBeRawPtr<CSSRuleList> ElementRuleCollector::matchedCSSRuleList()
76{
77    ASSERT(m_mode == SelectorChecker::CollectingCSSRules);
78    return m_cssRuleList.release();
79}
80
81inline 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
88void ElementRuleCollector::clearMatchedRules()
89{
90    if (!m_matchedRules)
91        return;
92    m_matchedRules->clear();
93}
94
95inline StyleRuleList* ElementRuleCollector::ensureStyleRuleList()
96{
97    if (!m_styleRuleList)
98        m_styleRuleList = StyleRuleList::create();
99    return m_styleRuleList.get();
100}
101
102inline StaticCSSRuleList* ElementRuleCollector::ensureRuleList()
103{
104    if (!m_cssRuleList)
105        m_cssRuleList = StaticCSSRuleList::create();
106    return m_cssRuleList.get();
107}
108
109void 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
121static bool rulesApplicableInCurrentTreeScope(const Element* element, const ContainerNode* scopingNode, bool matchingTreeBoundaryRules)
122{
123    // [skipped, because already checked] a) it's a UA rule
124    // b) We're mathcing tree boundary rules.
125    if (matchingTreeBoundaryRules)
126        return true;
127    // c) the rules comes from a scoped style sheet within the same tree scope
128    if (!scopingNode || element->treeScope() == scopingNode->treeScope())
129        return true;
130    // d) the rules comes from a scoped style sheet within an active shadow root whose host is the given element
131    if (SelectorChecker::isHostInItsShadowTree(*element, scopingNode))
132        return true;
133    return false;
134}
135
136void ElementRuleCollector::collectMatchingRules(const MatchRequest& matchRequest, RuleRange& ruleRange, SelectorChecker::ContextFlags contextFlags, CascadeScope cascadeScope, CascadeOrder cascadeOrder, bool matchingTreeBoundaryRules)
137{
138    ASSERT(matchRequest.ruleSet);
139    ASSERT(m_context.element());
140
141    Element& element = *m_context.element();
142    const AtomicString& pseudoId = element.shadowPseudoId();
143    if (!pseudoId.isEmpty()) {
144        ASSERT(element.isStyledElement());
145        collectMatchingRulesForList(matchRequest.ruleSet->shadowPseudoElementRules(pseudoId), contextFlags, ignoreCascadeScope, cascadeOrder, matchRequest, ruleRange);
146    }
147
148    if (element.isVTTElement())
149        collectMatchingRulesForList(matchRequest.ruleSet->cuePseudoRules(), contextFlags, cascadeScope, cascadeOrder, matchRequest, ruleRange);
150    // Check whether other types of rules are applicable in the current tree scope. Criteria for this:
151    // a) it's a UA rule
152    // b) the rules comes from a scoped style sheet within the same tree scope
153    // c) the rules comes from a scoped style sheet within an active shadow root whose host is the given element
154    // d) the rules can cross boundaries
155    // b)-e) is checked in rulesApplicableInCurrentTreeScope.
156    if (!m_matchingUARules && !rulesApplicableInCurrentTreeScope(&element, matchRequest.scope, matchingTreeBoundaryRules))
157        return;
158
159    // We need to collect the rules for id, class, tag, and everything else into a buffer and
160    // then sort the buffer.
161    if (element.hasID())
162        collectMatchingRulesForList(matchRequest.ruleSet->idRules(element.idForStyleResolution()), contextFlags, cascadeScope, cascadeOrder, matchRequest, ruleRange);
163    if (element.isStyledElement() && element.hasClass()) {
164        for (size_t i = 0; i < element.classNames().size(); ++i)
165            collectMatchingRulesForList(matchRequest.ruleSet->classRules(element.classNames()[i]), contextFlags, cascadeScope, cascadeOrder, matchRequest, ruleRange);
166    }
167
168    if (element.isLink())
169        collectMatchingRulesForList(matchRequest.ruleSet->linkPseudoClassRules(), contextFlags, cascadeScope, cascadeOrder, matchRequest, ruleRange);
170    if (SelectorChecker::matchesFocusPseudoClass(element))
171        collectMatchingRulesForList(matchRequest.ruleSet->focusPseudoClassRules(), contextFlags, cascadeScope, cascadeOrder, matchRequest, ruleRange);
172    collectMatchingRulesForList(matchRequest.ruleSet->tagRules(element.localName()), contextFlags, cascadeScope, cascadeOrder, matchRequest, ruleRange);
173    collectMatchingRulesForList(matchRequest.ruleSet->universalRules(), contextFlags, cascadeScope, cascadeOrder, matchRequest, ruleRange);
174}
175
176CSSRuleList* ElementRuleCollector::nestedRuleList(CSSRule* rule)
177{
178    switch (rule->type()) {
179    case CSSRule::MEDIA_RULE:
180        return toCSSMediaRule(rule)->cssRules();
181    case CSSRule::KEYFRAMES_RULE:
182        return toCSSKeyframesRule(rule)->cssRules();
183    case CSSRule::SUPPORTS_RULE:
184        return toCSSSupportsRule(rule)->cssRules();
185    default:
186        return 0;
187    }
188}
189
190template<class CSSRuleCollection>
191CSSRule* ElementRuleCollector::findStyleRule(CSSRuleCollection* cssRules, StyleRule* styleRule)
192{
193    if (!cssRules)
194        return 0;
195    CSSRule* result = 0;
196    for (unsigned i = 0; i < cssRules->length() && !result; ++i) {
197        CSSRule* cssRule = cssRules->item(i);
198        CSSRule::Type cssRuleType = cssRule->type();
199        if (cssRuleType == CSSRule::STYLE_RULE) {
200            CSSStyleRule* cssStyleRule = toCSSStyleRule(cssRule);
201            if (cssStyleRule->styleRule() == styleRule)
202                result = cssRule;
203        } else if (cssRuleType == CSSRule::IMPORT_RULE) {
204            CSSImportRule* cssImportRule = toCSSImportRule(cssRule);
205            result = findStyleRule(cssImportRule->styleSheet(), styleRule);
206        } else {
207            result = findStyleRule(nestedRuleList(cssRule), styleRule);
208        }
209    }
210    return result;
211}
212
213void ElementRuleCollector::appendCSSOMWrapperForRule(CSSStyleSheet* parentStyleSheet, StyleRule* rule)
214{
215    // |parentStyleSheet| is 0 if and only if the |rule| is coming from User Agent. In this case,
216    // it is safe to create CSSOM wrappers without parentStyleSheets as they will be used only
217    // by inspector which will not try to edit them.
218    RefPtrWillBeRawPtr<CSSRule> cssRule = nullptr;
219    if (parentStyleSheet)
220        cssRule = findStyleRule(parentStyleSheet, rule);
221    else
222        cssRule = rule->createCSSOMWrapper();
223    ASSERT(!parentStyleSheet || cssRule);
224    ensureRuleList()->rules().append(cssRule);
225}
226
227void ElementRuleCollector::sortAndTransferMatchedRules()
228{
229    if (!m_matchedRules || m_matchedRules->isEmpty())
230        return;
231
232    sortMatchedRules();
233
234    WillBeHeapVector<MatchedRule, 32>& matchedRules = *m_matchedRules;
235    if (m_mode == SelectorChecker::CollectingStyleRules) {
236        for (unsigned i = 0; i < matchedRules.size(); ++i)
237            ensureStyleRuleList()->m_list.append(matchedRules[i].ruleData()->rule());
238        return;
239    }
240
241    if (m_mode == SelectorChecker::CollectingCSSRules) {
242        for (unsigned i = 0; i < matchedRules.size(); ++i)
243            appendCSSOMWrapperForRule(const_cast<CSSStyleSheet*>(matchedRules[i].parentStyleSheet()), matchedRules[i].ruleData()->rule());
244        return;
245    }
246
247    // Now transfer the set of matched rules over to our list of declarations.
248    for (unsigned i = 0; i < matchedRules.size(); i++) {
249        // FIXME: Matching should not modify the style directly.
250        const RuleData* ruleData = matchedRules[i].ruleData();
251        if (m_style && ruleData->containsUncommonAttributeSelector())
252            m_style->setUnique();
253        m_result.addMatchedProperties(&ruleData->rule()->properties(), ruleData->linkMatchType(), ruleData->propertyWhitelistType(m_matchingUARules));
254    }
255}
256
257inline bool ElementRuleCollector::ruleMatches(const RuleData& ruleData, const ContainerNode* scope, SelectorChecker::ContextFlags contextFlags, SelectorChecker::MatchResult* result)
258{
259    SelectorChecker selectorChecker(m_context.element()->document(), m_mode);
260    SelectorChecker::SelectorCheckingContext context(ruleData.selector(), m_context.element(), SelectorChecker::VisitedMatchEnabled);
261    context.elementStyle = m_style.get();
262    context.scope = scope;
263    context.pseudoId = m_pseudoStyleRequest.pseudoId;
264    context.scrollbar = m_pseudoStyleRequest.scrollbar;
265    context.scrollbarPart = m_pseudoStyleRequest.scrollbarPart;
266    context.isUARule = m_matchingUARules;
267    context.contextFlags = contextFlags;
268    SelectorChecker::Match match = selectorChecker.match(context, DOMSiblingTraversalStrategy(), result);
269    if (match != SelectorChecker::SelectorMatches)
270        return false;
271    if (m_pseudoStyleRequest.pseudoId != NOPSEUDO && m_pseudoStyleRequest.pseudoId != result->dynamicPseudo)
272        return false;
273    return true;
274}
275
276void ElementRuleCollector::collectRuleIfMatches(const RuleData& ruleData, SelectorChecker::ContextFlags contextFlags, CascadeScope cascadeScope, CascadeOrder cascadeOrder, const MatchRequest& matchRequest, RuleRange& ruleRange)
277{
278    if (m_canUseFastReject && m_selectorFilter.fastRejectSelector<RuleData::maximumIdentifierCount>(ruleData.descendantSelectorIdentifierHashes()))
279        return;
280
281    StyleRule* rule = ruleData.rule();
282    SelectorChecker::MatchResult result;
283    if (ruleMatches(ruleData, matchRequest.scope, contextFlags, &result)) {
284        // If the rule has no properties to apply, then ignore it in the non-debug mode.
285        const StylePropertySet& properties = rule->properties();
286        if (properties.isEmpty() && !matchRequest.includeEmptyRules)
287            return;
288        // FIXME: Exposing the non-standard getMatchedCSSRules API to web is the only reason this is needed.
289        if (m_sameOriginOnly && !ruleData.hasDocumentSecurityOrigin())
290            return;
291
292        PseudoId dynamicPseudo = result.dynamicPseudo;
293        // If we're matching normal rules, set a pseudo bit if
294        // we really just matched a pseudo-element.
295        if (dynamicPseudo != NOPSEUDO && m_pseudoStyleRequest.pseudoId == NOPSEUDO) {
296            if (m_mode == SelectorChecker::CollectingCSSRules || m_mode == SelectorChecker::CollectingStyleRules)
297                return;
298            // FIXME: Matching should not modify the style directly.
299            if (!m_style || dynamicPseudo >= FIRST_INTERNAL_PSEUDOID)
300                return;
301            if ((dynamicPseudo == BEFORE || dynamicPseudo == AFTER) && !ruleData.rule()->properties().hasProperty(CSSPropertyContent))
302                return;
303            m_style->setHasPseudoStyle(dynamicPseudo);
304        } else {
305            // Update our first/last rule indices in the matched rules array.
306            ++ruleRange.lastRuleIndex;
307            if (ruleRange.firstRuleIndex == -1)
308                ruleRange.firstRuleIndex = ruleRange.lastRuleIndex;
309
310            // Add this rule to our list of matched rules.
311            addMatchedRule(&ruleData, result.specificity, cascadeScope, cascadeOrder, matchRequest.styleSheetIndex, matchRequest.styleSheet);
312            return;
313        }
314    }
315}
316
317static inline bool compareRules(const MatchedRule& matchedRule1, const MatchedRule& matchedRule2)
318{
319    if (matchedRule1.cascadeScope() != matchedRule2.cascadeScope())
320        return matchedRule1.cascadeScope() > matchedRule2.cascadeScope();
321
322    unsigned specificity1 = matchedRule1.specificity();
323    unsigned specificity2 = matchedRule2.specificity();
324    if (specificity1 != specificity2)
325        return specificity1 < specificity2;
326
327    return matchedRule1.position() < matchedRule2.position();
328}
329
330void ElementRuleCollector::sortMatchedRules()
331{
332    ASSERT(m_matchedRules);
333    std::sort(m_matchedRules->begin(), m_matchedRules->end(), compareRules);
334}
335
336bool ElementRuleCollector::hasAnyMatchingRules(RuleSet* ruleSet)
337{
338    clearMatchedRules();
339
340    m_mode = SelectorChecker::SharingRules;
341    // To check whether a given RuleSet has any rule matching a given element,
342    // should not see the element's treescope. Because RuleSet has no
343    // information about "scope".
344    int firstRuleIndex = -1, lastRuleIndex = -1;
345    RuleRange ruleRange(firstRuleIndex, lastRuleIndex);
346    // FIXME: Verify whether it's ok to ignore CascadeScope here.
347    collectMatchingRules(MatchRequest(ruleSet), ruleRange, SelectorChecker::DefaultBehavior);
348
349    return m_matchedRules && !m_matchedRules->isEmpty();
350}
351
352} // namespace blink
353