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