1/*
2 * Copyright (C) 1999 Lars Knoll (knoll@kde.org)
3 *           (C) 1999 Antti Koivisto (koivisto@kde.org)
4 *           (C) 2001 Dirk Mueller (mueller@kde.org)
5 *           (C) 2006 Alexey Proskuryakov (ap@webkit.org)
6 * Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2012 Apple Inc. All rights reserved.
7 * Copyright (C) 2008, 2009 Torch Mobile Inc. All rights reserved. (http://www.torchmobile.com/)
8 * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies)
9 * Copyright (C) 2013 Google Inc. All rights reserved.
10 *
11 * This library is free software; you can redistribute it and/or
12 * modify it under the terms of the GNU Library General Public
13 * License as published by the Free Software Foundation; either
14 * version 2 of the License, or (at your option) any later version.
15 *
16 * This library is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
19 * Library General Public License for more details.
20 *
21 * You should have received a copy of the GNU Library General Public License
22 * along with this library; see the file COPYING.LIB.  If not, write to
23 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
24 * Boston, MA 02110-1301, USA.
25 */
26
27#include "config.h"
28#include "core/dom/TreeScopeStyleSheetCollection.h"
29
30#include "core/css/CSSStyleSheet.h"
31#include "core/css/StyleRuleImport.h"
32#include "core/css/StyleSheetContents.h"
33#include "core/css/invalidation/StyleSheetInvalidationAnalysis.h"
34#include "core/css/resolver/StyleResolver.h"
35#include "core/dom/Element.h"
36#include "core/dom/StyleEngine.h"
37#include "core/html/HTMLLinkElement.h"
38#include "core/html/HTMLStyleElement.h"
39
40namespace blink {
41
42TreeScopeStyleSheetCollection::TreeScopeStyleSheetCollection(TreeScope& treeScope)
43    : m_treeScope(treeScope)
44    , m_hadActiveLoadingStylesheet(false)
45    , m_usesRemUnits(false)
46{
47}
48
49void TreeScopeStyleSheetCollection::addStyleSheetCandidateNode(Node* node, bool createdByParser)
50{
51    if (!node->inDocument())
52        return;
53
54    // Until the <body> exists, we have no choice but to compare document positions,
55    // since styles outside of the body and head continue to be shunted into the head
56    // (and thus can shift to end up before dynamically added DOM content that is also
57    // outside the body).
58    if (createdByParser && document().body())
59        m_styleSheetCandidateNodes.parserAdd(node);
60    else
61        m_styleSheetCandidateNodes.add(node);
62}
63
64void TreeScopeStyleSheetCollection::removeStyleSheetCandidateNode(Node* node, ContainerNode* scopingNode)
65{
66    m_styleSheetCandidateNodes.remove(node);
67}
68
69TreeScopeStyleSheetCollection::StyleResolverUpdateType TreeScopeStyleSheetCollection::compareStyleSheets(const WillBeHeapVector<RefPtrWillBeMember<CSSStyleSheet> >& oldStyleSheets, const WillBeHeapVector<RefPtrWillBeMember<CSSStyleSheet> >& newStylesheets, WillBeHeapVector<RawPtrWillBeMember<StyleSheetContents> >& addedSheets)
70{
71    unsigned newStyleSheetCount = newStylesheets.size();
72    unsigned oldStyleSheetCount = oldStyleSheets.size();
73    ASSERT(newStyleSheetCount >= oldStyleSheetCount);
74
75    if (!newStyleSheetCount)
76        return Reconstruct;
77
78    unsigned newIndex = 0;
79    for (unsigned oldIndex = 0; oldIndex < oldStyleSheetCount; ++oldIndex) {
80        while (oldStyleSheets[oldIndex] != newStylesheets[newIndex]) {
81            addedSheets.append(newStylesheets[newIndex]->contents());
82            if (++newIndex == newStyleSheetCount)
83                return Reconstruct;
84        }
85        if (++newIndex == newStyleSheetCount)
86            return Reconstruct;
87    }
88    bool hasInsertions = !addedSheets.isEmpty();
89    while (newIndex < newStyleSheetCount) {
90        addedSheets.append(newStylesheets[newIndex]->contents());
91        ++newIndex;
92    }
93    // If all new sheets were added at the end of the list we can just add them to existing StyleResolver.
94    // If there were insertions we need to re-add all the stylesheets so rules are ordered correctly.
95    return hasInsertions ? Reset : Additive;
96}
97
98bool TreeScopeStyleSheetCollection::activeLoadingStyleSheetLoaded(const WillBeHeapVector<RefPtrWillBeMember<CSSStyleSheet> >& newStyleSheets)
99{
100    // StyleSheets of <style> elements that @import stylesheets are active but loading. We need to trigger a full recalc when such loads are done.
101    bool hasActiveLoadingStylesheet = false;
102    unsigned newStylesheetCount = newStyleSheets.size();
103    for (unsigned i = 0; i < newStylesheetCount; ++i) {
104        if (newStyleSheets[i]->isLoading())
105            hasActiveLoadingStylesheet = true;
106    }
107    if (m_hadActiveLoadingStylesheet && !hasActiveLoadingStylesheet) {
108        m_hadActiveLoadingStylesheet = false;
109        return true;
110    }
111    m_hadActiveLoadingStylesheet = hasActiveLoadingStylesheet;
112    return false;
113}
114
115static bool findFontFaceRulesFromStyleSheetContents(const WillBeHeapVector<RawPtrWillBeMember<StyleSheetContents> >& sheets, WillBeHeapVector<RawPtrWillBeMember<const StyleRuleFontFace> >& fontFaceRules)
116{
117    bool hasFontFaceRule = false;
118
119    for (unsigned i = 0; i < sheets.size(); ++i) {
120        ASSERT(sheets[i]);
121        if (sheets[i]->hasFontFaceRule()) {
122            // FIXME: We don't need this for styles in shadow tree.
123            sheets[i]->findFontFaceRules(fontFaceRules);
124            hasFontFaceRule = true;
125        }
126    }
127    return hasFontFaceRule;
128}
129
130void TreeScopeStyleSheetCollection::analyzeStyleSheetChange(StyleResolverUpdateMode updateMode, const StyleSheetCollection& newCollection, StyleSheetChange& change)
131{
132    if (activeLoadingStyleSheetLoaded(newCollection.activeAuthorStyleSheets()))
133        return;
134
135    if (updateMode != AnalyzedStyleUpdate)
136        return;
137
138    // Find out which stylesheets are new.
139    WillBeHeapVector<RawPtrWillBeMember<StyleSheetContents> > addedSheets;
140    if (m_activeAuthorStyleSheets.size() <= newCollection.activeAuthorStyleSheets().size()) {
141        change.styleResolverUpdateType = compareStyleSheets(m_activeAuthorStyleSheets, newCollection.activeAuthorStyleSheets(), addedSheets);
142    } else {
143        StyleResolverUpdateType updateType = compareStyleSheets(newCollection.activeAuthorStyleSheets(), m_activeAuthorStyleSheets, addedSheets);
144        if (updateType != Additive) {
145            change.styleResolverUpdateType = updateType;
146        } else {
147            change.styleResolverUpdateType = Reset;
148            // If @font-face is removed, needs full style recalc.
149            if (findFontFaceRulesFromStyleSheetContents(addedSheets, change.fontFaceRulesToRemove))
150                return;
151        }
152    }
153
154    // FIXME: If styleResolverUpdateType is Reconstruct, we should return early here since
155    // we need to recalc the whole document. It's wrong to use StyleSheetInvalidationAnalysis since
156    // it only looks at the addedSheets.
157
158    // No point in doing the analysis work if we're just going to recalc the whole document anyways.
159    // This needs to be done after the compareStyleSheets calls above to ensure we don't throw away
160    // the StyleResolver if we don't need to.
161    if (document().hasPendingForcedStyleRecalc())
162        return;
163
164    // If we are already parsing the body and so may have significant amount of elements, put some effort into trying to avoid style recalcs.
165    if (!document().body() || document().hasNodesWithPlaceholderStyle())
166        return;
167    StyleSheetInvalidationAnalysis invalidationAnalysis(addedSheets);
168    if (invalidationAnalysis.dirtiesAllStyle())
169        return;
170    invalidationAnalysis.invalidateStyle(document());
171    change.requiresFullStyleRecalc = false;
172    return;
173}
174
175void TreeScopeStyleSheetCollection::clearMediaQueryRuleSetStyleSheets()
176{
177    for (size_t i = 0; i < m_activeAuthorStyleSheets.size(); ++i) {
178        StyleSheetContents* contents = m_activeAuthorStyleSheets[i]->contents();
179        if (contents->hasMediaQueries())
180            contents->clearRuleSet();
181    }
182}
183
184void TreeScopeStyleSheetCollection::enableExitTransitionStylesheets()
185{
186    DocumentOrderedList::iterator begin = m_styleSheetCandidateNodes.begin();
187    DocumentOrderedList::iterator end = m_styleSheetCandidateNodes.end();
188    for (DocumentOrderedList::iterator it = begin; it != end; ++it) {
189        Node* node = *it;
190        if (isHTMLLinkElement(*node))
191            toHTMLLinkElement(node)->enableIfExitTransitionStyle();
192    }
193}
194
195static bool styleSheetsUseRemUnits(const WillBeHeapVector<RefPtrWillBeMember<CSSStyleSheet> >& sheets)
196{
197    for (unsigned i = 0; i < sheets.size(); ++i) {
198        if (sheets[i]->contents()->usesRemUnits())
199            return true;
200    }
201    return false;
202}
203
204void TreeScopeStyleSheetCollection::updateUsesRemUnits()
205{
206    m_usesRemUnits = styleSheetsUseRemUnits(m_activeAuthorStyleSheets);
207}
208
209void TreeScopeStyleSheetCollection::trace(Visitor* visitor)
210{
211    visitor->trace(m_treeScope);
212    visitor->trace(m_styleSheetCandidateNodes);
213    StyleSheetCollection::trace(visitor);
214}
215
216}
217