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