Replace static_casts with to* helper functions.
[WebKit-https.git] / Source / WebCore / 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 "ElementRuleCollector.h"
31
32 #include "CSSDefaultStyleSheets.h"
33 #include "CSSRule.h"
34 #include "CSSRuleList.h"
35 #include "CSSSelector.h"
36 #include "CSSSelectorList.h"
37 #include "CSSValueKeywords.h"
38 #include "HTMLElement.h"
39 #include "RenderRegion.h"
40 #include "SVGElement.h"
41 #include "SelectorCheckerFastPath.h"
42 #include "SiblingTraversalStrategies.h"
43 #include "StylePropertySet.h"
44 #include "StyledElement.h"
45
46 #include <wtf/TemporaryChange.h>
47
48 namespace WebCore {
49
50 static StylePropertySet* leftToRightDeclaration()
51 {
52     DEFINE_STATIC_LOCAL(RefPtr<StylePropertySet>, leftToRightDecl, (StylePropertySet::create()));
53     if (leftToRightDecl->isEmpty())
54         leftToRightDecl->setProperty(CSSPropertyDirection, CSSValueLtr);
55     return leftToRightDecl.get();
56 }
57
58 static StylePropertySet* rightToLeftDeclaration()
59 {
60     DEFINE_STATIC_LOCAL(RefPtr<StylePropertySet>, rightToLeftDecl, (StylePropertySet::create()));
61     if (rightToLeftDecl->isEmpty())
62         rightToLeftDecl->setProperty(CSSPropertyDirection, CSSValueRtl);
63     return rightToLeftDecl.get();
64 }
65
66 StyleResolver::MatchResult& ElementRuleCollector::matchedResult()
67 {
68     ASSERT(m_mode == SelectorChecker::ResolvingStyle);
69     return m_result;
70 }
71
72 PassRefPtr<CSSRuleList> ElementRuleCollector::matchedRuleList()
73 {
74     ASSERT(m_mode == SelectorChecker::CollectingRules);
75     return m_ruleList.release();
76 }
77
78 inline void ElementRuleCollector::addMatchedRule(const RuleData* rule)
79 {
80     if (!m_matchedRules)
81         m_matchedRules = adoptPtr(new Vector<const RuleData*, 32>);
82     m_matchedRules->append(rule);
83 }
84
85 inline void ElementRuleCollector::clearMatchedRules()
86 {
87     if (!m_matchedRules)
88         return;
89     m_matchedRules->clear();
90 }
91
92 inline StaticCSSRuleList* ElementRuleCollector::ensureRuleList()
93 {
94     if (!m_ruleList)
95         m_ruleList = StaticCSSRuleList::create();
96     return m_ruleList.get();
97 }
98
99 inline void ElementRuleCollector::addElementStyleProperties(const StylePropertySet* propertySet, bool isCacheable)
100 {
101     if (!propertySet)
102         return;
103     m_result.ranges.lastAuthorRule = m_result.matchedProperties.size();
104     if (m_result.ranges.firstAuthorRule == -1)
105         m_result.ranges.firstAuthorRule = m_result.ranges.lastAuthorRule;
106     m_result.addMatchedProperties(propertySet);
107     if (!isCacheable)
108         m_result.isCacheable = false;
109 }
110
111 class MatchingUARulesScope {
112 public:
113     MatchingUARulesScope();
114     ~MatchingUARulesScope();
115
116     static bool isMatchingUARules();
117
118 private:
119     static bool m_matchingUARules;
120 };
121
122 MatchingUARulesScope::MatchingUARulesScope()
123 {
124     ASSERT(!m_matchingUARules);
125     m_matchingUARules = true;
126 }
127
128 MatchingUARulesScope::~MatchingUARulesScope()
129 {
130     m_matchingUARules = false;
131 }
132
133 inline bool MatchingUARulesScope::isMatchingUARules()
134 {
135     return m_matchingUARules;
136 }
137
138 bool MatchingUARulesScope::m_matchingUARules = false;
139
140 void ElementRuleCollector::collectMatchingRules(const MatchRequest& matchRequest, StyleResolver::RuleRange& ruleRange)
141 {
142     ASSERT(matchRequest.ruleSet);
143     ASSERT(m_state.element());
144
145     const StyleResolver::State& state = m_state;
146     Element* element = state.element();
147     const StyledElement* styledElement = state.styledElement();
148     const AtomicString& pseudoId = element->shadowPseudoId();
149     if (!pseudoId.isEmpty()) {
150         ASSERT(styledElement);
151         collectMatchingRulesForList(matchRequest.ruleSet->shadowPseudoElementRules(pseudoId.impl()), matchRequest, ruleRange);
152     }
153
154 #if ENABLE(VIDEO_TRACK)
155     if (element->isWebVTTElement())
156         collectMatchingRulesForList(matchRequest.ruleSet->cuePseudoRules(), matchRequest, ruleRange);
157 #endif
158     // Check whether other types of rules are applicable in the current tree scope. Criteria for this:
159     // a) it's a UA rule
160     // b) the tree scope allows author rules
161     // c) the rules comes from a scoped style sheet within the same tree scope
162     TreeScope* treeScope = element->treeScope();
163     if (!MatchingUARulesScope::isMatchingUARules()
164         && !treeScope->applyAuthorStyles()
165         && (!matchRequest.scope || matchRequest.scope->treeScope() != treeScope)
166         && m_behaviorAtBoundary == SelectorChecker::DoesNotCrossBoundary)
167         return;
168
169     // We need to collect the rules for id, class, tag, and everything else into a buffer and
170     // then sort the buffer.
171     if (element->hasID())
172         collectMatchingRulesForList(matchRequest.ruleSet->idRules(element->idForStyleResolution().impl()), matchRequest, ruleRange);
173     if (styledElement && styledElement->hasClass()) {
174         for (size_t i = 0; i < styledElement->classNames().size(); ++i)
175             collectMatchingRulesForList(matchRequest.ruleSet->classRules(styledElement->classNames()[i].impl()), matchRequest, ruleRange);
176     }
177
178     if (element->isLink())
179         collectMatchingRulesForList(matchRequest.ruleSet->linkPseudoClassRules(), matchRequest, ruleRange);
180     if (SelectorChecker::matchesFocusPseudoClass(element))
181         collectMatchingRulesForList(matchRequest.ruleSet->focusPseudoClassRules(), matchRequest, ruleRange);
182     collectMatchingRulesForList(matchRequest.ruleSet->tagRules(element->localName().impl()), matchRequest, ruleRange);
183     collectMatchingRulesForList(matchRequest.ruleSet->universalRules(), matchRequest, ruleRange);
184 }
185
186 void ElementRuleCollector::collectMatchingRulesForRegion(const MatchRequest& matchRequest, StyleResolver::RuleRange& ruleRange)
187 {
188     if (!m_regionForStyling)
189         return;
190
191     unsigned size = matchRequest.ruleSet->m_regionSelectorsAndRuleSets.size();
192     for (unsigned i = 0; i < size; ++i) {
193         const CSSSelector* regionSelector = matchRequest.ruleSet->m_regionSelectorsAndRuleSets.at(i).selector;
194         if (checkRegionSelector(regionSelector, toElement(m_regionForStyling->node()))) {
195             RuleSet* regionRules = matchRequest.ruleSet->m_regionSelectorsAndRuleSets.at(i).ruleSet.get();
196             ASSERT(regionRules);
197             collectMatchingRules(MatchRequest(regionRules, matchRequest.includeEmptyRules, matchRequest.scope), ruleRange);
198         }
199     }
200 }
201
202 void ElementRuleCollector::sortAndTransferMatchedRules()
203 {
204     const StyleResolver::State& state = m_state;
205
206     if (!m_matchedRules || m_matchedRules->isEmpty())
207         return;
208
209     sortMatchedRules();
210
211     Vector<const RuleData*, 32>& matchedRules = *m_matchedRules;
212     if (m_mode == SelectorChecker::CollectingRules) {
213         for (unsigned i = 0; i < matchedRules.size(); ++i)
214             ensureRuleList()->rules().append(matchedRules[i]->rule()->createCSSOMWrapper());
215         return;
216     }
217
218     // Now transfer the set of matched rules over to our list of declarations.
219     for (unsigned i = 0; i < matchedRules.size(); i++) {
220         if (state.style() && matchedRules[i]->containsUncommonAttributeSelector())
221             state.style()->setUnique();
222         m_result.addMatchedProperties(matchedRules[i]->rule()->properties(), matchedRules[i]->rule(), matchedRules[i]->linkMatchType(), matchedRules[i]->propertyWhitelistType(MatchingUARulesScope::isMatchingUARules()));
223     }
224 }
225
226 void ElementRuleCollector::matchScopedAuthorRules(bool includeEmptyRules)
227 {
228 #if ENABLE(STYLE_SCOPED) || ENABLE(SHADOW_DOM)
229     if (!m_scopeResolver)
230         return;
231
232     // Match scoped author rules by traversing the scoped element stack (rebuild it if it got inconsistent).
233     if (m_scopeResolver->hasScopedStyles() && m_scopeResolver->ensureStackConsistency(m_state.element())) {
234         bool applyAuthorStyles = m_state.element()->treeScope()->applyAuthorStyles();
235         bool documentScope = true;
236         unsigned scopeSize = m_scopeResolver->stackSize();
237         for (unsigned i = 0; i < scopeSize; ++i) {
238             clearMatchedRules();
239             m_result.ranges.lastAuthorRule = m_result.matchedProperties.size() - 1;
240
241             const StyleScopeResolver::StackFrame& frame = m_scopeResolver->stackFrameAt(i);
242             documentScope = documentScope && !frame.m_scope->isInShadowTree();
243             if (documentScope) {
244                 if (!applyAuthorStyles)
245                     continue;
246             } else {
247                 if (!m_scopeResolver->matchesStyleBounds(frame))
248                     continue;
249             }
250
251             MatchRequest matchRequest(frame.m_ruleSet, includeEmptyRules, frame.m_scope);
252             StyleResolver::RuleRange ruleRange = m_result.ranges.authorRuleRange();
253             collectMatchingRules(matchRequest, ruleRange);
254             collectMatchingRulesForRegion(matchRequest, ruleRange);
255             sortAndTransferMatchedRules();
256         }
257     }
258
259     matchHostRules(includeEmptyRules);
260 #else
261     UNUSED_PARAM(includeEmptyRules);
262 #endif
263 }
264
265 void ElementRuleCollector::matchHostRules(bool includeEmptyRules)
266 {
267 #if ENABLE(SHADOW_DOM)
268     ASSERT(m_scopeResolver);
269
270     clearMatchedRules();
271     m_result.ranges.lastAuthorRule = m_result.matchedProperties.size() - 1;
272
273     Vector<RuleSet*> matchedRules;
274     m_scopeResolver->matchHostRules(m_state.element(), matchedRules);
275     if (matchedRules.isEmpty())
276         return;
277
278     for (unsigned i = matchedRules.size(); i > 0; --i) {
279         StyleResolver::RuleRange ruleRange = m_result.ranges.authorRuleRange();
280         collectMatchingRules(MatchRequest(matchedRules.at(i-1), includeEmptyRules, m_state.element()), ruleRange);
281     }
282     sortAndTransferMatchedRules();
283 #else
284     UNUSED_PARAM(includeEmptyRules);
285 #endif
286 }
287
288 #if ENABLE(SHADOW_DOM)
289 inline void ElementRuleCollector::matchShadowDistributedRules(bool includeEmptyRules, StyleResolver::RuleRange& ruleRange)
290 {
291     if (m_ruleSets.shadowDistributedRules().isEmpty())
292         return;
293
294     TemporaryChange<bool> canUseFastReject(m_canUseFastReject, false);
295     TemporaryChange<SelectorChecker::BehaviorAtBoundary> behaviorAtBoundary(m_behaviorAtBoundary, SelectorChecker::CrossesBoundary);
296
297     Vector<MatchRequest> matchRequests;
298     m_ruleSets.shadowDistributedRules().collectMatchRequests(includeEmptyRules, matchRequests);
299     for (size_t i = 0; i < matchRequests.size(); ++i)
300         collectMatchingRules(matchRequests[i], ruleRange);
301 }
302 #endif
303
304 void ElementRuleCollector::matchAuthorRules(bool includeEmptyRules)
305 {
306     clearMatchedRules();
307     m_result.ranges.lastAuthorRule = m_result.matchedProperties.size() - 1;
308
309     if (!m_state.element())
310         return;
311
312     // Match global author rules.
313     MatchRequest matchRequest(m_ruleSets.authorStyle(), includeEmptyRules);
314     StyleResolver::RuleRange ruleRange = m_result.ranges.authorRuleRange();
315     collectMatchingRules(matchRequest, ruleRange);
316     collectMatchingRulesForRegion(matchRequest, ruleRange);
317 #if ENABLE(SHADOW_DOM)
318     matchShadowDistributedRules(includeEmptyRules, ruleRange);
319 #endif
320     sortAndTransferMatchedRules();
321
322     matchScopedAuthorRules(includeEmptyRules);
323 }
324
325 void ElementRuleCollector::matchUserRules(bool includeEmptyRules)
326 {
327     if (!m_ruleSets.userStyle())
328         return;
329     
330     clearMatchedRules();
331
332     m_result.ranges.lastUserRule = m_result.matchedProperties.size() - 1;
333     MatchRequest matchRequest(m_ruleSets.userStyle(), includeEmptyRules);
334     StyleResolver::RuleRange ruleRange = m_result.ranges.userRuleRange();
335     collectMatchingRules(matchRequest, ruleRange);
336     collectMatchingRulesForRegion(matchRequest, ruleRange);
337
338     sortAndTransferMatchedRules();
339 }
340
341 void ElementRuleCollector::matchUARules()
342 {
343     MatchingUARulesScope scope;
344
345     // First we match rules from the user agent sheet.
346     if (CSSDefaultStyleSheets::simpleDefaultStyleSheet)
347         m_result.isCacheable = false;
348     RuleSet* userAgentStyleSheet = m_isPrintStyle
349         ? CSSDefaultStyleSheets::defaultPrintStyle : CSSDefaultStyleSheets::defaultStyle;
350     matchUARules(userAgentStyleSheet);
351
352     // In quirks mode, we match rules from the quirks user agent sheet.
353     if (document()->inQuirksMode())
354         matchUARules(CSSDefaultStyleSheets::defaultQuirksStyle);
355
356     // If document uses view source styles (in view source mode or in xml viewer mode), then we match rules from the view source style sheet.
357     if (document()->isViewSource())
358         matchUARules(CSSDefaultStyleSheets::viewSourceStyle());
359 }
360
361 void ElementRuleCollector::matchUARules(RuleSet* rules)
362 {
363     clearMatchedRules();
364     
365     m_result.ranges.lastUARule = m_result.matchedProperties.size() - 1;
366     StyleResolver::RuleRange ruleRange = m_result.ranges.UARuleRange();
367     collectMatchingRules(MatchRequest(rules), ruleRange);
368
369     sortAndTransferMatchedRules();
370 }
371
372 inline bool ElementRuleCollector::ruleMatches(const RuleData& ruleData, const ContainerNode* scope, PseudoId& dynamicPseudo)
373 {
374     const StyleResolver::State& state = m_state;
375
376     if (ruleData.hasFastCheckableSelector()) {
377         // We know this selector does not include any pseudo elements.
378         if (m_pseudoStyleRequest.pseudoId != NOPSEUDO)
379             return false;
380         // We know a sufficiently simple single part selector matches simply because we found it from the rule hash.
381         // This is limited to HTML only so we don't need to check the namespace.
382         if (ruleData.hasRightmostSelectorMatchingHTMLBasedOnRuleHash() && state.element()->isHTMLElement()) {
383             if (!ruleData.hasMultipartSelector())
384                 return true;
385         }
386         if (ruleData.selector()->m_match == CSSSelector::Tag && !SelectorChecker::tagMatches(state.element(), ruleData.selector()->tagQName()))
387             return false;
388         SelectorCheckerFastPath selectorCheckerFastPath(ruleData.selector(), state.element());
389         if (!selectorCheckerFastPath.matchesRightmostAttributeSelector())
390             return false;
391
392         return selectorCheckerFastPath.matches();
393     }
394
395     // Slow path.
396     SelectorChecker selectorChecker(document(), m_mode);
397     SelectorChecker::SelectorCheckingContext context(ruleData.selector(), state.element(), SelectorChecker::VisitedMatchEnabled);
398     context.elementStyle = state.style();
399     context.scope = scope;
400     context.pseudoId = m_pseudoStyleRequest.pseudoId;
401     context.scrollbar = m_pseudoStyleRequest.scrollbar;
402     context.scrollbarPart = m_pseudoStyleRequest.scrollbarPart;
403     context.behaviorAtBoundary = m_behaviorAtBoundary;
404     SelectorChecker::Match match = selectorChecker.match(context, dynamicPseudo, DOMSiblingTraversalStrategy());
405     if (match != SelectorChecker::SelectorMatches)
406         return false;
407     if (m_pseudoStyleRequest.pseudoId != NOPSEUDO && m_pseudoStyleRequest.pseudoId != dynamicPseudo)
408         return false;
409     return true;
410 }
411
412 void ElementRuleCollector::collectMatchingRulesForList(const Vector<RuleData>* rules, const MatchRequest& matchRequest, StyleResolver::RuleRange& ruleRange)
413 {
414     if (!rules)
415         return;
416
417     const StyleResolver::State& state = m_state;
418
419     unsigned size = rules->size();
420     for (unsigned i = 0; i < size; ++i) {
421         const RuleData& ruleData = rules->at(i);
422         if (m_canUseFastReject && m_selectorFilter.fastRejectSelector<RuleData::maximumIdentifierCount>(ruleData.descendantSelectorIdentifierHashes()))
423             continue;
424
425         StyleRule* rule = ruleData.rule();
426         InspectorInstrumentationCookie cookie = InspectorInstrumentation::willMatchRule(document(), rule, m_inspectorCSSOMWrappers, document()->styleSheetCollection());
427         PseudoId dynamicPseudo = NOPSEUDO;
428         if (ruleMatches(ruleData, matchRequest.scope, dynamicPseudo)) {
429             // If the rule has no properties to apply, then ignore it in the non-debug mode.
430             const StylePropertySet* properties = rule->properties();
431             if (!properties || (properties->isEmpty() && !matchRequest.includeEmptyRules)) {
432                 InspectorInstrumentation::didMatchRule(cookie, false);
433                 continue;
434             }
435             // FIXME: Exposing the non-standard getMatchedCSSRules API to web is the only reason this is needed.
436             if (m_sameOriginOnly && !ruleData.hasDocumentSecurityOrigin()) {
437                 InspectorInstrumentation::didMatchRule(cookie, false);
438                 continue;
439             }
440             // If we're matching normal rules, set a pseudo bit if
441             // we really just matched a pseudo-element.
442             if (dynamicPseudo != NOPSEUDO && m_pseudoStyleRequest.pseudoId == NOPSEUDO) {
443                 if (m_mode == SelectorChecker::CollectingRules) {
444                     InspectorInstrumentation::didMatchRule(cookie, false);
445                     continue;
446                 }
447                 if (dynamicPseudo < FIRST_INTERNAL_PSEUDOID)
448                     state.style()->setHasPseudoStyle(dynamicPseudo);
449             } else {
450                 // Update our first/last rule indices in the matched rules array.
451                 ++ruleRange.lastRuleIndex;
452                 if (ruleRange.firstRuleIndex == -1)
453                     ruleRange.firstRuleIndex = ruleRange.lastRuleIndex;
454
455                 // Add this rule to our list of matched rules.
456                 addMatchedRule(&ruleData);
457                 InspectorInstrumentation::didMatchRule(cookie, true);
458                 continue;
459             }
460         }
461         InspectorInstrumentation::didMatchRule(cookie, false);
462     }
463 }
464
465 static inline bool compareRules(const RuleData* r1, const RuleData* r2)
466 {
467     unsigned specificity1 = r1->specificity();
468     unsigned specificity2 = r2->specificity();
469     return (specificity1 == specificity2) ? r1->position() < r2->position() : specificity1 < specificity2;
470 }
471
472 void ElementRuleCollector::sortMatchedRules()
473 {
474     ASSERT(m_matchedRules);
475     std::sort(m_matchedRules->begin(), m_matchedRules->end(), compareRules);
476 }
477
478 void ElementRuleCollector::matchAllRules(bool matchAuthorAndUserStyles, bool includeSMILProperties)
479 {
480     matchUARules();
481
482     // Now we check user sheet rules.
483     if (matchAuthorAndUserStyles)
484         matchUserRules(false);
485
486     // Now check author rules, beginning first with presentational attributes mapped from HTML.
487     if (m_state.styledElement()) {
488         addElementStyleProperties(m_state.styledElement()->presentationAttributeStyle());
489
490         // Now we check additional mapped declarations.
491         // Tables and table cells share an additional mapped rule that must be applied
492         // after all attributes, since their mapped style depends on the values of multiple attributes.
493         addElementStyleProperties(m_state.styledElement()->additionalPresentationAttributeStyle());
494
495         if (m_state.styledElement()->isHTMLElement()) {
496             bool isAuto;
497             TextDirection textDirection = toHTMLElement(m_state.styledElement())->directionalityIfhasDirAutoAttribute(isAuto);
498             if (isAuto)
499                 m_result.addMatchedProperties(textDirection == LTR ? leftToRightDeclaration() : rightToLeftDeclaration());
500         }
501     }
502     
503     // Check the rules in author sheets next.
504     if (matchAuthorAndUserStyles)
505         matchAuthorRules(false);
506
507     // Now check our inline style attribute.
508     if (matchAuthorAndUserStyles && m_state.styledElement() && m_state.styledElement()->inlineStyle()) {
509         // Inline style is immutable as long as there is no CSSOM wrapper.
510         // FIXME: Media control shadow trees seem to have problems with caching.
511         bool isInlineStyleCacheable = !m_state.styledElement()->inlineStyle()->isMutable() && !m_state.styledElement()->isInShadowTree();
512         // FIXME: Constify.
513         addElementStyleProperties(m_state.styledElement()->inlineStyle(), isInlineStyleCacheable);
514     }
515
516 #if ENABLE(SVG)
517     // Now check SMIL animation override style.
518     if (includeSMILProperties && matchAuthorAndUserStyles && m_state.styledElement() && m_state.styledElement()->isSVGElement())
519         addElementStyleProperties(toSVGElement(m_state.styledElement())->animatedSMILStyleProperties(), false /* isCacheable */);
520 #else
521     UNUSED_PARAM(includeSMILProperties);
522 #endif
523 }
524
525 bool ElementRuleCollector::hasAnyMatchingRules(RuleSet* ruleSet)
526 {
527     clearMatchedRules();
528
529     m_mode = SelectorChecker::SharingRules;
530     int firstRuleIndex = -1, lastRuleIndex = -1;
531     StyleResolver::RuleRange ruleRange(firstRuleIndex, lastRuleIndex);
532     collectMatchingRules(MatchRequest(ruleSet), ruleRange);
533
534     return m_matchedRules && !m_matchedRules->isEmpty();
535 }
536
537 } // namespace WebCore