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