1/*
2 * Copyright (C) 2012 Apple Inc. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 * 1. Redistributions of source code must retain the above copyright
8 *    notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 *    notice, this list of conditions and the following disclaimer in the
11 *    documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26#include "config.h"
27#include "StyleInvalidationAnalysis.h"
28
29#include "CSSSelectorList.h"
30#include "Document.h"
31#include "NodeTraversal.h"
32#include "StyleRuleImport.h"
33#include "StyleSheetContents.h"
34#include "StyledElement.h"
35
36namespace WebCore {
37
38StyleInvalidationAnalysis::StyleInvalidationAnalysis(const Vector<StyleSheetContents*>& sheets)
39    : m_dirtiesAllStyle(false)
40{
41    for (unsigned i = 0; i < sheets.size() && !m_dirtiesAllStyle; ++i)
42        analyzeStyleSheet(sheets[i]);
43}
44
45static bool determineSelectorScopes(const CSSSelectorList& selectorList, HashSet<AtomicStringImpl*>& idScopes, HashSet<AtomicStringImpl*>& classScopes)
46{
47    for (const CSSSelector* selector = selectorList.first(); selector; selector = CSSSelectorList::next(selector)) {
48        const CSSSelector* scopeSelector = 0;
49        // This picks the widest scope, not the narrowest, to minimize the number of found scopes.
50        for (const CSSSelector* current = selector; current; current = current->tagHistory()) {
51            // Prefer ids over classes.
52            if (current->m_match == CSSSelector::Id)
53                scopeSelector = current;
54            else if (current->m_match == CSSSelector::Class && (!scopeSelector || scopeSelector->m_match != CSSSelector::Id))
55                scopeSelector = current;
56            CSSSelector::Relation relation = current->relation();
57            if (relation != CSSSelector::Descendant && relation != CSSSelector::Child && relation != CSSSelector::SubSelector)
58                break;
59        }
60        if (!scopeSelector)
61            return false;
62        ASSERT(scopeSelector->m_match == CSSSelector::Class || scopeSelector->m_match == CSSSelector::Id);
63        if (scopeSelector->m_match == CSSSelector::Id)
64            idScopes.add(scopeSelector->value().impl());
65        else
66            classScopes.add(scopeSelector->value().impl());
67    }
68    return true;
69}
70
71void StyleInvalidationAnalysis::analyzeStyleSheet(StyleSheetContents* styleSheetContents)
72{
73    ASSERT(!styleSheetContents->isLoading());
74
75    // See if all rules on the sheet are scoped to some specific ids or classes.
76    // Then test if we actually have any of those in the tree at the moment.
77    const Vector<RefPtr<StyleRuleImport> >& importRules = styleSheetContents->importRules();
78    for (unsigned i = 0; i < importRules.size(); ++i) {
79        if (!importRules[i]->styleSheet())
80            continue;
81        analyzeStyleSheet(importRules[i]->styleSheet());
82        if (m_dirtiesAllStyle)
83            return;
84    }
85    const Vector<RefPtr<StyleRuleBase> >& rules = styleSheetContents->childRules();
86    for (unsigned i = 0; i < rules.size(); i++) {
87        StyleRuleBase* rule = rules[i].get();
88        if (!rule->isStyleRule()) {
89            // FIXME: Media rules and maybe some others could be allowed.
90            m_dirtiesAllStyle = true;
91            return;
92        }
93        StyleRule* styleRule = static_cast<StyleRule*>(rule);
94        if (!determineSelectorScopes(styleRule->selectorList(), m_idScopes, m_classScopes)) {
95            m_dirtiesAllStyle = true;
96            return;
97        }
98    }
99}
100
101static bool elementMatchesSelectorScopes(const Element* element, const HashSet<AtomicStringImpl*>& idScopes, const HashSet<AtomicStringImpl*>& classScopes)
102{
103    if (!idScopes.isEmpty() && element->hasID() && idScopes.contains(element->idForStyleResolution().impl()))
104        return true;
105    if (classScopes.isEmpty() || !element->hasClass())
106        return false;
107    const SpaceSplitString& classNames = element->classNames();
108    for (unsigned i = 0; i < classNames.size(); ++i) {
109        if (classScopes.contains(classNames[i].impl()))
110            return true;
111    }
112    return false;
113}
114
115void StyleInvalidationAnalysis::invalidateStyle(Document* document)
116{
117    ASSERT(!m_dirtiesAllStyle);
118    if (m_idScopes.isEmpty() && m_classScopes.isEmpty())
119        return;
120    Element* element = ElementTraversal::firstWithin(document);
121    while (element) {
122        if (elementMatchesSelectorScopes(element, m_idScopes, m_classScopes)) {
123            element->setNeedsStyleRecalc();
124            // The whole subtree is now invalidated, we can skip to the next sibling.
125            element = ElementTraversal::nextSkippingChildren(element);
126            continue;
127        }
128        element = ElementTraversal::next(element);
129    }
130}
131
132}
133