53bd4fb706ef31479193655ab9bc542d65d9a80e
[platform/framework/web/crosswalk.git] / src / third_party / WebKit / Source / core / css / ElementRuleCollector.cpp
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/SelectorCheckerFastPath.h"
41 #include "core/css/SiblingTraversalStrategies.h"
42 #include "core/css/StylePropertySet.h"
43 #include "core/css/resolver/StyleResolver.h"
44 #include "core/dom/shadow/ShadowRoot.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 PassRefPtr<StyleRuleList> ElementRuleCollector::matchedStyleRuleList()
70 {
71     ASSERT(m_mode == SelectorChecker::CollectingStyleRules);
72     return m_styleRuleList.release();
73 }
74
75 PassRefPtr<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 = adoptPtr(new Vector<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 (element->isInShadowTree() && (behaviorAtBoundary & SelectorChecker::ScopeIsShadowHost) && scopingNode == element->containingShadowRoot()->host())
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     RefPtr<CSSRule> cssRule = parentStyleSheet ? findStyleRule(parentStyleSheet, rule) : rule->createCSSOMWrapper();
222     ASSERT(!parentStyleSheet || cssRule);
223     ensureRuleList()->rules().append(cssRule);
224 }
225
226 void ElementRuleCollector::sortAndTransferMatchedRules()
227 {
228     if (!m_matchedRules || m_matchedRules->isEmpty())
229         return;
230
231     sortMatchedRules();
232
233     Vector<MatchedRule, 32>& matchedRules = *m_matchedRules;
234     if (m_mode == SelectorChecker::CollectingStyleRules) {
235         for (unsigned i = 0; i < matchedRules.size(); ++i)
236             ensureStyleRuleList()->m_list.append(matchedRules[i].ruleData()->rule());
237         return;
238     }
239
240     if (m_mode == SelectorChecker::CollectingCSSRules) {
241         for (unsigned i = 0; i < matchedRules.size(); ++i)
242             appendCSSOMWrapperForRule(const_cast<CSSStyleSheet*>(matchedRules[i].parentStyleSheet()), matchedRules[i].ruleData()->rule());
243         return;
244     }
245
246     // Now transfer the set of matched rules over to our list of declarations.
247     for (unsigned i = 0; i < matchedRules.size(); i++) {
248         // FIXME: Matching should not modify the style directly.
249         const RuleData* ruleData = matchedRules[i].ruleData();
250         if (m_style && ruleData->containsUncommonAttributeSelector())
251             m_style->setUnique();
252         m_result.addMatchedProperties(ruleData->rule()->properties(), ruleData->rule(), ruleData->linkMatchType(), ruleData->propertyWhitelistType(m_matchingUARules));
253     }
254 }
255
256 inline bool ElementRuleCollector::ruleMatches(const RuleData& ruleData, const ContainerNode* scope, SelectorChecker::BehaviorAtBoundary behaviorAtBoundary, SelectorChecker::MatchResult* result)
257 {
258     // Scoped rules can't match because the fast path uses a pool of tag/class/ids, collected from
259     // elements in that tree and those will never match the host, since it's in a different pool.
260     if (ruleData.hasFastCheckableSelector() && !scope) {
261         // We know this selector does not include any pseudo elements.
262         if (m_pseudoStyleRequest.pseudoId != NOPSEUDO)
263             return false;
264         // We know a sufficiently simple single part selector matches simply because we found it from the rule hash.
265         // This is limited to HTML only so we don't need to check the namespace.
266         ASSERT(m_context.element());
267         if (ruleData.hasRightmostSelectorMatchingHTMLBasedOnRuleHash() && m_context.element()->isHTMLElement()) {
268             if (!ruleData.hasMultipartSelector())
269                 return true;
270         }
271         if (ruleData.selector().m_match == CSSSelector::Tag && !SelectorChecker::tagMatches(*m_context.element(), ruleData.selector().tagQName()))
272             return false;
273         SelectorCheckerFastPath selectorCheckerFastPath(ruleData.selector(), *m_context.element());
274         if (!selectorCheckerFastPath.matchesRightmostAttributeSelector())
275             return false;
276
277         return selectorCheckerFastPath.matches();
278     }
279
280     // Slow path.
281     SelectorChecker selectorChecker(m_context.element()->document(), m_mode);
282     SelectorChecker::SelectorCheckingContext context(ruleData.selector(), m_context.element(), SelectorChecker::VisitedMatchEnabled);
283     context.elementStyle = m_style.get();
284     context.scope = scope;
285     context.pseudoId = m_pseudoStyleRequest.pseudoId;
286     context.scrollbar = m_pseudoStyleRequest.scrollbar;
287     context.scrollbarPart = m_pseudoStyleRequest.scrollbarPart;
288     context.behaviorAtBoundary = behaviorAtBoundary;
289     SelectorChecker::Match match = selectorChecker.match(context, DOMSiblingTraversalStrategy(), result);
290     if (match != SelectorChecker::SelectorMatches)
291         return false;
292     if (m_pseudoStyleRequest.pseudoId != NOPSEUDO && m_pseudoStyleRequest.pseudoId != result->dynamicPseudo)
293         return false;
294     return true;
295 }
296
297 void ElementRuleCollector::collectRuleIfMatches(const RuleData& ruleData, SelectorChecker::BehaviorAtBoundary behaviorAtBoundary, CascadeScope cascadeScope, CascadeOrder cascadeOrder, const MatchRequest& matchRequest, RuleRange& ruleRange)
298 {
299     if (m_canUseFastReject && m_selectorFilter.fastRejectSelector<RuleData::maximumIdentifierCount>(ruleData.descendantSelectorIdentifierHashes()))
300         return;
301
302     StyleRule* rule = ruleData.rule();
303     SelectorChecker::MatchResult result;
304     if (ruleMatches(ruleData, matchRequest.scope, behaviorAtBoundary, &result)) {
305         // If the rule has no properties to apply, then ignore it in the non-debug mode.
306         const StylePropertySet* properties = rule->properties();
307         if (!properties || (properties->isEmpty() && !matchRequest.includeEmptyRules))
308             return;
309         // FIXME: Exposing the non-standard getMatchedCSSRules API to web is the only reason this is needed.
310         if (m_sameOriginOnly && !ruleData.hasDocumentSecurityOrigin())
311             return;
312
313         PseudoId dynamicPseudo = result.dynamicPseudo;
314         // If we're matching normal rules, set a pseudo bit if
315         // we really just matched a pseudo-element.
316         if (dynamicPseudo != NOPSEUDO && m_pseudoStyleRequest.pseudoId == NOPSEUDO) {
317             if (m_mode == SelectorChecker::CollectingCSSRules || m_mode == SelectorChecker::CollectingStyleRules)
318                 return;
319             // FIXME: Matching should not modify the style directly.
320             if (m_style && dynamicPseudo < FIRST_INTERNAL_PSEUDOID)
321                 m_style->setHasPseudoStyle(dynamicPseudo);
322         } else {
323             // Update our first/last rule indices in the matched rules array.
324             ++ruleRange.lastRuleIndex;
325             if (ruleRange.firstRuleIndex == -1)
326                 ruleRange.firstRuleIndex = ruleRange.lastRuleIndex;
327
328             // Add this rule to our list of matched rules.
329             addMatchedRule(&ruleData, result.specificity, cascadeScope, cascadeOrder, matchRequest.styleSheetIndex, matchRequest.styleSheet);
330             return;
331         }
332     }
333 }
334
335 void ElementRuleCollector::collectMatchingRulesForList(const RuleData* rules, SelectorChecker::BehaviorAtBoundary behaviorAtBoundary, CascadeScope cascadeScope, CascadeOrder cascadeOrder, const MatchRequest& matchRequest, RuleRange& ruleRange)
336 {
337     if (!rules)
338         return;
339     while (!rules->isLastInArray())
340         collectRuleIfMatches(*rules++, behaviorAtBoundary, cascadeScope, cascadeOrder, matchRequest, ruleRange);
341     collectRuleIfMatches(*rules, behaviorAtBoundary, cascadeScope, cascadeOrder, matchRequest, ruleRange);
342 }
343
344 void ElementRuleCollector::collectMatchingRulesForList(const Vector<RuleData>* rules, SelectorChecker::BehaviorAtBoundary behaviorAtBoundary, CascadeScope cascadeScope, CascadeOrder cascadeOrder, const MatchRequest& matchRequest, RuleRange& ruleRange)
345 {
346     if (!rules)
347         return;
348     unsigned size = rules->size();
349     for (unsigned i = 0; i < size; ++i)
350         collectRuleIfMatches(rules->at(i), behaviorAtBoundary, cascadeScope, cascadeOrder, matchRequest, ruleRange);
351 }
352
353 static inline bool compareRules(const MatchedRule& matchedRule1, const MatchedRule& matchedRule2)
354 {
355     if (matchedRule1.cascadeScope() != matchedRule2.cascadeScope())
356         return matchedRule1.cascadeScope() > matchedRule2.cascadeScope();
357
358     unsigned specificity1 = matchedRule1.specificity();
359     unsigned specificity2 = matchedRule2.specificity();
360     if (specificity1 != specificity2)
361         return specificity1 < specificity2;
362
363     return matchedRule1.position() < matchedRule2.position();
364 }
365
366 void ElementRuleCollector::sortMatchedRules()
367 {
368     ASSERT(m_matchedRules);
369     std::sort(m_matchedRules->begin(), m_matchedRules->end(), compareRules);
370 }
371
372 bool ElementRuleCollector::hasAnyMatchingRules(RuleSet* ruleSet)
373 {
374     clearMatchedRules();
375
376     m_mode = SelectorChecker::SharingRules;
377     // To check whether a given RuleSet has any rule matching a given element,
378     // should not see the element's treescope. Because RuleSet has no
379     // information about "scope".
380     int firstRuleIndex = -1, lastRuleIndex = -1;
381     RuleRange ruleRange(firstRuleIndex, lastRuleIndex);
382     // FIXME: Verify whether it's ok to ignore CascadeScope here.
383     collectMatchingRules(MatchRequest(ruleSet), ruleRange, SelectorChecker::StaysWithinTreeScope);
384
385     return m_matchedRules && !m_matchedRules->isEmpty();
386 }
387
388 } // namespace WebCore