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, 2011, 2012 Apple Inc. All rights reserved.
7 * Copyright (C) 2008, 2009 Torch Mobile Inc. All rights reserved. (http://www.torchmobile.com/)
8 * Copyright (C) 2008, 2009, 2011, 2012 Google Inc. All rights reserved.
9 * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies)
10 * Copyright (C) Research In Motion Limited 2010-2011. All rights reserved.
11 *
12 * This library is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU Library General Public
14 * License as published by the Free Software Foundation; either
15 * version 2 of the License, or (at your option) any later version.
16 *
17 * This library is distributed in the hope that it will be useful,
18 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
20 * Library General Public License for more details.
21 *
22 * You should have received a copy of the GNU Library General Public License
23 * along with this library; see the file COPYING.LIB.  If not, write to
24 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
25 * Boston, MA 02110-1301, USA.
26 */
27
28#include "config.h"
29#include "DocumentStyleSheetCollection.h"
30
31#include "CSSStyleSheet.h"
32#include "DOMWrapperWorld.h"
33#include "Document.h"
34#include "Element.h"
35#include "HTMLIFrameElement.h"
36#include "HTMLLinkElement.h"
37#include "HTMLNames.h"
38#include "HTMLStyleElement.h"
39#include "Page.h"
40#include "PageGroup.h"
41#include "ProcessingInstruction.h"
42#include "SVGNames.h"
43#include "SVGStyleElement.h"
44#include "SelectorChecker.h"
45#include "Settings.h"
46#include "StyleInvalidationAnalysis.h"
47#include "StyleResolver.h"
48#include "StyleSheetContents.h"
49#include "StyleSheetList.h"
50#include "UserContentURLPattern.h"
51
52namespace WebCore {
53
54using namespace HTMLNames;
55
56DocumentStyleSheetCollection::DocumentStyleSheetCollection(Document* document)
57    : m_document(document)
58    , m_pendingStylesheets(0)
59    , m_injectedStyleSheetCacheValid(false)
60    , m_hadActiveLoadingStylesheet(false)
61    , m_pendingUpdateType(NoUpdate)
62    , m_usesSiblingRules(false)
63    , m_usesSiblingRulesOverride(false)
64    , m_usesFirstLineRules(false)
65    , m_usesFirstLetterRules(false)
66    , m_usesBeforeAfterRules(false)
67    , m_usesBeforeAfterRulesOverride(false)
68    , m_usesRemUnits(false)
69{
70}
71
72DocumentStyleSheetCollection::~DocumentStyleSheetCollection()
73{
74    if (m_pageUserSheet)
75        m_pageUserSheet->clearOwnerNode();
76    for (unsigned i = 0; i < m_injectedUserStyleSheets.size(); ++i)
77        m_injectedUserStyleSheets[i]->clearOwnerNode();
78    for (unsigned i = 0; i < m_injectedAuthorStyleSheets.size(); ++i)
79        m_injectedAuthorStyleSheets[i]->clearOwnerNode();
80    for (unsigned i = 0; i < m_userStyleSheets.size(); ++i)
81        m_userStyleSheets[i]->clearOwnerNode();
82    for (unsigned i = 0; i < m_authorStyleSheets.size(); ++i)
83        m_authorStyleSheets[i]->clearOwnerNode();
84}
85
86void DocumentStyleSheetCollection::combineCSSFeatureFlags()
87{
88    // Delay resetting the flags until after next style recalc since unapplying the style may not work without these set (this is true at least with before/after).
89    StyleResolver* styleResolver = m_document->ensureStyleResolver();
90    m_usesSiblingRules = m_usesSiblingRules || styleResolver->usesSiblingRules();
91    m_usesFirstLineRules = m_usesFirstLineRules || styleResolver->usesFirstLineRules();
92    m_usesBeforeAfterRules = m_usesBeforeAfterRules || styleResolver->usesBeforeAfterRules();
93}
94
95void DocumentStyleSheetCollection::resetCSSFeatureFlags()
96{
97    StyleResolver* styleResolver = m_document->ensureStyleResolver();
98    m_usesSiblingRules = styleResolver->usesSiblingRules();
99    m_usesFirstLineRules = styleResolver->usesFirstLineRules();
100    m_usesBeforeAfterRules = styleResolver->usesBeforeAfterRules();
101}
102
103CSSStyleSheet* DocumentStyleSheetCollection::pageUserSheet()
104{
105    if (m_pageUserSheet)
106        return m_pageUserSheet.get();
107
108    Page* owningPage = m_document->page();
109    if (!owningPage)
110        return 0;
111
112    String userSheetText = owningPage->userStyleSheet();
113    if (userSheetText.isEmpty())
114        return 0;
115
116    // Parse the sheet and cache it.
117    m_pageUserSheet = CSSStyleSheet::createInline(m_document, m_document->settings()->userStyleSheetLocation());
118    m_pageUserSheet->contents()->setIsUserStyleSheet(true);
119    m_pageUserSheet->contents()->parseString(userSheetText);
120    return m_pageUserSheet.get();
121}
122
123void DocumentStyleSheetCollection::clearPageUserSheet()
124{
125    if (m_pageUserSheet) {
126        m_pageUserSheet = 0;
127        m_document->styleResolverChanged(DeferRecalcStyle);
128    }
129}
130
131void DocumentStyleSheetCollection::updatePageUserSheet()
132{
133    clearPageUserSheet();
134    if (pageUserSheet())
135        m_document->styleResolverChanged(RecalcStyleImmediately);
136}
137
138const Vector<RefPtr<CSSStyleSheet> >& DocumentStyleSheetCollection::injectedUserStyleSheets() const
139{
140    updateInjectedStyleSheetCache();
141    return m_injectedUserStyleSheets;
142}
143
144const Vector<RefPtr<CSSStyleSheet> >& DocumentStyleSheetCollection::injectedAuthorStyleSheets() const
145{
146    updateInjectedStyleSheetCache();
147    return m_injectedAuthorStyleSheets;
148}
149
150void DocumentStyleSheetCollection::updateInjectedStyleSheetCache() const
151{
152    if (m_injectedStyleSheetCacheValid)
153        return;
154    m_injectedStyleSheetCacheValid = true;
155    m_injectedUserStyleSheets.clear();
156    m_injectedAuthorStyleSheets.clear();
157
158    Page* owningPage = m_document->page();
159    if (!owningPage)
160        return;
161
162    const PageGroup& pageGroup = owningPage->group();
163    const UserStyleSheetMap* sheetsMap = pageGroup.userStyleSheets();
164    if (!sheetsMap)
165        return;
166
167    UserStyleSheetMap::const_iterator end = sheetsMap->end();
168    for (UserStyleSheetMap::const_iterator it = sheetsMap->begin(); it != end; ++it) {
169        const UserStyleSheetVector* sheets = it->value.get();
170        for (unsigned i = 0; i < sheets->size(); ++i) {
171            const UserStyleSheet* sheet = sheets->at(i).get();
172            if (sheet->injectedFrames() == InjectInTopFrameOnly && m_document->ownerElement())
173                continue;
174            if (!UserContentURLPattern::matchesPatterns(m_document->url(), sheet->whitelist(), sheet->blacklist()))
175                continue;
176            RefPtr<CSSStyleSheet> groupSheet = CSSStyleSheet::createInline(const_cast<Document*>(m_document), sheet->url());
177            bool isUserStyleSheet = sheet->level() == UserStyleUserLevel;
178            if (isUserStyleSheet)
179                m_injectedUserStyleSheets.append(groupSheet);
180            else
181                m_injectedAuthorStyleSheets.append(groupSheet);
182            groupSheet->contents()->setIsUserStyleSheet(isUserStyleSheet);
183            groupSheet->contents()->parseString(sheet->source());
184        }
185    }
186}
187
188void DocumentStyleSheetCollection::invalidateInjectedStyleSheetCache()
189{
190    if (!m_injectedStyleSheetCacheValid)
191        return;
192    m_injectedStyleSheetCacheValid = false;
193    if (m_injectedUserStyleSheets.isEmpty() && m_injectedAuthorStyleSheets.isEmpty())
194        return;
195    m_document->styleResolverChanged(DeferRecalcStyle);
196}
197
198void DocumentStyleSheetCollection::addAuthorSheet(PassRefPtr<StyleSheetContents> authorSheet)
199{
200    ASSERT(!authorSheet->isUserStyleSheet());
201    m_authorStyleSheets.append(CSSStyleSheet::create(authorSheet, m_document));
202    m_document->styleResolverChanged(RecalcStyleImmediately);
203}
204
205void DocumentStyleSheetCollection::addUserSheet(PassRefPtr<StyleSheetContents> userSheet)
206{
207    ASSERT(userSheet->isUserStyleSheet());
208    m_userStyleSheets.append(CSSStyleSheet::create(userSheet, m_document));
209    m_document->styleResolverChanged(RecalcStyleImmediately);
210}
211
212// This method is called whenever a top-level stylesheet has finished loading.
213void DocumentStyleSheetCollection::removePendingSheet(RemovePendingSheetNotificationType notification)
214{
215    // Make sure we knew this sheet was pending, and that our count isn't out of sync.
216    ASSERT(m_pendingStylesheets > 0);
217
218    m_pendingStylesheets--;
219
220#ifdef INSTRUMENT_LAYOUT_SCHEDULING
221    if (!ownerElement())
222        printf("Stylesheet loaded at time %d. %d stylesheets still remain.\n", elapsedTime(), m_pendingStylesheets);
223#endif
224
225    if (m_pendingStylesheets)
226        return;
227
228    if (notification == RemovePendingSheetNotifyLater) {
229        m_document->setNeedsNotifyRemoveAllPendingStylesheet();
230        return;
231    }
232
233    m_document->didRemoveAllPendingStylesheet();
234}
235
236void DocumentStyleSheetCollection::addStyleSheetCandidateNode(Node* node, bool createdByParser)
237{
238    if (!node->inDocument())
239        return;
240
241    // Until the <body> exists, we have no choice but to compare document positions,
242    // since styles outside of the body and head continue to be shunted into the head
243    // (and thus can shift to end up before dynamically added DOM content that is also
244    // outside the body).
245    if ((createdByParser && m_document->body()) || m_styleSheetCandidateNodes.isEmpty()) {
246        m_styleSheetCandidateNodes.add(node);
247        return;
248    }
249
250    // Determine an appropriate insertion point.
251    StyleSheetCandidateListHashSet::iterator begin = m_styleSheetCandidateNodes.begin();
252    StyleSheetCandidateListHashSet::iterator end = m_styleSheetCandidateNodes.end();
253    StyleSheetCandidateListHashSet::iterator it = end;
254    Node* followingNode = 0;
255    do {
256        --it;
257        Node* n = *it;
258        unsigned short position = n->compareDocumentPosition(node);
259        if (position == Node::DOCUMENT_POSITION_FOLLOWING) {
260            m_styleSheetCandidateNodes.insertBefore(followingNode, node);
261            return;
262        }
263        followingNode = n;
264    } while (it != begin);
265
266    m_styleSheetCandidateNodes.insertBefore(followingNode, node);
267}
268
269void DocumentStyleSheetCollection::removeStyleSheetCandidateNode(Node* node)
270{
271    m_styleSheetCandidateNodes.remove(node);
272}
273
274void DocumentStyleSheetCollection::collectActiveStyleSheets(Vector<RefPtr<StyleSheet> >& sheets)
275{
276    if (m_document->settings() && !m_document->settings()->authorAndUserStylesEnabled())
277        return;
278
279    StyleSheetCandidateListHashSet::iterator begin = m_styleSheetCandidateNodes.begin();
280    StyleSheetCandidateListHashSet::iterator end = m_styleSheetCandidateNodes.end();
281    for (StyleSheetCandidateListHashSet::iterator it = begin; it != end; ++it) {
282        Node* n = *it;
283        StyleSheet* sheet = 0;
284        if (n->nodeType() == Node::PROCESSING_INSTRUCTION_NODE) {
285            // Processing instruction (XML documents only).
286            // We don't support linking to embedded CSS stylesheets, see <https://bugs.webkit.org/show_bug.cgi?id=49281> for discussion.
287            ProcessingInstruction* pi = static_cast<ProcessingInstruction*>(n);
288            sheet = pi->sheet();
289#if ENABLE(XSLT)
290            // Don't apply XSL transforms to already transformed documents -- <rdar://problem/4132806>
291            if (pi->isXSL() && !m_document->transformSourceDocument()) {
292                // Don't apply XSL transforms until loading is finished.
293                if (!m_document->parsing())
294                    m_document->applyXSLTransform(pi);
295                return;
296            }
297#endif
298        } else if ((n->isHTMLElement() && (n->hasTagName(linkTag) || n->hasTagName(styleTag)))
299#if ENABLE(SVG)
300                   ||  (n->isSVGElement() && n->hasTagName(SVGNames::styleTag))
301#endif
302                   ) {
303            Element* e = toElement(n);
304            AtomicString title = e->getAttribute(titleAttr);
305            bool enabledViaScript = false;
306            if (e->hasLocalName(linkTag)) {
307                // <LINK> element
308                HTMLLinkElement* linkElement = static_cast<HTMLLinkElement*>(n);
309                if (linkElement->isDisabled())
310                    continue;
311                enabledViaScript = linkElement->isEnabledViaScript();
312                if (linkElement->styleSheetIsLoading()) {
313                    // it is loading but we should still decide which style sheet set to use
314                    if (!enabledViaScript && !title.isEmpty() && m_preferredStylesheetSetName.isEmpty()) {
315                        const AtomicString& rel = e->getAttribute(relAttr);
316                        if (!rel.contains("alternate")) {
317                            m_preferredStylesheetSetName = title;
318                            m_selectedStylesheetSetName = title;
319                        }
320                    }
321                    continue;
322                }
323                if (!linkElement->sheet())
324                    title = nullAtom;
325            }
326            // Get the current preferred styleset. This is the
327            // set of sheets that will be enabled.
328#if ENABLE(SVG)
329            if (n->isSVGElement() && n->hasTagName(SVGNames::styleTag))
330                sheet = static_cast<SVGStyleElement*>(n)->sheet();
331            else
332#endif
333            if (e->hasLocalName(linkTag))
334                sheet = static_cast<HTMLLinkElement*>(n)->sheet();
335            else
336                // <STYLE> element
337                sheet = static_cast<HTMLStyleElement*>(n)->sheet();
338            // Check to see if this sheet belongs to a styleset
339            // (thus making it PREFERRED or ALTERNATE rather than
340            // PERSISTENT).
341            AtomicString rel = e->getAttribute(relAttr);
342            if (!enabledViaScript && !title.isEmpty()) {
343                // Yes, we have a title.
344                if (m_preferredStylesheetSetName.isEmpty()) {
345                    // No preferred set has been established. If
346                    // we are NOT an alternate sheet, then establish
347                    // us as the preferred set. Otherwise, just ignore
348                    // this sheet.
349                    if (e->hasLocalName(styleTag) || !rel.contains("alternate"))
350                        m_preferredStylesheetSetName = m_selectedStylesheetSetName = title;
351                }
352                if (title != m_preferredStylesheetSetName)
353                    sheet = 0;
354            }
355
356            if (rel.contains("alternate") && title.isEmpty())
357                sheet = 0;
358        }
359        if (sheet)
360            sheets.append(sheet);
361    }
362}
363
364void DocumentStyleSheetCollection::analyzeStyleSheetChange(UpdateFlag updateFlag, const Vector<RefPtr<CSSStyleSheet> >& newStylesheets, StyleResolverUpdateType& styleResolverUpdateType, bool& requiresFullStyleRecalc)
365{
366    styleResolverUpdateType = Reconstruct;
367    requiresFullStyleRecalc = true;
368
369    // Stylesheets of <style> elements that @import stylesheets are active but loading. We need to trigger a full recalc when such loads are done.
370    bool hasActiveLoadingStylesheet = false;
371    unsigned newStylesheetCount = newStylesheets.size();
372    for (unsigned i = 0; i < newStylesheetCount; ++i) {
373        if (newStylesheets[i]->isLoading())
374            hasActiveLoadingStylesheet = true;
375    }
376    if (m_hadActiveLoadingStylesheet && !hasActiveLoadingStylesheet) {
377        m_hadActiveLoadingStylesheet = false;
378        return;
379    }
380    m_hadActiveLoadingStylesheet = hasActiveLoadingStylesheet;
381
382    if (updateFlag != OptimizedUpdate)
383        return;
384    if (!m_document->styleResolverIfExists())
385        return;
386
387    // Find out which stylesheets are new.
388    unsigned oldStylesheetCount = m_activeAuthorStyleSheets.size();
389    if (newStylesheetCount < oldStylesheetCount)
390        return;
391    Vector<StyleSheetContents*> addedSheets;
392    unsigned newIndex = 0;
393    for (unsigned oldIndex = 0; oldIndex < oldStylesheetCount; ++oldIndex) {
394        if (newIndex >= newStylesheetCount)
395            return;
396        while (m_activeAuthorStyleSheets[oldIndex] != newStylesheets[newIndex]) {
397            addedSheets.append(newStylesheets[newIndex]->contents());
398            ++newIndex;
399            if (newIndex == newStylesheetCount)
400                return;
401        }
402        ++newIndex;
403    }
404    bool hasInsertions = !addedSheets.isEmpty();
405    while (newIndex < newStylesheetCount) {
406        addedSheets.append(newStylesheets[newIndex]->contents());
407        ++newIndex;
408    }
409    // If all new sheets were added at the end of the list we can just add them to existing StyleResolver.
410    // If there were insertions we need to re-add all the stylesheets so rules are ordered correctly.
411    styleResolverUpdateType = hasInsertions ? Reset : Additive;
412
413    // If we are already parsing the body and so may have significant amount of elements, put some effort into trying to avoid style recalcs.
414    if (!m_document->body() || m_document->hasNodesWithPlaceholderStyle())
415        return;
416    StyleInvalidationAnalysis invalidationAnalysis(addedSheets);
417    if (invalidationAnalysis.dirtiesAllStyle())
418        return;
419    invalidationAnalysis.invalidateStyle(m_document);
420    requiresFullStyleRecalc = false;
421}
422
423static bool styleSheetsUseRemUnits(const Vector<RefPtr<CSSStyleSheet> >& sheets)
424{
425    for (unsigned i = 0; i < sheets.size(); ++i) {
426        if (sheets[i]->contents()->usesRemUnits())
427            return true;
428    }
429    return false;
430}
431
432static void filterEnabledNonemptyCSSStyleSheets(Vector<RefPtr<CSSStyleSheet> >& result, const Vector<RefPtr<StyleSheet> >& sheets)
433{
434    for (unsigned i = 0; i < sheets.size(); ++i) {
435        if (!sheets[i]->isCSSStyleSheet())
436            continue;
437        if (sheets[i]->disabled())
438            continue;
439        CSSStyleSheet* sheet = static_cast<CSSStyleSheet*>(sheets[i].get());
440        if (!sheet->length())
441            continue;
442        result.append(sheet);
443    }
444}
445
446static void collectActiveCSSStyleSheetsFromSeamlessParents(Vector<RefPtr<CSSStyleSheet> >& sheets, Document* document)
447{
448    HTMLIFrameElement* seamlessParentIFrame = document->seamlessParentIFrame();
449    if (!seamlessParentIFrame)
450        return;
451    sheets.appendVector(seamlessParentIFrame->document()->styleSheetCollection()->activeAuthorStyleSheets());
452}
453
454bool DocumentStyleSheetCollection::updateActiveStyleSheets(UpdateFlag updateFlag)
455{
456    if (m_document->inStyleRecalc()) {
457        // SVG <use> element may manage to invalidate style selector in the middle of a style recalc.
458        // https://bugs.webkit.org/show_bug.cgi?id=54344
459        // FIXME: This should be fixed in SVG and the call site replaced by ASSERT(!m_inStyleRecalc).
460        m_pendingUpdateType = FullUpdate;
461        m_document->scheduleForcedStyleRecalc();
462        return false;
463
464    }
465    if (!m_document->renderer() || !m_document->attached())
466        return false;
467
468    Vector<RefPtr<StyleSheet> > activeStyleSheets;
469    collectActiveStyleSheets(activeStyleSheets);
470
471    Vector<RefPtr<CSSStyleSheet> > activeCSSStyleSheets;
472    activeCSSStyleSheets.appendVector(injectedAuthorStyleSheets());
473    activeCSSStyleSheets.appendVector(documentAuthorStyleSheets());
474    collectActiveCSSStyleSheetsFromSeamlessParents(activeCSSStyleSheets, m_document);
475    filterEnabledNonemptyCSSStyleSheets(activeCSSStyleSheets, activeStyleSheets);
476
477    StyleResolverUpdateType styleResolverUpdateType;
478    bool requiresFullStyleRecalc;
479    analyzeStyleSheetChange(updateFlag, activeCSSStyleSheets, styleResolverUpdateType, requiresFullStyleRecalc);
480
481    if (styleResolverUpdateType == Reconstruct)
482        m_document->clearStyleResolver();
483    else {
484        StyleResolver* styleResolver = m_document->ensureStyleResolver();
485        if (styleResolverUpdateType == Reset) {
486            styleResolver->ruleSets().resetAuthorStyle();
487            styleResolver->appendAuthorStyleSheets(0, activeCSSStyleSheets);
488        } else {
489            ASSERT(styleResolverUpdateType == Additive);
490            styleResolver->appendAuthorStyleSheets(m_activeAuthorStyleSheets.size(), activeCSSStyleSheets);
491        }
492        resetCSSFeatureFlags();
493    }
494
495    m_weakCopyOfActiveStyleSheetListForFastLookup.clear();
496    m_activeAuthorStyleSheets.swap(activeCSSStyleSheets);
497    m_styleSheetsForStyleSheetList.swap(activeStyleSheets);
498
499    m_usesRemUnits = styleSheetsUseRemUnits(m_activeAuthorStyleSheets);
500    m_pendingUpdateType = NoUpdate;
501
502    m_document->notifySeamlessChildDocumentsOfStylesheetUpdate();
503
504    return requiresFullStyleRecalc;
505}
506
507bool DocumentStyleSheetCollection::activeStyleSheetsContains(const CSSStyleSheet* sheet) const
508{
509    if (!m_weakCopyOfActiveStyleSheetListForFastLookup) {
510        m_weakCopyOfActiveStyleSheetListForFastLookup = adoptPtr(new HashSet<const CSSStyleSheet*>);
511        for (unsigned i = 0; i < m_activeAuthorStyleSheets.size(); ++i)
512            m_weakCopyOfActiveStyleSheetListForFastLookup->add(m_activeAuthorStyleSheets[i].get());
513    }
514    return m_weakCopyOfActiveStyleSheetListForFastLookup->contains(sheet);
515}
516
517}
518