1/*
2 * (C) 1999-2003 Lars Knoll (knoll@kde.org)
3 * Copyright (C) 2004, 2006, 2007, 2012, 2013 Apple Inc. All rights reserved.
4 *
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Library General Public
7 * License as published by the Free Software Foundation; either
8 * version 2 of the License, or (at your option) any later version.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13 * Library General Public License for more details.
14 *
15 * You should have received a copy of the GNU Library General Public License
16 * along with this library; see the file COPYING.LIB.  If not, write to
17 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18 * Boston, MA 02110-1301, USA.
19 */
20
21#include "config.h"
22#include "StyleSheetContents.h"
23
24#include "CSSImportRule.h"
25#include "CSSParser.h"
26#include "CSSStyleSheet.h"
27#include "CachedCSSStyleSheet.h"
28#include "Document.h"
29#include "MediaList.h"
30#include "Node.h"
31#include "RuleSet.h"
32#include "SecurityOrigin.h"
33#include "StylePropertySet.h"
34#include "StyleRule.h"
35#include "StyleRuleImport.h"
36#include <wtf/Deque.h>
37
38namespace WebCore {
39
40// Rough size estimate for the memory cache.
41unsigned StyleSheetContents::estimatedSizeInBytes() const
42{
43    // Note that this does not take into account size of the strings hanging from various objects.
44    // The assumption is that nearly all of of them are atomic and would exist anyway.
45    unsigned size = sizeof(*this);
46
47    // FIXME: This ignores the children of media and region rules.
48    // Most rules are StyleRules.
49    size += ruleCount() * StyleRule::averageSizeInBytes();
50
51    for (unsigned i = 0; i < m_importRules.size(); ++i) {
52        if (StyleSheetContents* sheet = m_importRules[i]->styleSheet())
53            size += sheet->estimatedSizeInBytes();
54    }
55    return size;
56}
57
58StyleSheetContents::StyleSheetContents(StyleRuleImport* ownerRule, const String& originalURL, const CSSParserContext& context)
59    : m_ownerRule(ownerRule)
60    , m_originalURL(originalURL)
61    , m_loadCompleted(false)
62    , m_isUserStyleSheet(ownerRule && ownerRule->parentStyleSheet() && ownerRule->parentStyleSheet()->isUserStyleSheet())
63    , m_hasSyntacticallyValidCSSHeader(true)
64    , m_didLoadErrorOccur(false)
65    , m_usesRemUnits(false)
66    , m_isMutable(false)
67    , m_isInMemoryCache(false)
68    , m_parserContext(context)
69{
70}
71
72StyleSheetContents::StyleSheetContents(const StyleSheetContents& o)
73    : RefCounted<StyleSheetContents>()
74    , m_ownerRule(0)
75    , m_originalURL(o.m_originalURL)
76    , m_encodingFromCharsetRule(o.m_encodingFromCharsetRule)
77    , m_importRules(o.m_importRules.size())
78    , m_childRules(o.m_childRules.size())
79    , m_namespaces(o.m_namespaces)
80    , m_loadCompleted(true)
81    , m_isUserStyleSheet(o.m_isUserStyleSheet)
82    , m_hasSyntacticallyValidCSSHeader(o.m_hasSyntacticallyValidCSSHeader)
83    , m_didLoadErrorOccur(false)
84    , m_usesRemUnits(o.m_usesRemUnits)
85    , m_isMutable(false)
86    , m_isInMemoryCache(false)
87    , m_parserContext(o.m_parserContext)
88{
89    ASSERT(o.isCacheable());
90
91    // FIXME: Copy import rules.
92    ASSERT(o.m_importRules.isEmpty());
93
94    for (unsigned i = 0; i < m_childRules.size(); ++i)
95        m_childRules[i] = o.m_childRules[i]->copy();
96}
97
98StyleSheetContents::~StyleSheetContents()
99{
100    clearRules();
101}
102
103bool StyleSheetContents::isCacheable() const
104{
105    // FIXME: Support copying import rules.
106    if (!m_importRules.isEmpty())
107        return false;
108    // FIXME: Support cached stylesheets in import rules.
109    if (m_ownerRule)
110        return false;
111    // This would require dealing with multiple clients for load callbacks.
112    if (!m_loadCompleted)
113        return false;
114    if (m_didLoadErrorOccur)
115        return false;
116    // It is not the original sheet anymore.
117    if (m_isMutable)
118        return false;
119    // If the header is valid we are not going to need to check the SecurityOrigin.
120    // FIXME: Valid mime type avoids the check too.
121    if (!m_hasSyntacticallyValidCSSHeader)
122        return false;
123    return true;
124}
125
126void StyleSheetContents::parserAppendRule(PassRefPtr<StyleRuleBase> rule)
127{
128    ASSERT(!rule->isCharsetRule());
129    if (rule->isImportRule()) {
130        // Parser enforces that @import rules come before anything else except @charset.
131        ASSERT(m_childRules.isEmpty());
132        m_importRules.append(static_cast<StyleRuleImport*>(rule.get()));
133        m_importRules.last()->setParentStyleSheet(this);
134        m_importRules.last()->requestStyleSheet();
135        return;
136    }
137
138#if ENABLE(RESOLUTION_MEDIA_QUERY)
139    // Add warning message to inspector if dpi/dpcm values are used for screen media.
140    if (rule->isMediaRule())
141        reportMediaQueryWarningIfNeeded(singleOwnerDocument(), static_cast<StyleRuleMedia*>(rule.get())->mediaQueries());
142#endif
143
144    // NOTE: The selector list has to fit into RuleData. <http://webkit.org/b/118369>
145    // If we're adding a rule with a huge number of selectors, split it up into multiple rules
146    if (rule->isStyleRule() && toStyleRule(rule.get())->selectorList().componentCount() > RuleData::maximumSelectorComponentCount) {
147        Vector<RefPtr<StyleRule> > rules = toStyleRule(rule.get())->splitIntoMultipleRulesWithMaximumSelectorComponentCount(RuleData::maximumSelectorComponentCount);
148        m_childRules.appendVector(rules);
149        return;
150    }
151
152    m_childRules.append(rule);
153}
154
155StyleRuleBase* StyleSheetContents::ruleAt(unsigned index) const
156{
157    ASSERT_WITH_SECURITY_IMPLICATION(index < ruleCount());
158
159    unsigned childVectorIndex = index;
160    if (hasCharsetRule()) {
161        if (index == 0)
162            return 0;
163        --childVectorIndex;
164    }
165    if (childVectorIndex < m_importRules.size())
166        return m_importRules[childVectorIndex].get();
167
168    childVectorIndex -= m_importRules.size();
169    return m_childRules[childVectorIndex].get();
170}
171
172unsigned StyleSheetContents::ruleCount() const
173{
174    unsigned result = 0;
175    result += hasCharsetRule() ? 1 : 0;
176    result += m_importRules.size();
177    result += m_childRules.size();
178    return result;
179}
180
181void StyleSheetContents::clearCharsetRule()
182{
183    m_encodingFromCharsetRule = String();
184}
185
186void StyleSheetContents::clearRules()
187{
188    for (unsigned i = 0; i < m_importRules.size(); ++i) {
189        ASSERT(m_importRules.at(i)->parentStyleSheet() == this);
190        m_importRules[i]->clearParentStyleSheet();
191    }
192    m_importRules.clear();
193    m_childRules.clear();
194    clearCharsetRule();
195}
196
197void StyleSheetContents::parserSetEncodingFromCharsetRule(const String& encoding)
198{
199    // Parser enforces that there is ever only one @charset.
200    ASSERT(m_encodingFromCharsetRule.isNull());
201    m_encodingFromCharsetRule = encoding;
202}
203
204bool StyleSheetContents::wrapperInsertRule(PassRefPtr<StyleRuleBase> rule, unsigned index)
205{
206    ASSERT(m_isMutable);
207    ASSERT_WITH_SECURITY_IMPLICATION(index <= ruleCount());
208    // Parser::parseRule doesn't currently allow @charset so we don't need to deal with it.
209    ASSERT(!rule->isCharsetRule());
210
211    unsigned childVectorIndex = index;
212    // m_childRules does not contain @charset which is always in index 0 if it exists.
213    if (hasCharsetRule()) {
214        if (childVectorIndex == 0) {
215            // Nothing can be inserted before @charset.
216            return false;
217        }
218        --childVectorIndex;
219    }
220
221    if (childVectorIndex < m_importRules.size() || (childVectorIndex == m_importRules.size() && rule->isImportRule())) {
222        // Inserting non-import rule before @import is not allowed.
223        if (!rule->isImportRule())
224            return false;
225        m_importRules.insert(childVectorIndex, static_cast<StyleRuleImport*>(rule.get()));
226        m_importRules[childVectorIndex]->setParentStyleSheet(this);
227        m_importRules[childVectorIndex]->requestStyleSheet();
228        // FIXME: Stylesheet doesn't actually change meaningfully before the imported sheets are loaded.
229        return true;
230    }
231    // Inserting @import rule after a non-import rule is not allowed.
232    if (rule->isImportRule())
233        return false;
234    childVectorIndex -= m_importRules.size();
235
236    m_childRules.insert(childVectorIndex, rule);
237    return true;
238}
239
240void StyleSheetContents::wrapperDeleteRule(unsigned index)
241{
242    ASSERT(m_isMutable);
243    ASSERT_WITH_SECURITY_IMPLICATION(index < ruleCount());
244
245    unsigned childVectorIndex = index;
246    if (hasCharsetRule()) {
247        if (childVectorIndex == 0) {
248            clearCharsetRule();
249            return;
250        }
251        --childVectorIndex;
252    }
253    if (childVectorIndex < m_importRules.size()) {
254        m_importRules[childVectorIndex]->clearParentStyleSheet();
255        m_importRules.remove(childVectorIndex);
256        return;
257    }
258    childVectorIndex -= m_importRules.size();
259
260    m_childRules.remove(childVectorIndex);
261}
262
263void StyleSheetContents::parserAddNamespace(const AtomicString& prefix, const AtomicString& uri)
264{
265    if (uri.isNull() || prefix.isNull())
266        return;
267    PrefixNamespaceURIMap::AddResult result = m_namespaces.add(prefix, uri);
268    if (result.isNewEntry)
269        return;
270    result.iterator->value = uri;
271}
272
273const AtomicString& StyleSheetContents::determineNamespace(const AtomicString& prefix)
274{
275    if (prefix.isNull())
276        return nullAtom; // No namespace. If an element/attribute has a namespace, we won't match it.
277    if (prefix == starAtom)
278        return starAtom; // We'll match any namespace.
279    PrefixNamespaceURIMap::const_iterator it = m_namespaces.find(prefix);
280    if (it == m_namespaces.end())
281        return nullAtom;
282    return it->value;
283}
284
285void StyleSheetContents::parseAuthorStyleSheet(const CachedCSSStyleSheet* cachedStyleSheet, const SecurityOrigin* securityOrigin)
286{
287    // Check to see if we should enforce the MIME type of the CSS resource in strict mode.
288    // Running in iWeb 2 is one example of where we don't want to - <rdar://problem/6099748>
289    bool enforceMIMEType = isStrictParserMode(m_parserContext.mode) && m_parserContext.enforcesCSSMIMETypeInNoQuirksMode;
290    bool hasValidMIMEType = false;
291    String sheetText = cachedStyleSheet->sheetText(enforceMIMEType, &hasValidMIMEType);
292
293    CSSParser p(parserContext());
294    p.parseSheet(this, sheetText, 0, 0, true);
295
296    // If we're loading a stylesheet cross-origin, and the MIME type is not standard, require the CSS
297    // to at least start with a syntactically valid CSS rule.
298    // This prevents an attacker playing games by injecting CSS strings into HTML, XML, JSON, etc. etc.
299    if (!hasValidMIMEType && !hasSyntacticallyValidCSSHeader()) {
300        bool isCrossOriginCSS = !securityOrigin || !securityOrigin->canRequest(baseURL());
301        if (isCrossOriginCSS) {
302            clearRules();
303            return;
304        }
305    }
306    if (m_parserContext.needsSiteSpecificQuirks && isStrictParserMode(m_parserContext.mode)) {
307        // Work around <https://bugs.webkit.org/show_bug.cgi?id=28350>.
308        DEFINE_STATIC_LOCAL(const String, mediaWikiKHTMLFixesStyleSheet, (ASCIILiteral("/* KHTML fix stylesheet */\n/* work around the horizontal scrollbars */\n#column-content { margin-left: 0; }\n\n")));
309        // There are two variants of KHTMLFixes.css. One is equal to mediaWikiKHTMLFixesStyleSheet,
310        // while the other lacks the second trailing newline.
311        if (baseURL().string().endsWith("/KHTMLFixes.css") && !sheetText.isNull() && mediaWikiKHTMLFixesStyleSheet.startsWith(sheetText)
312            && sheetText.length() >= mediaWikiKHTMLFixesStyleSheet.length() - 1)
313            clearRules();
314    }
315}
316
317bool StyleSheetContents::parseString(const String& sheetText)
318{
319    return parseStringAtLine(sheetText, 0, false);
320}
321
322bool StyleSheetContents::parseStringAtLine(const String& sheetText, int startLineNumber, bool createdByParser)
323{
324    CSSParser p(parserContext());
325    p.parseSheet(this, sheetText, startLineNumber, 0, createdByParser);
326
327    return true;
328}
329
330bool StyleSheetContents::isLoading() const
331{
332    for (unsigned i = 0; i < m_importRules.size(); ++i) {
333        if (m_importRules[i]->isLoading())
334            return true;
335    }
336    return false;
337}
338
339void StyleSheetContents::checkLoaded()
340{
341    if (isLoading())
342        return;
343
344    RefPtr<StyleSheetContents> protect(this);
345
346    // Avoid |this| being deleted by scripts that run via
347    // ScriptableDocumentParser::executeScriptsWaitingForStylesheets().
348    // See <rdar://problem/6622300>.
349    RefPtr<StyleSheetContents> protector(this);
350    StyleSheetContents* parentSheet = parentStyleSheet();
351    if (parentSheet) {
352        parentSheet->checkLoaded();
353        m_loadCompleted = true;
354        return;
355    }
356    RefPtr<Node> ownerNode = singleOwnerNode();
357    if (!ownerNode) {
358        m_loadCompleted = true;
359        return;
360    }
361    m_loadCompleted = ownerNode->sheetLoaded();
362    if (m_loadCompleted)
363        ownerNode->notifyLoadedSheetAndAllCriticalSubresources(m_didLoadErrorOccur);
364}
365
366void StyleSheetContents::notifyLoadedSheet(const CachedCSSStyleSheet* sheet)
367{
368    ASSERT(sheet);
369    m_didLoadErrorOccur |= sheet->errorOccurred();
370}
371
372void StyleSheetContents::startLoadingDynamicSheet()
373{
374    if (Node* owner = singleOwnerNode())
375        owner->startLoadingDynamicSheet();
376}
377
378StyleSheetContents* StyleSheetContents::rootStyleSheet() const
379{
380    const StyleSheetContents* root = this;
381    while (root->parentStyleSheet())
382        root = root->parentStyleSheet();
383    return const_cast<StyleSheetContents*>(root);
384}
385
386Node* StyleSheetContents::singleOwnerNode() const
387{
388    StyleSheetContents* root = rootStyleSheet();
389    if (root->m_clients.isEmpty())
390        return 0;
391    ASSERT(root->m_clients.size() == 1);
392    return root->m_clients[0]->ownerNode();
393}
394
395Document* StyleSheetContents::singleOwnerDocument() const
396{
397    Node* ownerNode = singleOwnerNode();
398    return ownerNode ? ownerNode->document() : 0;
399}
400
401KURL StyleSheetContents::completeURL(const String& url) const
402{
403    return CSSParser::completeURL(m_parserContext, url);
404}
405
406void StyleSheetContents::addSubresourceStyleURLs(ListHashSet<KURL>& urls)
407{
408    Deque<StyleSheetContents*> styleSheetQueue;
409    styleSheetQueue.append(this);
410
411    while (!styleSheetQueue.isEmpty()) {
412        StyleSheetContents* styleSheet = styleSheetQueue.takeFirst();
413
414        for (unsigned i = 0; i < styleSheet->m_importRules.size(); ++i) {
415            StyleRuleImport* importRule = styleSheet->m_importRules[i].get();
416            if (importRule->styleSheet()) {
417                styleSheetQueue.append(importRule->styleSheet());
418                addSubresourceURL(urls, importRule->styleSheet()->baseURL());
419            }
420        }
421        for (unsigned i = 0; i < styleSheet->m_childRules.size(); ++i) {
422            StyleRuleBase* rule = styleSheet->m_childRules[i].get();
423            if (rule->isStyleRule())
424                static_cast<StyleRule*>(rule)->properties()->addSubresourceStyleURLs(urls, this);
425            else if (rule->isFontFaceRule())
426                static_cast<StyleRuleFontFace*>(rule)->properties()->addSubresourceStyleURLs(urls, this);
427        }
428    }
429}
430
431static bool childRulesHaveFailedOrCanceledSubresources(const Vector<RefPtr<StyleRuleBase> >& rules)
432{
433    for (unsigned i = 0; i < rules.size(); ++i) {
434        const StyleRuleBase* rule = rules[i].get();
435        switch (rule->type()) {
436        case StyleRuleBase::Style:
437            if (static_cast<const StyleRule*>(rule)->properties()->hasFailedOrCanceledSubresources())
438                return true;
439            break;
440        case StyleRuleBase::FontFace:
441            if (static_cast<const StyleRuleFontFace*>(rule)->properties()->hasFailedOrCanceledSubresources())
442                return true;
443            break;
444        case StyleRuleBase::Media:
445            if (childRulesHaveFailedOrCanceledSubresources(static_cast<const StyleRuleMedia*>(rule)->childRules()))
446                return true;
447            break;
448        case StyleRuleBase::Region:
449            if (childRulesHaveFailedOrCanceledSubresources(static_cast<const StyleRuleRegion*>(rule)->childRules()))
450                return true;
451            break;
452#if ENABLE(SHADOW_DOM)
453        case StyleRuleBase::HostInternal:
454            if (childRulesHaveFailedOrCanceledSubresources(static_cast<const StyleRuleHost*>(rule)->childRules()))
455                return true;
456            break;
457#endif
458        case StyleRuleBase::Import:
459            ASSERT_NOT_REACHED();
460        case StyleRuleBase::Page:
461        case StyleRuleBase::Keyframes:
462        case StyleRuleBase::Unknown:
463        case StyleRuleBase::Charset:
464        case StyleRuleBase::Keyframe:
465#if ENABLE(CSS3_CONDITIONAL_RULES)
466        case StyleRuleBase::Supports:
467#endif
468#if ENABLE(CSS_DEVICE_ADAPTATION)
469        case StyleRuleBase::Viewport:
470#endif
471#if ENABLE(CSS_SHADERS)
472        case StyleRuleBase::Filter:
473#endif
474            break;
475        }
476    }
477    return false;
478}
479
480bool StyleSheetContents::hasFailedOrCanceledSubresources() const
481{
482    ASSERT(isCacheable());
483    return childRulesHaveFailedOrCanceledSubresources(m_childRules);
484}
485
486StyleSheetContents* StyleSheetContents::parentStyleSheet() const
487{
488    return m_ownerRule ? m_ownerRule->parentStyleSheet() : 0;
489}
490
491void StyleSheetContents::registerClient(CSSStyleSheet* sheet)
492{
493    ASSERT(!m_clients.contains(sheet));
494    m_clients.append(sheet);
495}
496
497void StyleSheetContents::unregisterClient(CSSStyleSheet* sheet)
498{
499    size_t position = m_clients.find(sheet);
500    ASSERT(position != notFound);
501    m_clients.remove(position);
502}
503
504void StyleSheetContents::addedToMemoryCache()
505{
506    ASSERT(!m_isInMemoryCache);
507    ASSERT(isCacheable());
508    m_isInMemoryCache = true;
509}
510
511void StyleSheetContents::removedFromMemoryCache()
512{
513    ASSERT(m_isInMemoryCache);
514    ASSERT(isCacheable());
515    m_isInMemoryCache = false;
516}
517
518void StyleSheetContents::shrinkToFit()
519{
520    m_importRules.shrinkToFit();
521    m_childRules.shrinkToFit();
522}
523
524}
525