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/CSSStyleSheet.h" 23 24 #include "bindings/core/v8/ExceptionState.h" 25 #include "bindings/core/v8/V8Binding.h" 26 #include "bindings/core/v8/V8PerIsolateData.h" 27 #include "core/HTMLNames.h" 28 #include "core/SVGNames.h" 29 #include "core/css/CSSCharsetRule.h" 30 #include "core/css/CSSImportRule.h" 31 #include "core/css/CSSRuleList.h" 32 #include "core/css/MediaList.h" 33 #include "core/css/StyleRule.h" 34 #include "core/css/StyleSheetContents.h" 35 #include "core/css/parser/CSSParser.h" 36 #include "core/dom/Document.h" 37 #include "core/dom/ExceptionCode.h" 38 #include "core/dom/Node.h" 39 #include "core/frame/UseCounter.h" 40 #include "core/html/HTMLStyleElement.h" 41 #include "core/inspector/InspectorInstrumentation.h" 42 #include "core/svg/SVGStyleElement.h" 43 #include "platform/weborigin/SecurityOrigin.h" 44 #include "wtf/text/StringBuilder.h" 45 46 namespace blink { 47 48 class StyleSheetCSSRuleList FINAL : public CSSRuleList { 49 public: 50 static PassOwnPtrWillBeRawPtr<StyleSheetCSSRuleList> create(CSSStyleSheet* sheet) 51 { 52 return adoptPtrWillBeNoop(new StyleSheetCSSRuleList(sheet)); 53 } 54 55 virtual void trace(Visitor* visitor) OVERRIDE 56 { 57 visitor->trace(m_styleSheet); 58 CSSRuleList::trace(visitor); 59 } 60 61 private: 62 StyleSheetCSSRuleList(CSSStyleSheet* sheet) : m_styleSheet(sheet) { } 63 64 #if !ENABLE(OILPAN) 65 virtual void ref() OVERRIDE { m_styleSheet->ref(); } 66 virtual void deref() OVERRIDE { m_styleSheet->deref(); } 67 #endif 68 69 virtual unsigned length() const OVERRIDE { return m_styleSheet->length(); } 70 virtual CSSRule* item(unsigned index) const OVERRIDE { return m_styleSheet->item(index); } 71 72 virtual CSSStyleSheet* styleSheet() const OVERRIDE { return m_styleSheet; } 73 74 RawPtrWillBeMember<CSSStyleSheet> m_styleSheet; 75 }; 76 77 #if ENABLE(ASSERT) 78 static bool isAcceptableCSSStyleSheetParent(Node* parentNode) 79 { 80 // Only these nodes can be parents of StyleSheets, and they need to call 81 // clearOwnerNode() when moved out of document. 82 // Destruction of the style sheet counts as being "moved out of the 83 // document", but only in the non-oilpan version of blink. I.e. don't call 84 // clearOwnerNode() in the owner's destructor in oilpan. 85 return !parentNode 86 || parentNode->isDocumentNode() 87 || isHTMLLinkElement(*parentNode) 88 || isHTMLStyleElement(*parentNode) 89 || isSVGStyleElement(*parentNode) 90 || parentNode->nodeType() == Node::PROCESSING_INSTRUCTION_NODE; 91 } 92 #endif 93 94 PassRefPtrWillBeRawPtr<CSSStyleSheet> CSSStyleSheet::create(PassRefPtrWillBeRawPtr<StyleSheetContents> sheet, CSSImportRule* ownerRule) 95 { 96 return adoptRefWillBeNoop(new CSSStyleSheet(sheet, ownerRule)); 97 } 98 99 PassRefPtrWillBeRawPtr<CSSStyleSheet> CSSStyleSheet::create(PassRefPtrWillBeRawPtr<StyleSheetContents> sheet, Node* ownerNode) 100 { 101 return adoptRefWillBeNoop(new CSSStyleSheet(sheet, ownerNode, false, TextPosition::minimumPosition())); 102 } 103 104 PassRefPtrWillBeRawPtr<CSSStyleSheet> CSSStyleSheet::createInline(PassRefPtrWillBeRawPtr<StyleSheetContents> sheet, Node* ownerNode, const TextPosition& startPosition) 105 { 106 ASSERT(sheet); 107 return adoptRefWillBeNoop(new CSSStyleSheet(sheet, ownerNode, true, startPosition)); 108 } 109 110 PassRefPtrWillBeRawPtr<CSSStyleSheet> CSSStyleSheet::createInline(Node* ownerNode, const KURL& baseURL, const TextPosition& startPosition, const String& encoding) 111 { 112 CSSParserContext parserContext(ownerNode->document(), 0, baseURL, encoding); 113 RefPtrWillBeRawPtr<StyleSheetContents> sheet = StyleSheetContents::create(baseURL.string(), parserContext); 114 return adoptRefWillBeNoop(new CSSStyleSheet(sheet.release(), ownerNode, true, startPosition)); 115 } 116 117 CSSStyleSheet::CSSStyleSheet(PassRefPtrWillBeRawPtr<StyleSheetContents> contents, CSSImportRule* ownerRule) 118 : m_contents(contents) 119 , m_isInlineStylesheet(false) 120 , m_isDisabled(false) 121 , m_ownerNode(nullptr) 122 , m_ownerRule(ownerRule) 123 , m_startPosition(TextPosition::minimumPosition()) 124 , m_loadCompleted(false) 125 { 126 m_contents->registerClient(this); 127 } 128 129 CSSStyleSheet::CSSStyleSheet(PassRefPtrWillBeRawPtr<StyleSheetContents> contents, Node* ownerNode, bool isInlineStylesheet, const TextPosition& startPosition) 130 : m_contents(contents) 131 , m_isInlineStylesheet(isInlineStylesheet) 132 , m_isDisabled(false) 133 , m_ownerNode(ownerNode) 134 , m_ownerRule(nullptr) 135 , m_startPosition(startPosition) 136 , m_loadCompleted(false) 137 { 138 ASSERT(isAcceptableCSSStyleSheetParent(ownerNode)); 139 m_contents->registerClient(this); 140 } 141 142 CSSStyleSheet::~CSSStyleSheet() 143 { 144 // With oilpan the parent style sheet pointer is strong and the sheet and 145 // its RuleCSSOMWrappers die together and we don't need to clear them here. 146 // Also with oilpan the StyleSheetContents client pointers are weak and 147 // therefore do not need to be cleared here. 148 #if !ENABLE(OILPAN) 149 // For style rules outside the document, .parentStyleSheet can become null even if the style rule 150 // is still observable from JavaScript. This matches the behavior of .parentNode for nodes, but 151 // it's not ideal because it makes the CSSOM's behavior depend on the timing of garbage collection. 152 for (unsigned i = 0; i < m_childRuleCSSOMWrappers.size(); ++i) { 153 if (m_childRuleCSSOMWrappers[i]) 154 m_childRuleCSSOMWrappers[i]->setParentStyleSheet(0); 155 } 156 157 if (m_mediaCSSOMWrapper) 158 m_mediaCSSOMWrapper->clearParentStyleSheet(); 159 160 m_contents->unregisterClient(this); 161 #endif 162 } 163 164 void CSSStyleSheet::willMutateRules() 165 { 166 InspectorInstrumentation::willMutateRules(this); 167 168 // If we are the only client it is safe to mutate. 169 if (m_contents->clientSize() <= 1 && !m_contents->isInMemoryCache()) { 170 m_contents->clearRuleSet(); 171 if (Document* document = ownerDocument()) 172 m_contents->removeSheetFromCache(document); 173 m_contents->setMutable(); 174 return; 175 } 176 // Only cacheable stylesheets should have multiple clients. 177 ASSERT(m_contents->isCacheable()); 178 179 // Copy-on-write. 180 m_contents->unregisterClient(this); 181 m_contents = m_contents->copy(); 182 m_contents->registerClient(this); 183 184 m_contents->setMutable(); 185 186 // Any existing CSSOM wrappers need to be connected to the copied child rules. 187 reattachChildRuleCSSOMWrappers(); 188 } 189 190 void CSSStyleSheet::didMutateRules() 191 { 192 ASSERT(m_contents->isMutable()); 193 ASSERT(m_contents->clientSize() <= 1); 194 195 InspectorInstrumentation::didMutateRules(this); 196 didMutate(PartialRuleUpdate); 197 } 198 199 void CSSStyleSheet::didMutate(StyleSheetUpdateType updateType) 200 { 201 Document* owner = ownerDocument(); 202 if (!owner) 203 return; 204 205 // Need FullStyleUpdate when insertRule or deleteRule, 206 // because StyleSheetCollection::analyzeStyleSheetChange cannot detect partial rule update. 207 StyleResolverUpdateMode updateMode = updateType != PartialRuleUpdate ? AnalyzedStyleUpdate : FullStyleUpdate; 208 owner->modifiedStyleSheet(this, updateMode); 209 } 210 211 void CSSStyleSheet::reattachChildRuleCSSOMWrappers() 212 { 213 for (unsigned i = 0; i < m_childRuleCSSOMWrappers.size(); ++i) { 214 if (!m_childRuleCSSOMWrappers[i]) 215 continue; 216 m_childRuleCSSOMWrappers[i]->reattach(m_contents->ruleAt(i)); 217 } 218 } 219 220 void CSSStyleSheet::setDisabled(bool disabled) 221 { 222 if (disabled == m_isDisabled) 223 return; 224 m_isDisabled = disabled; 225 226 didMutate(); 227 } 228 229 void CSSStyleSheet::setMediaQueries(PassRefPtrWillBeRawPtr<MediaQuerySet> mediaQueries) 230 { 231 m_mediaQueries = mediaQueries; 232 if (m_mediaCSSOMWrapper && m_mediaQueries) 233 m_mediaCSSOMWrapper->reattach(m_mediaQueries.get()); 234 235 // Add warning message to inspector whenever dpi/dpcm values are used for "screen" media. 236 reportMediaQueryWarningIfNeeded(ownerDocument(), m_mediaQueries.get()); 237 } 238 239 unsigned CSSStyleSheet::length() const 240 { 241 return m_contents->ruleCount(); 242 } 243 244 CSSRule* CSSStyleSheet::item(unsigned index) 245 { 246 unsigned ruleCount = length(); 247 if (index >= ruleCount) 248 return 0; 249 250 if (m_childRuleCSSOMWrappers.isEmpty()) 251 m_childRuleCSSOMWrappers.grow(ruleCount); 252 ASSERT(m_childRuleCSSOMWrappers.size() == ruleCount); 253 254 RefPtrWillBeMember<CSSRule>& cssRule = m_childRuleCSSOMWrappers[index]; 255 if (!cssRule) { 256 if (index == 0 && m_contents->hasCharsetRule()) { 257 ASSERT(!m_contents->ruleAt(0)); 258 cssRule = CSSCharsetRule::create(this, m_contents->encodingFromCharsetRule()); 259 } else 260 cssRule = m_contents->ruleAt(index)->createCSSOMWrapper(this); 261 } 262 return cssRule.get(); 263 } 264 265 void CSSStyleSheet::clearOwnerNode() 266 { 267 didMutate(EntireStyleSheetUpdate); 268 if (m_ownerNode) 269 m_contents->unregisterClient(this); 270 m_ownerNode = nullptr; 271 } 272 273 bool CSSStyleSheet::canAccessRules() const 274 { 275 if (m_isInlineStylesheet) 276 return true; 277 KURL baseURL = m_contents->baseURL(); 278 if (baseURL.isEmpty()) 279 return true; 280 Document* document = ownerDocument(); 281 if (!document) 282 return true; 283 if (document->securityOrigin()->canRequest(baseURL)) 284 return true; 285 return false; 286 } 287 288 PassRefPtrWillBeRawPtr<CSSRuleList> CSSStyleSheet::rules() 289 { 290 if (!canAccessRules()) 291 return nullptr; 292 // IE behavior. 293 RefPtrWillBeRawPtr<StaticCSSRuleList> nonCharsetRules(StaticCSSRuleList::create()); 294 unsigned ruleCount = length(); 295 for (unsigned i = 0; i < ruleCount; ++i) { 296 CSSRule* rule = item(i); 297 if (rule->type() == CSSRule::CHARSET_RULE) 298 continue; 299 nonCharsetRules->rules().append(rule); 300 } 301 return nonCharsetRules.release(); 302 } 303 304 unsigned CSSStyleSheet::insertRule(const String& ruleString, unsigned index, ExceptionState& exceptionState) 305 { 306 ASSERT(m_childRuleCSSOMWrappers.isEmpty() || m_childRuleCSSOMWrappers.size() == m_contents->ruleCount()); 307 308 if (index > length()) { 309 exceptionState.throwDOMException(IndexSizeError, "The index provided (" + String::number(index) + ") is larger than the maximum index (" + String::number(length()) + ")."); 310 return 0; 311 } 312 CSSParserContext context(m_contents->parserContext(), UseCounter::getFrom(this)); 313 RefPtrWillBeRawPtr<StyleRuleBase> rule = CSSParser::parseRule(context, m_contents.get(), ruleString); 314 315 if (!rule) { 316 exceptionState.throwDOMException(SyntaxError, "Failed to parse the rule '" + ruleString + "'."); 317 return 0; 318 } 319 RuleMutationScope mutationScope(this); 320 321 bool success = m_contents->wrapperInsertRule(rule, index); 322 if (!success) { 323 exceptionState.throwDOMException(HierarchyRequestError, "Failed to insert the rule."); 324 return 0; 325 } 326 if (!m_childRuleCSSOMWrappers.isEmpty()) 327 m_childRuleCSSOMWrappers.insert(index, RefPtrWillBeMember<CSSRule>(nullptr)); 328 329 return index; 330 } 331 332 unsigned CSSStyleSheet::insertRule(const String& rule, ExceptionState& exceptionState) 333 { 334 UseCounter::countDeprecation(callingExecutionContext(V8PerIsolateData::mainThreadIsolate()), UseCounter::CSSStyleSheetInsertRuleOptionalArg); 335 return insertRule(rule, 0, exceptionState); 336 } 337 338 void CSSStyleSheet::deleteRule(unsigned index, ExceptionState& exceptionState) 339 { 340 ASSERT(m_childRuleCSSOMWrappers.isEmpty() || m_childRuleCSSOMWrappers.size() == m_contents->ruleCount()); 341 342 if (index >= length()) { 343 exceptionState.throwDOMException(IndexSizeError, "The index provided (" + String::number(index) + ") is larger than the maximum index (" + String::number(length() - 1) + ")."); 344 return; 345 } 346 RuleMutationScope mutationScope(this); 347 348 m_contents->wrapperDeleteRule(index); 349 350 if (!m_childRuleCSSOMWrappers.isEmpty()) { 351 if (m_childRuleCSSOMWrappers[index]) 352 m_childRuleCSSOMWrappers[index]->setParentStyleSheet(0); 353 m_childRuleCSSOMWrappers.remove(index); 354 } 355 } 356 357 int CSSStyleSheet::addRule(const String& selector, const String& style, int index, ExceptionState& exceptionState) 358 { 359 StringBuilder text; 360 text.append(selector); 361 text.appendLiteral(" { "); 362 text.append(style); 363 if (!style.isEmpty()) 364 text.append(' '); 365 text.append('}'); 366 insertRule(text.toString(), index, exceptionState); 367 368 // As per Microsoft documentation, always return -1. 369 return -1; 370 } 371 372 int CSSStyleSheet::addRule(const String& selector, const String& style, ExceptionState& exceptionState) 373 { 374 return addRule(selector, style, length(), exceptionState); 375 } 376 377 378 PassRefPtrWillBeRawPtr<CSSRuleList> CSSStyleSheet::cssRules() 379 { 380 if (!canAccessRules()) 381 return nullptr; 382 if (!m_ruleListCSSOMWrapper) 383 m_ruleListCSSOMWrapper = StyleSheetCSSRuleList::create(this); 384 return m_ruleListCSSOMWrapper.get(); 385 } 386 387 String CSSStyleSheet::href() const 388 { 389 return m_contents->originalURL(); 390 } 391 392 KURL CSSStyleSheet::baseURL() const 393 { 394 return m_contents->baseURL(); 395 } 396 397 bool CSSStyleSheet::isLoading() const 398 { 399 return m_contents->isLoading(); 400 } 401 402 MediaList* CSSStyleSheet::media() const 403 { 404 if (!m_mediaQueries) 405 return 0; 406 407 if (!m_mediaCSSOMWrapper) 408 m_mediaCSSOMWrapper = MediaList::create(m_mediaQueries.get(), const_cast<CSSStyleSheet*>(this)); 409 return m_mediaCSSOMWrapper.get(); 410 } 411 412 CSSStyleSheet* CSSStyleSheet::parentStyleSheet() const 413 { 414 return m_ownerRule ? m_ownerRule->parentStyleSheet() : 0; 415 } 416 417 Document* CSSStyleSheet::ownerDocument() const 418 { 419 const CSSStyleSheet* root = this; 420 while (root->parentStyleSheet()) 421 root = root->parentStyleSheet(); 422 return root->ownerNode() ? &root->ownerNode()->document() : 0; 423 } 424 425 void CSSStyleSheet::clearChildRuleCSSOMWrappers() 426 { 427 m_childRuleCSSOMWrappers.clear(); 428 } 429 430 bool CSSStyleSheet::sheetLoaded() 431 { 432 ASSERT(m_ownerNode); 433 setLoadCompleted(m_ownerNode->sheetLoaded()); 434 return m_loadCompleted; 435 } 436 437 void CSSStyleSheet::startLoadingDynamicSheet() 438 { 439 setLoadCompleted(false); 440 m_ownerNode->startLoadingDynamicSheet(); 441 } 442 443 void CSSStyleSheet::setLoadCompleted(bool completed) 444 { 445 if (completed == m_loadCompleted) 446 return; 447 448 m_loadCompleted = completed; 449 450 if (completed) 451 m_contents->clientLoadCompleted(this); 452 else 453 m_contents->clientLoadStarted(this); 454 } 455 456 void CSSStyleSheet::trace(Visitor* visitor) 457 { 458 visitor->trace(m_contents); 459 visitor->trace(m_mediaQueries); 460 visitor->trace(m_ownerNode); 461 visitor->trace(m_ownerRule); 462 visitor->trace(m_mediaCSSOMWrapper); 463 visitor->trace(m_childRuleCSSOMWrappers); 464 visitor->trace(m_ruleListCSSOMWrapper); 465 StyleSheet::trace(visitor); 466 } 467 468 } // namespace blink 469