2 * (C) 1999-2003 Lars Knoll (knoll@kde.org)
3 * Copyright (C) 2004, 2006, 2007, 2012 Apple Inc. All rights reserved.
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.
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.
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.
22 #include "StyleSheetContents.h"
24 #include "CSSImportRule.h"
25 #include "CSSParser.h"
26 #include "CSSStyleSheet.h"
27 #include "CachedCSSStyleSheet.h"
29 #include "MemoryInstrumentation.h"
31 #include "SecurityOrigin.h"
32 #include "StylePropertySet.h"
33 #include "StyleRule.h"
34 #include "StyleRuleImport.h"
35 #include <wtf/Deque.h>
39 // Rough size estimate for the memory cache.
40 unsigned StyleSheetContents::estimatedSizeInBytes() const
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);
46 // FIXME: This ignores the children of media and region rules.
47 // Most rules are StyleRules.
48 size += ruleCount() * StyleRule::averageSizeInBytes();
50 for (unsigned i = 0; i < m_importRules.size(); ++i) {
51 if (StyleSheetContents* sheet = m_importRules[i]->styleSheet())
52 size += sheet->estimatedSizeInBytes();
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)
67 , m_isInMemoryCache(false)
68 , m_parserContext(context)
72 StyleSheetContents::StyleSheetContents(const StyleSheetContents& o)
73 : RefCounted<StyleSheetContents>()
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)
87 , m_isInMemoryCache(false)
88 , m_parserContext(o.m_parserContext)
90 ASSERT(o.isCacheable());
92 // FIXME: Copy import rules.
93 ASSERT(o.m_importRules.isEmpty());
95 for (unsigned i = 0; i < m_childRules.size(); ++i)
96 m_childRules[i] = o.m_childRules[i]->copy();
99 StyleSheetContents::~StyleSheetContents()
104 bool StyleSheetContents::isCacheable() const
106 // FIXME: Support copying import rules.
107 if (!m_importRules.isEmpty())
109 // FIXME: Support cached stylesheets in import rules.
112 // This would require dealing with multiple clients for load callbacks.
113 if (!m_loadCompleted)
115 if (m_didLoadErrorOccur)
117 // It is not the original sheet anymore.
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)
127 void StyleSheetContents::parserAppendRule(PassRefPtr<StyleRuleBase> rule)
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();
138 m_childRules.append(rule);
141 StyleRuleBase* StyleSheetContents::ruleAt(unsigned index) const
143 ASSERT(index < ruleCount());
145 unsigned childVectorIndex = index;
146 if (hasCharsetRule()) {
151 if (childVectorIndex < m_importRules.size())
152 return m_importRules[childVectorIndex].get();
154 childVectorIndex -= m_importRules.size();
155 return m_childRules[childVectorIndex].get();
158 unsigned StyleSheetContents::ruleCount() const
161 result += hasCharsetRule() ? 1 : 0;
162 result += m_importRules.size();
163 result += m_childRules.size();
167 void StyleSheetContents::clearCharsetRule()
169 m_encodingFromCharsetRule = String();
172 void StyleSheetContents::clearRules()
174 for (unsigned i = 0; i < m_importRules.size(); ++i) {
175 ASSERT(m_importRules.at(i)->parentStyleSheet() == this);
176 m_importRules[i]->clearParentStyleSheet();
178 m_importRules.clear();
179 m_childRules.clear();
183 void StyleSheetContents::parserSetEncodingFromCharsetRule(const String& encoding)
185 // Parser enforces that there is ever only one @charset.
186 ASSERT(m_encodingFromCharsetRule.isNull());
187 m_encodingFromCharsetRule = encoding;
190 bool StyleSheetContents::wrapperInsertRule(PassRefPtr<StyleRuleBase> rule, unsigned index)
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());
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.
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())
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.
217 // Inserting @import rule after a non-import rule is not allowed.
218 if (rule->isImportRule())
220 childVectorIndex -= m_importRules.size();
222 m_childRules.insert(childVectorIndex, rule);
226 void StyleSheetContents::wrapperDeleteRule(unsigned index)
229 ASSERT(index < ruleCount());
231 unsigned childVectorIndex = index;
232 if (hasCharsetRule()) {
233 if (childVectorIndex == 0) {
239 if (childVectorIndex < m_importRules.size()) {
240 m_importRules[childVectorIndex]->clearParentStyleSheet();
241 m_importRules.remove(childVectorIndex);
244 childVectorIndex -= m_importRules.size();
246 m_childRules.remove(childVectorIndex);
249 void StyleSheetContents::parserAddNamespace(const AtomicString& prefix, const AtomicString& uri)
251 if (uri.isNull() || prefix.isNull())
253 m_namespaces.add(prefix, uri);
256 const AtomicString& StyleSheetContents::determineNamespace(const AtomicString& prefix)
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())
268 void StyleSheetContents::parseAuthorStyleSheet(const CachedCSSStyleSheet* cachedStyleSheet, const SecurityOrigin* securityOrigin)
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);
276 CSSParser p(parserContext());
277 p.parseSheet(this, sheetText, 0);
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) {
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)
301 bool StyleSheetContents::parseString(const String& sheetText)
303 return parseStringAtLine(sheetText, 0);
306 bool StyleSheetContents::parseStringAtLine(const String& sheetText, int startLineNumber)
308 CSSParser p(parserContext());
309 p.parseSheet(this, sheetText, startLineNumber);
314 bool StyleSheetContents::isLoading() const
316 for (unsigned i = 0; i < m_importRules.size(); ++i) {
317 if (m_importRules[i]->isLoading())
323 void StyleSheetContents::checkLoaded()
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();
334 parentSheet->checkLoaded();
335 m_loadCompleted = true;
338 RefPtr<Node> ownerNode = singleOwnerNode();
340 m_loadCompleted = true;
343 m_loadCompleted = ownerNode->sheetLoaded();
345 ownerNode->notifyLoadedSheetAndAllCriticalSubresources(m_didLoadErrorOccur);
348 void StyleSheetContents::notifyLoadedSheet(const CachedCSSStyleSheet* sheet)
351 m_didLoadErrorOccur |= sheet->errorOccurred();
354 void StyleSheetContents::startLoadingDynamicSheet()
356 if (Node* owner = singleOwnerNode())
357 owner->startLoadingDynamicSheet();
360 StyleSheetContents* StyleSheetContents::rootStyleSheet() const
362 const StyleSheetContents* root = this;
363 while (root->parentStyleSheet())
364 root = root->parentStyleSheet();
365 return const_cast<StyleSheetContents*>(root);
368 Node* StyleSheetContents::singleOwnerNode() const
370 StyleSheetContents* root = rootStyleSheet();
371 if (root->m_clients.isEmpty())
373 ASSERT(root->m_clients.size() == 1);
374 return root->m_clients[0]->ownerNode();
377 Document* StyleSheetContents::singleOwnerDocument() const
379 Node* ownerNode = singleOwnerNode();
380 return ownerNode ? ownerNode->document() : 0;
383 KURL StyleSheetContents::completeURL(const String& url) const
385 return CSSParser::completeURL(m_parserContext, url);
388 void StyleSheetContents::addSubresourceStyleURLs(ListHashSet<KURL>& urls)
390 Deque<StyleSheetContents*> styleSheetQueue;
391 styleSheetQueue.append(this);
393 while (!styleSheetQueue.isEmpty()) {
394 StyleSheetContents* styleSheet = styleSheetQueue.takeFirst();
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());
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);
413 StyleSheetContents* StyleSheetContents::parentStyleSheet() const
415 return m_ownerRule ? m_ownerRule->parentStyleSheet() : 0;
418 void StyleSheetContents::registerClient(CSSStyleSheet* sheet)
420 ASSERT(!m_clients.contains(sheet));
421 m_clients.append(sheet);
424 void StyleSheetContents::unregisterClient(CSSStyleSheet* sheet)
426 size_t position = m_clients.find(sheet);
427 ASSERT(position != notFound);
428 m_clients.remove(position);
431 void StyleSheetContents::addedToMemoryCache()
433 ASSERT(!m_isInMemoryCache);
434 ASSERT(isCacheable());
435 m_isInMemoryCache = true;
438 void StyleSheetContents::removedFromMemoryCache()
440 ASSERT(m_isInMemoryCache);
441 ASSERT(isCacheable());
442 m_isInMemoryCache = false;
445 void StyleSheetContents::reportMemoryUsage(MemoryObjectInfo* memoryObjectInfo) const
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);