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