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/v8/ExceptionState.h" 25 #include "bindings/v8/V8Binding.h" 26 #include "bindings/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/parser/BisonCSSParser.h" 32 #include "core/css/CSSRuleList.h" 33 #include "core/css/MediaList.h" 34 #include "core/css/StyleRule.h" 35 #include "core/css/StyleSheetContents.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 WebCore { 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 ASSERT_ENABLED 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 BisonCSSParser p(context); 314 RefPtrWillBeRawPtr<StyleRuleBase> rule = p.parseRule(m_contents.get(), ruleString); 315 316 if (!rule) { 317 exceptionState.throwDOMException(SyntaxError, "Failed to parse the rule '" + ruleString + "'."); 318 return 0; 319 } 320 RuleMutationScope mutationScope(this); 321 322 bool success = m_contents->wrapperInsertRule(rule, index); 323 if (!success) { 324 exceptionState.throwDOMException(HierarchyRequestError, "Failed to insert the rule."); 325 return 0; 326 } 327 if (!m_childRuleCSSOMWrappers.isEmpty()) 328 m_childRuleCSSOMWrappers.insert(index, RefPtrWillBeMember<CSSRule>(nullptr)); 329 330 return index; 331 } 332 333 unsigned CSSStyleSheet::insertRule(const String& rule, ExceptionState& exceptionState) 334 { 335 UseCounter::countDeprecation(callingExecutionContext(V8PerIsolateData::mainThreadIsolate()), UseCounter::CSSStyleSheetInsertRuleOptionalArg); 336 return insertRule(rule, 0, exceptionState); 337 } 338 339 void CSSStyleSheet::deleteRule(unsigned index, ExceptionState& exceptionState) 340 { 341 ASSERT(m_childRuleCSSOMWrappers.isEmpty() || m_childRuleCSSOMWrappers.size() == m_contents->ruleCount()); 342 343 if (index >= length()) { 344 exceptionState.throwDOMException(IndexSizeError, "The index provided (" + String::number(index) + ") is larger than the maximum index (" + String::number(length() - 1) + ")."); 345 return; 346 } 347 RuleMutationScope mutationScope(this); 348 349 m_contents->wrapperDeleteRule(index); 350 351 if (!m_childRuleCSSOMWrappers.isEmpty()) { 352 if (m_childRuleCSSOMWrappers[index]) 353 m_childRuleCSSOMWrappers[index]->setParentStyleSheet(0); 354 m_childRuleCSSOMWrappers.remove(index); 355 } 356 } 357 358 int CSSStyleSheet::addRule(const String& selector, const String& style, int index, ExceptionState& exceptionState) 359 { 360 StringBuilder text; 361 text.append(selector); 362 text.appendLiteral(" { "); 363 text.append(style); 364 if (!style.isEmpty()) 365 text.append(' '); 366 text.append('}'); 367 insertRule(text.toString(), index, exceptionState); 368 369 // As per Microsoft documentation, always return -1. 370 return -1; 371 } 372 373 int CSSStyleSheet::addRule(const String& selector, const String& style, ExceptionState& exceptionState) 374 { 375 return addRule(selector, style, length(), exceptionState); 376 } 377 378 379 PassRefPtrWillBeRawPtr<CSSRuleList> CSSStyleSheet::cssRules() 380 { 381 if (!canAccessRules()) 382 return nullptr; 383 if (!m_ruleListCSSOMWrapper) 384 m_ruleListCSSOMWrapper = StyleSheetCSSRuleList::create(this); 385 return m_ruleListCSSOMWrapper.get(); 386 } 387 388 String CSSStyleSheet::href() const 389 { 390 return m_contents->originalURL(); 391 } 392 393 KURL CSSStyleSheet::baseURL() const 394 { 395 return m_contents->baseURL(); 396 } 397 398 bool CSSStyleSheet::isLoading() const 399 { 400 return m_contents->isLoading(); 401 } 402 403 MediaList* CSSStyleSheet::media() const 404 { 405 if (!m_mediaQueries) 406 return 0; 407 408 if (!m_mediaCSSOMWrapper) 409 m_mediaCSSOMWrapper = MediaList::create(m_mediaQueries.get(), const_cast<CSSStyleSheet*>(this)); 410 return m_mediaCSSOMWrapper.get(); 411 } 412 413 CSSStyleSheet* CSSStyleSheet::parentStyleSheet() const 414 { 415 return m_ownerRule ? m_ownerRule->parentStyleSheet() : 0; 416 } 417 418 Document* CSSStyleSheet::ownerDocument() const 419 { 420 const CSSStyleSheet* root = this; 421 while (root->parentStyleSheet()) 422 root = root->parentStyleSheet(); 423 return root->ownerNode() ? &root->ownerNode()->document() : 0; 424 } 425 426 void CSSStyleSheet::clearChildRuleCSSOMWrappers() 427 { 428 m_childRuleCSSOMWrappers.clear(); 429 } 430 431 bool CSSStyleSheet::sheetLoaded() 432 { 433 ASSERT(m_ownerNode); 434 setLoadCompleted(m_ownerNode->sheetLoaded()); 435 return m_loadCompleted; 436 } 437 438 void CSSStyleSheet::startLoadingDynamicSheet() 439 { 440 setLoadCompleted(false); 441 m_ownerNode->startLoadingDynamicSheet(); 442 } 443 444 void CSSStyleSheet::setLoadCompleted(bool completed) 445 { 446 if (completed == m_loadCompleted) 447 return; 448 449 m_loadCompleted = completed; 450 451 if (completed) 452 m_contents->clientLoadCompleted(this); 453 else 454 m_contents->clientLoadStarted(this); 455 } 456 457 void CSSStyleSheet::trace(Visitor* visitor) 458 { 459 visitor->trace(m_contents); 460 visitor->trace(m_mediaQueries); 461 visitor->trace(m_ownerNode); 462 visitor->trace(m_ownerRule); 463 visitor->trace(m_mediaCSSOMWrapper); 464 visitor->trace(m_childRuleCSSOMWrappers); 465 visitor->trace(m_ruleListCSSOMWrapper); 466 StyleSheet::trace(visitor); 467 } 468 469 } 470