1 /* 2 * (C) 1999-2003 Lars Knoll (knoll (at) 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 "core/css/StyleSheetContents.h" 23 24 #include "core/css/CSSParser.h" 25 #include "core/css/CSSStyleSheet.h" 26 #include "core/css/MediaList.h" 27 #include "core/css/StylePropertySet.h" 28 #include "core/css/StyleRule.h" 29 #include "core/css/StyleRuleImport.h" 30 #include "core/css/resolver/StyleResolver.h" 31 #include "core/dom/Node.h" 32 #include "core/dom/StyleEngine.h" 33 #include "core/fetch/CSSStyleSheetResource.h" 34 #include "platform/TraceEvent.h" 35 #include "platform/weborigin/SecurityOrigin.h" 36 #include "wtf/Deque.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_hasSyntacticallyValidCSSHeader(true) 63 , m_didLoadErrorOccur(false) 64 , m_usesRemUnits(false) 65 , m_isMutable(false) 66 , m_isInMemoryCache(false) 67 , m_hasFontFaceRule(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_hasSyntacticallyValidCSSHeader(o.m_hasSyntacticallyValidCSSHeader) 82 , m_didLoadErrorOccur(false) 83 , m_usesRemUnits(o.m_usesRemUnits) 84 , m_isMutable(false) 85 , m_isInMemoryCache(false) 86 , m_hasFontFaceRule(o.m_hasFontFaceRule) 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: StyleSheets with media queries can't be cached because their RuleSet 106 // is processed differently based off the media queries, which might resolve 107 // differently depending on the context of the parent CSSStyleSheet (e.g. 108 // if they are in differently sized iframes). Once RuleSets are media query 109 // agnostic, we can restore sharing of StyleSheetContents with medea queries. 110 if (m_hasMediaQueries) 111 return false; 112 // FIXME: Support copying import rules. 113 if (!m_importRules.isEmpty()) 114 return false; 115 // FIXME: Support cached stylesheets in import rules. 116 if (m_ownerRule) 117 return false; 118 // This would require dealing with multiple clients for load callbacks. 119 if (!m_loadCompleted) 120 return false; 121 if (m_didLoadErrorOccur) 122 return false; 123 // It is not the original sheet anymore. 124 if (m_isMutable) 125 return false; 126 // If the header is valid we are not going to need to check the SecurityOrigin. 127 // FIXME: Valid mime type avoids the check too. 128 if (!m_hasSyntacticallyValidCSSHeader) 129 return false; 130 return true; 131 } 132 133 void StyleSheetContents::parserAppendRule(PassRefPtr<StyleRuleBase> rule) 134 { 135 ASSERT(!rule->isCharsetRule()); 136 if (rule->isImportRule()) { 137 // Parser enforces that @import rules come before anything else except @charset. 138 ASSERT(m_childRules.isEmpty()); 139 StyleRuleImport* importRule = toStyleRuleImport(rule.get()); 140 if (importRule->mediaQueries()) 141 setHasMediaQueries(); 142 m_importRules.append(importRule); 143 m_importRules.last()->setParentStyleSheet(this); 144 m_importRules.last()->requestStyleSheet(); 145 return; 146 } 147 148 // Add warning message to inspector if dpi/dpcm values are used for screen media. 149 if (rule->isMediaRule()) { 150 setHasMediaQueries(); 151 reportMediaQueryWarningIfNeeded(singleOwnerDocument(), toStyleRuleMedia(rule.get())->mediaQueries()); 152 } 153 154 m_childRules.append(rule); 155 } 156 157 void StyleSheetContents::setHasMediaQueries() 158 { 159 m_hasMediaQueries = true; 160 if (parentStyleSheet()) 161 parentStyleSheet()->setHasMediaQueries(); 162 } 163 164 StyleRuleBase* StyleSheetContents::ruleAt(unsigned index) const 165 { 166 ASSERT_WITH_SECURITY_IMPLICATION(index < ruleCount()); 167 168 unsigned childVectorIndex = index; 169 if (hasCharsetRule()) { 170 if (index == 0) 171 return 0; 172 --childVectorIndex; 173 } 174 if (childVectorIndex < m_importRules.size()) 175 return m_importRules[childVectorIndex].get(); 176 177 childVectorIndex -= m_importRules.size(); 178 return m_childRules[childVectorIndex].get(); 179 } 180 181 unsigned StyleSheetContents::ruleCount() const 182 { 183 unsigned result = 0; 184 result += hasCharsetRule() ? 1 : 0; 185 result += m_importRules.size(); 186 result += m_childRules.size(); 187 return result; 188 } 189 190 void StyleSheetContents::clearCharsetRule() 191 { 192 m_encodingFromCharsetRule = String(); 193 } 194 195 void StyleSheetContents::clearRules() 196 { 197 for (unsigned i = 0; i < m_importRules.size(); ++i) { 198 ASSERT(m_importRules.at(i)->parentStyleSheet() == this); 199 m_importRules[i]->clearParentStyleSheet(); 200 } 201 m_importRules.clear(); 202 m_childRules.clear(); 203 clearCharsetRule(); 204 } 205 206 void StyleSheetContents::parserSetEncodingFromCharsetRule(const String& encoding) 207 { 208 // Parser enforces that there is ever only one @charset. 209 ASSERT(m_encodingFromCharsetRule.isNull()); 210 m_encodingFromCharsetRule = encoding; 211 } 212 213 bool StyleSheetContents::wrapperInsertRule(PassRefPtr<StyleRuleBase> rule, unsigned index) 214 { 215 ASSERT(m_isMutable); 216 ASSERT_WITH_SECURITY_IMPLICATION(index <= ruleCount()); 217 // Parser::parseRule doesn't currently allow @charset so we don't need to deal with it. 218 ASSERT(!rule->isCharsetRule()); 219 220 unsigned childVectorIndex = index; 221 // m_childRules does not contain @charset which is always in index 0 if it exists. 222 if (hasCharsetRule()) { 223 if (childVectorIndex == 0) { 224 // Nothing can be inserted before @charset. 225 return false; 226 } 227 --childVectorIndex; 228 } 229 230 if (childVectorIndex < m_importRules.size() || (childVectorIndex == m_importRules.size() && rule->isImportRule())) { 231 // Inserting non-import rule before @import is not allowed. 232 if (!rule->isImportRule()) 233 return false; 234 235 StyleRuleImport* importRule = toStyleRuleImport(rule.get()); 236 if (importRule->mediaQueries()) 237 setHasMediaQueries(); 238 239 m_importRules.insert(childVectorIndex, importRule); 240 m_importRules[childVectorIndex]->setParentStyleSheet(this); 241 m_importRules[childVectorIndex]->requestStyleSheet(); 242 // FIXME: Stylesheet doesn't actually change meaningfully before the imported sheets are loaded. 243 return true; 244 } 245 // Inserting @import rule after a non-import rule is not allowed. 246 if (rule->isImportRule()) 247 return false; 248 249 if (rule->isMediaRule()) 250 setHasMediaQueries(); 251 252 childVectorIndex -= m_importRules.size(); 253 254 m_childRules.insert(childVectorIndex, rule); 255 return true; 256 } 257 258 void StyleSheetContents::wrapperDeleteRule(unsigned index) 259 { 260 ASSERT(m_isMutable); 261 ASSERT_WITH_SECURITY_IMPLICATION(index < ruleCount()); 262 263 unsigned childVectorIndex = index; 264 if (hasCharsetRule()) { 265 if (childVectorIndex == 0) { 266 clearCharsetRule(); 267 return; 268 } 269 --childVectorIndex; 270 } 271 if (childVectorIndex < m_importRules.size()) { 272 m_importRules[childVectorIndex]->clearParentStyleSheet(); 273 m_importRules.remove(childVectorIndex); 274 return; 275 } 276 childVectorIndex -= m_importRules.size(); 277 278 m_childRules.remove(childVectorIndex); 279 } 280 281 void StyleSheetContents::parserAddNamespace(const AtomicString& prefix, const AtomicString& uri) 282 { 283 if (uri.isNull() || prefix.isNull()) 284 return; 285 PrefixNamespaceURIMap::AddResult result = m_namespaces.add(prefix, uri); 286 if (result.isNewEntry) 287 return; 288 result.iterator->value = uri; 289 } 290 291 const AtomicString& StyleSheetContents::determineNamespace(const AtomicString& prefix) 292 { 293 if (prefix.isNull()) 294 return nullAtom; // No namespace. If an element/attribute has a namespace, we won't match it. 295 if (prefix == starAtom) 296 return starAtom; // We'll match any namespace. 297 return m_namespaces.get(prefix); 298 } 299 300 void StyleSheetContents::parseAuthorStyleSheet(const CSSStyleSheetResource* cachedStyleSheet, const SecurityOrigin* securityOrigin) 301 { 302 TRACE_EVENT0("webkit", "StyleSheetContents::parseAuthorStyleSheet"); 303 304 bool quirksMode = isQuirksModeBehavior(m_parserContext.mode()); 305 306 bool enforceMIMEType = !quirksMode; 307 bool hasValidMIMEType = false; 308 String sheetText = cachedStyleSheet->sheetText(enforceMIMEType, &hasValidMIMEType); 309 310 CSSParser p(parserContext(), UseCounter::getFrom(this)); 311 p.parseSheet(this, sheetText, TextPosition::minimumPosition(), 0, true); 312 313 // If we're loading a stylesheet cross-origin, and the MIME type is not standard, require the CSS 314 // to at least start with a syntactically valid CSS rule. 315 // This prevents an attacker playing games by injecting CSS strings into HTML, XML, JSON, etc. etc. 316 if (!hasValidMIMEType && !hasSyntacticallyValidCSSHeader()) { 317 bool isCrossOriginCSS = !securityOrigin || !securityOrigin->canRequest(baseURL()); 318 if (isCrossOriginCSS) { 319 clearRules(); 320 return; 321 } 322 } 323 } 324 325 bool StyleSheetContents::parseString(const String& sheetText) 326 { 327 return parseStringAtPosition(sheetText, TextPosition::minimumPosition(), false); 328 } 329 330 bool StyleSheetContents::parseStringAtPosition(const String& sheetText, const TextPosition& startPosition, bool createdByParser) 331 { 332 CSSParser p(parserContext(), UseCounter::getFrom(this)); 333 p.parseSheet(this, sheetText, startPosition, 0, createdByParser); 334 335 return true; 336 } 337 338 bool StyleSheetContents::isLoading() const 339 { 340 for (unsigned i = 0; i < m_importRules.size(); ++i) { 341 if (m_importRules[i]->isLoading()) 342 return true; 343 } 344 return false; 345 } 346 347 void StyleSheetContents::checkLoaded() 348 { 349 if (isLoading()) 350 return; 351 352 // Avoid |this| being deleted by scripts that run via 353 // ScriptableDocumentParser::executeScriptsWaitingForResources(). 354 // See https://bugs.webkit.org/show_bug.cgi?id=95106 355 RefPtr<StyleSheetContents> protect(this); 356 357 StyleSheetContents* parentSheet = parentStyleSheet(); 358 if (parentSheet) { 359 parentSheet->checkLoaded(); 360 m_loadCompleted = true; 361 return; 362 } 363 RefPtr<Node> ownerNode = singleOwnerNode(); 364 if (!ownerNode) { 365 m_loadCompleted = true; 366 return; 367 } 368 m_loadCompleted = ownerNode->sheetLoaded(); 369 if (m_loadCompleted) 370 ownerNode->notifyLoadedSheetAndAllCriticalSubresources(m_didLoadErrorOccur); 371 } 372 373 void StyleSheetContents::notifyLoadedSheet(const CSSStyleSheetResource* sheet) 374 { 375 ASSERT(sheet); 376 m_didLoadErrorOccur |= sheet->errorOccurred(); 377 // updateLayoutIgnorePendingStyleSheets can cause us to create the RuleSet on this 378 // sheet before its imports have loaded. So clear the RuleSet when the imports 379 // load since the import's subrules are flattened into its parent sheet's RuleSet. 380 clearRuleSet(); 381 } 382 383 void StyleSheetContents::startLoadingDynamicSheet() 384 { 385 if (Node* owner = singleOwnerNode()) 386 owner->startLoadingDynamicSheet(); 387 } 388 389 StyleSheetContents* StyleSheetContents::rootStyleSheet() const 390 { 391 const StyleSheetContents* root = this; 392 while (root->parentStyleSheet()) 393 root = root->parentStyleSheet(); 394 return const_cast<StyleSheetContents*>(root); 395 } 396 397 bool StyleSheetContents::hasSingleOwnerNode() const 398 { 399 StyleSheetContents* root = rootStyleSheet(); 400 if (root->m_clients.isEmpty()) 401 return false; 402 return root->m_clients.size() == 1; 403 } 404 405 Node* StyleSheetContents::singleOwnerNode() const 406 { 407 StyleSheetContents* root = rootStyleSheet(); 408 if (root->m_clients.isEmpty()) 409 return 0; 410 ASSERT(root->m_clients.size() == 1); 411 return root->m_clients[0]->ownerNode(); 412 } 413 414 Document* StyleSheetContents::singleOwnerDocument() const 415 { 416 Node* ownerNode = singleOwnerNode(); 417 return ownerNode ? &ownerNode->document() : 0; 418 } 419 420 KURL StyleSheetContents::completeURL(const String& url) const 421 { 422 return CSSParser::completeURL(m_parserContext, url); 423 } 424 425 void StyleSheetContents::addSubresourceStyleURLs(ListHashSet<KURL>& urls) 426 { 427 Deque<StyleSheetContents*> styleSheetQueue; 428 styleSheetQueue.append(this); 429 430 while (!styleSheetQueue.isEmpty()) { 431 StyleSheetContents* styleSheet = styleSheetQueue.takeFirst(); 432 433 for (unsigned i = 0; i < styleSheet->m_importRules.size(); ++i) { 434 StyleRuleImport* importRule = styleSheet->m_importRules[i].get(); 435 if (importRule->styleSheet()) { 436 styleSheetQueue.append(importRule->styleSheet()); 437 addSubresourceURL(urls, importRule->styleSheet()->baseURL()); 438 } 439 } 440 for (unsigned i = 0; i < styleSheet->m_childRules.size(); ++i) { 441 StyleRuleBase* rule = styleSheet->m_childRules[i].get(); 442 if (rule->isStyleRule()) 443 toStyleRule(rule)->properties()->addSubresourceStyleURLs(urls, this); 444 else if (rule->isFontFaceRule()) 445 toStyleRuleFontFace(rule)->properties()->addSubresourceStyleURLs(urls, this); 446 } 447 } 448 } 449 450 static bool childRulesHaveFailedOrCanceledSubresources(const Vector<RefPtr<StyleRuleBase> >& rules) 451 { 452 for (unsigned i = 0; i < rules.size(); ++i) { 453 const StyleRuleBase* rule = rules[i].get(); 454 switch (rule->type()) { 455 case StyleRuleBase::Style: 456 if (toStyleRule(rule)->properties()->hasFailedOrCanceledSubresources()) 457 return true; 458 break; 459 case StyleRuleBase::FontFace: 460 if (toStyleRuleFontFace(rule)->properties()->hasFailedOrCanceledSubresources()) 461 return true; 462 break; 463 case StyleRuleBase::Media: 464 if (childRulesHaveFailedOrCanceledSubresources(toStyleRuleMedia(rule)->childRules())) 465 return true; 466 break; 467 case StyleRuleBase::Region: 468 if (childRulesHaveFailedOrCanceledSubresources(toStyleRuleRegion(rule)->childRules())) 469 return true; 470 break; 471 case StyleRuleBase::Import: 472 ASSERT_NOT_REACHED(); 473 case StyleRuleBase::Page: 474 case StyleRuleBase::Keyframes: 475 case StyleRuleBase::Unknown: 476 case StyleRuleBase::Charset: 477 case StyleRuleBase::Keyframe: 478 case StyleRuleBase::Supports: 479 case StyleRuleBase::Viewport: 480 case StyleRuleBase::Filter: 481 break; 482 } 483 } 484 return false; 485 } 486 487 bool StyleSheetContents::hasFailedOrCanceledSubresources() const 488 { 489 ASSERT(isCacheable()); 490 return childRulesHaveFailedOrCanceledSubresources(m_childRules); 491 } 492 493 StyleSheetContents* StyleSheetContents::parentStyleSheet() const 494 { 495 return m_ownerRule ? m_ownerRule->parentStyleSheet() : 0; 496 } 497 498 void StyleSheetContents::registerClient(CSSStyleSheet* sheet) 499 { 500 ASSERT(!m_clients.contains(sheet)); 501 m_clients.append(sheet); 502 } 503 504 void StyleSheetContents::unregisterClient(CSSStyleSheet* sheet) 505 { 506 size_t position = m_clients.find(sheet); 507 ASSERT(position != kNotFound); 508 m_clients.remove(position); 509 } 510 511 void StyleSheetContents::addedToMemoryCache() 512 { 513 ASSERT(!m_isInMemoryCache); 514 ASSERT(isCacheable()); 515 m_isInMemoryCache = true; 516 } 517 518 void StyleSheetContents::removedFromMemoryCache() 519 { 520 ASSERT(m_isInMemoryCache); 521 ASSERT(isCacheable()); 522 m_isInMemoryCache = false; 523 } 524 525 void StyleSheetContents::shrinkToFit() 526 { 527 m_importRules.shrinkToFit(); 528 m_childRules.shrinkToFit(); 529 } 530 531 RuleSet& StyleSheetContents::ensureRuleSet(const MediaQueryEvaluator& medium, AddRuleFlags addRuleFlags) 532 { 533 if (!m_ruleSet) { 534 m_ruleSet = RuleSet::create(); 535 m_ruleSet->addRulesFromSheet(this, medium, addRuleFlags); 536 } 537 return *m_ruleSet.get(); 538 } 539 540 void StyleSheetContents::clearRuleSet() 541 { 542 if (StyleSheetContents* parentSheet = parentStyleSheet()) 543 parentSheet->clearRuleSet(); 544 545 // Don't want to clear the StyleResolver if the RuleSet hasn't been created 546 // since we only clear the StyleResolver so that it's members are properly 547 // updated in ScopedStyleResolver::addRulesFromSheet. 548 if (!m_ruleSet) 549 return; 550 551 // Clearing the ruleSet means we need to recreate the styleResolver data structures. 552 // See the StyleResolver calls in ScopedStyleResolver::addRulesFromSheet. 553 for (size_t i = 0; i < m_clients.size(); ++i) { 554 if (Document* document = m_clients[i]->ownerDocument()) 555 document->styleEngine()->clearResolver(); 556 } 557 m_ruleSet.clear(); 558 } 559 560 561 } 562