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