c2244c603e1093a81d95620af2a3329a88f6e792
[WebKit-https.git] / Source / WebCore / css / StyleSheetContents.cpp
1 /*
2  * (C) 1999-2003 Lars Knoll (knoll@kde.org)
3  * Copyright (C) 2004, 2006, 2007, 2012 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 "MemoryInstrumentation.h"
30 #include "Node.h"
31 #include "SecurityOrigin.h"
32 #include "StylePropertySet.h"
33 #include "StyleRule.h"
34 #include "StyleRuleImport.h"
35 #include <wtf/Deque.h>
36
37 namespace WebCore {
38
39 // Rough size estimate for the memory cache.
40 unsigned StyleSheetContents::estimatedSizeInBytes() const
41 {
42     // Note that this does not take into account size of the strings hanging from various objects. 
43     // The assumption is that nearly all of of them are atomic and would exist anyway.
44     unsigned size = sizeof(*this);
45
46     // FIXME: This ignores the children of media and region rules.
47     // Most rules are StyleRules.
48     size += ruleCount() * StyleRule::averageSizeInBytes();
49
50     for (unsigned i = 0; i < m_importRules.size(); ++i) {
51         if (StyleSheetContents* sheet = m_importRules[i]->styleSheet())
52             size += sheet->estimatedSizeInBytes();
53     }
54     return size;
55 }
56
57 StyleSheetContents::StyleSheetContents(StyleRuleImport* ownerRule, const String& originalURL, const KURL& finalURL, const CSSParserContext& context)
58     : m_ownerRule(ownerRule)
59     , m_originalURL(originalURL)
60     , m_finalURL(finalURL)
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
72 StyleSheetContents::StyleSheetContents(const StyleSheetContents& o)
73     : RefCounted<StyleSheetContents>()
74     , m_ownerRule(0)
75     , m_originalURL(o.m_originalURL)
76     , m_finalURL(o.m_finalURL)
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
99 StyleSheetContents::~StyleSheetContents()
100 {
101     clearRules();
102 }
103
104 bool 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
127 void 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     m_childRules.append(rule);
139 }
140
141 StyleRuleBase* StyleSheetContents::ruleAt(unsigned index) const
142 {
143     ASSERT(index < ruleCount());
144     
145     unsigned childVectorIndex = index;
146     if (hasCharsetRule()) {
147         if (index == 0)
148             return 0;
149         --childVectorIndex;
150     }
151     if (childVectorIndex < m_importRules.size())
152         return m_importRules[childVectorIndex].get();
153
154     childVectorIndex -= m_importRules.size();
155     return m_childRules[childVectorIndex].get();
156 }
157
158 unsigned StyleSheetContents::ruleCount() const
159 {
160     unsigned result = 0;
161     result += hasCharsetRule() ? 1 : 0;
162     result += m_importRules.size();
163     result += m_childRules.size();
164     return result;
165 }
166
167 void StyleSheetContents::clearCharsetRule()
168 {
169     m_encodingFromCharsetRule = String();
170 }
171
172 void StyleSheetContents::clearRules()
173 {
174     for (unsigned i = 0; i < m_importRules.size(); ++i) {
175         ASSERT(m_importRules.at(i)->parentStyleSheet() == this);
176         m_importRules[i]->clearParentStyleSheet();
177     }
178     m_importRules.clear();
179     m_childRules.clear();
180     clearCharsetRule();
181 }
182
183 void StyleSheetContents::parserSetEncodingFromCharsetRule(const String& encoding)
184 {
185     // Parser enforces that there is ever only one @charset.
186     ASSERT(m_encodingFromCharsetRule.isNull());
187     m_encodingFromCharsetRule = encoding; 
188 }
189
190 bool StyleSheetContents::wrapperInsertRule(PassRefPtr<StyleRuleBase> rule, unsigned index)
191 {
192     ASSERT(m_isMutable);
193     ASSERT(index <= ruleCount());
194     // Parser::parseRule doesn't currently allow @charset so we don't need to deal with it.
195     ASSERT(!rule->isCharsetRule());
196     
197     unsigned childVectorIndex = index;
198     // m_childRules does not contain @charset which is always in index 0 if it exists.
199     if (hasCharsetRule()) {
200         if (childVectorIndex == 0) {
201             // Nothing can be inserted before @charset.
202             return false;
203         }
204         --childVectorIndex;
205     }
206     
207     if (childVectorIndex < m_importRules.size() || (childVectorIndex == m_importRules.size() && rule->isImportRule())) {
208         // Inserting non-import rule before @import is not allowed.
209         if (!rule->isImportRule())
210             return false;
211         m_importRules.insert(childVectorIndex, static_cast<StyleRuleImport*>(rule.get()));
212         m_importRules[childVectorIndex]->setParentStyleSheet(this);
213         m_importRules[childVectorIndex]->requestStyleSheet();
214         // FIXME: Stylesheet doesn't actually change meaningfully before the imported sheets are loaded.
215         return true;
216     }
217     // Inserting @import rule after a non-import rule is not allowed.
218     if (rule->isImportRule())
219         return false;
220     childVectorIndex -= m_importRules.size();
221  
222     m_childRules.insert(childVectorIndex, rule);
223     return true;
224 }
225
226 void StyleSheetContents::wrapperDeleteRule(unsigned index)
227 {
228     ASSERT(m_isMutable);
229     ASSERT(index < ruleCount());
230
231     unsigned childVectorIndex = index;
232     if (hasCharsetRule()) {
233         if (childVectorIndex == 0) {
234             clearCharsetRule();
235             return;
236         }
237         --childVectorIndex;
238     }
239     if (childVectorIndex < m_importRules.size()) {
240         m_importRules[childVectorIndex]->clearParentStyleSheet();
241         m_importRules.remove(childVectorIndex);
242         return;
243     }
244     childVectorIndex -= m_importRules.size();
245
246     m_childRules.remove(childVectorIndex);
247 }
248
249 void StyleSheetContents::parserAddNamespace(const AtomicString& prefix, const AtomicString& uri)
250 {
251     if (uri.isNull() || prefix.isNull())
252         return;
253     m_namespaces.add(prefix, uri);
254 }
255
256 const AtomicString& StyleSheetContents::determineNamespace(const AtomicString& prefix)
257 {
258     if (prefix.isNull())
259         return nullAtom; // No namespace. If an element/attribute has a namespace, we won't match it.
260     if (prefix == starAtom)
261         return starAtom; // We'll match any namespace.
262     PrefixNamespaceURIMap::const_iterator it = m_namespaces.find(prefix);
263     if (it == m_namespaces.end())
264         return nullAtom;
265     return it->second;
266 }
267
268 void StyleSheetContents::parseAuthorStyleSheet(const CachedCSSStyleSheet* cachedStyleSheet, const SecurityOrigin* securityOrigin)
269 {
270     // Check to see if we should enforce the MIME type of the CSS resource in strict mode.
271     // Running in iWeb 2 is one example of where we don't want to - <rdar://problem/6099748>
272     bool enforceMIMEType = isStrictParserMode(m_parserContext.mode) && m_parserContext.enforcesCSSMIMETypeInNoQuirksMode;
273     bool hasValidMIMEType = false;
274     String sheetText = cachedStyleSheet->sheetText(enforceMIMEType, &hasValidMIMEType);
275
276     CSSParser p(parserContext());
277     p.parseSheet(this, sheetText, 0);
278
279     // If we're loading a stylesheet cross-origin, and the MIME type is not standard, require the CSS
280     // to at least start with a syntactically valid CSS rule.
281     // This prevents an attacker playing games by injecting CSS strings into HTML, XML, JSON, etc. etc.
282     if (!hasValidMIMEType && !hasSyntacticallyValidCSSHeader()) {
283         bool isCrossOriginCSS = !securityOrigin || !securityOrigin->canRequest(finalURL());
284         if (isCrossOriginCSS) {
285             clearRules();
286             return;
287         }
288     }
289     if (m_parserContext.needsSiteSpecificQuirks && isStrictParserMode(m_parserContext.mode)) {
290         // Work around <https://bugs.webkit.org/show_bug.cgi?id=28350>.
291         DEFINE_STATIC_LOCAL(const String, slashKHTMLFixesDotCss, ("/KHTMLFixes.css"));
292         DEFINE_STATIC_LOCAL(const String, mediaWikiKHTMLFixesStyleSheet, ("/* KHTML fix stylesheet */\n/* work around the horizontal scrollbars */\n#column-content { margin-left: 0; }\n\n"));
293         // There are two variants of KHTMLFixes.css. One is equal to mediaWikiKHTMLFixesStyleSheet,
294         // while the other lacks the second trailing newline.
295         if (finalURL().string().endsWith(slashKHTMLFixesDotCss) && !sheetText.isNull() && mediaWikiKHTMLFixesStyleSheet.startsWith(sheetText)
296             && sheetText.length() >= mediaWikiKHTMLFixesStyleSheet.length() - 1)
297             clearRules();
298     }
299 }
300
301 bool StyleSheetContents::parseString(const String& sheetText)
302 {
303     return parseStringAtLine(sheetText, 0);
304 }
305
306 bool StyleSheetContents::parseStringAtLine(const String& sheetText, int startLineNumber)
307 {
308     CSSParser p(parserContext());
309     p.parseSheet(this, sheetText, startLineNumber);
310
311     return true;
312 }
313
314 bool StyleSheetContents::isLoading() const
315 {
316     for (unsigned i = 0; i < m_importRules.size(); ++i) {
317         if (m_importRules[i]->isLoading())
318             return true;
319     }
320     return false;
321 }
322
323 void StyleSheetContents::checkLoaded()
324 {
325     if (isLoading())
326         return;
327
328     // Avoid |this| being deleted by scripts that run via
329     // ScriptableDocumentParser::executeScriptsWaitingForStylesheets().
330     // See <rdar://problem/6622300>.
331     RefPtr<StyleSheetContents> protector(this);
332     StyleSheetContents* parentSheet = parentStyleSheet();
333     if (parentSheet) {
334         parentSheet->checkLoaded();
335         m_loadCompleted = true;
336         return;
337     }
338     RefPtr<Node> ownerNode = singleOwnerNode();
339     if (!ownerNode) {
340         m_loadCompleted = true;
341         return;
342     }
343     m_loadCompleted = ownerNode->sheetLoaded();
344     if (m_loadCompleted)
345         ownerNode->notifyLoadedSheetAndAllCriticalSubresources(m_didLoadErrorOccur);
346 }
347
348 void StyleSheetContents::notifyLoadedSheet(const CachedCSSStyleSheet* sheet)
349 {
350     ASSERT(sheet);
351     m_didLoadErrorOccur |= sheet->errorOccurred();
352 }
353
354 void StyleSheetContents::startLoadingDynamicSheet()
355 {
356     if (Node* owner = singleOwnerNode())
357         owner->startLoadingDynamicSheet();
358 }
359
360 StyleSheetContents* StyleSheetContents::rootStyleSheet() const
361 {
362     const StyleSheetContents* root = this;
363     while (root->parentStyleSheet())
364         root = root->parentStyleSheet();
365     return const_cast<StyleSheetContents*>(root);
366 }
367
368 Node* StyleSheetContents::singleOwnerNode() const
369 {
370     StyleSheetContents* root = rootStyleSheet();
371     if (root->m_clients.isEmpty())
372         return 0;
373     ASSERT(root->m_clients.size() == 1);
374     return root->m_clients[0]->ownerNode();
375 }
376
377 Document* StyleSheetContents::singleOwnerDocument() const
378 {
379     Node* ownerNode = singleOwnerNode();
380     return ownerNode ? ownerNode->document() : 0;
381 }
382
383 KURL StyleSheetContents::completeURL(const String& url) const
384 {
385     return CSSParser::completeURL(m_parserContext, url);
386 }
387
388 void StyleSheetContents::addSubresourceStyleURLs(ListHashSet<KURL>& urls)
389 {
390     Deque<StyleSheetContents*> styleSheetQueue;
391     styleSheetQueue.append(this);
392
393     while (!styleSheetQueue.isEmpty()) {
394         StyleSheetContents* styleSheet = styleSheetQueue.takeFirst();
395         
396         for (unsigned i = 0; i < styleSheet->m_importRules.size(); ++i) {
397             StyleRuleImport* importRule = styleSheet->m_importRules[i].get();
398             if (importRule->styleSheet()) {
399                 styleSheetQueue.append(importRule->styleSheet());
400                 addSubresourceURL(urls, importRule->styleSheet()->baseURL());
401             }
402         }
403         for (unsigned i = 0; i < styleSheet->m_childRules.size(); ++i) {
404             StyleRuleBase* rule = styleSheet->m_childRules[i].get();
405             if (rule->isStyleRule())
406                 static_cast<StyleRule*>(rule)->properties()->addSubresourceStyleURLs(urls, this);
407             else if (rule->isFontFaceRule())
408                 static_cast<StyleRuleFontFace*>(rule)->properties()->addSubresourceStyleURLs(urls, this);
409         }
410     }
411 }
412
413 StyleSheetContents* StyleSheetContents::parentStyleSheet() const
414 {
415     return m_ownerRule ? m_ownerRule->parentStyleSheet() : 0;
416 }
417
418 void StyleSheetContents::registerClient(CSSStyleSheet* sheet)
419 {
420     ASSERT(!m_clients.contains(sheet));
421     m_clients.append(sheet);
422 }
423
424 void StyleSheetContents::unregisterClient(CSSStyleSheet* sheet)
425 {
426     size_t position = m_clients.find(sheet);
427     ASSERT(position != notFound);
428     m_clients.remove(position);
429 }
430
431 void StyleSheetContents::addedToMemoryCache()
432 {
433     ASSERT(!m_isInMemoryCache);
434     ASSERT(isCacheable());
435     m_isInMemoryCache = true;
436 }
437
438 void StyleSheetContents::removedFromMemoryCache()
439 {
440     ASSERT(m_isInMemoryCache);
441     ASSERT(isCacheable());
442     m_isInMemoryCache = false;
443 }
444
445 void StyleSheetContents::reportMemoryUsage(MemoryObjectInfo* memoryObjectInfo) const
446 {
447     MemoryClassInfo<StyleSheetContents> info(memoryObjectInfo, this, MemoryInstrumentation::CSS);
448     info.addString(m_originalURL);
449     info.addString(m_encodingFromCharsetRule);
450     info.addVector(m_importRules);
451     info.addInstrumentedVector(m_childRules);
452     info.addHashMap(m_namespaces);
453     info.addVector(m_clients);
454 }
455
456 }