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