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 "HTMLNames.h" 25 #include "SVGNames.h" 26 #include "bindings/v8/ExceptionState.h" 27 #include "core/css/CSSCharsetRule.h" 28 #include "core/css/CSSImportRule.h" 29 #include "core/css/CSSParser.h" 30 #include "core/css/CSSRuleList.h" 31 #include "core/css/MediaList.h" 32 #include "core/css/StyleRule.h" 33 #include "core/css/StyleSheetContents.h" 34 #include "core/dom/Document.h" 35 #include "core/dom/ExceptionCode.h" 36 #include "core/dom/Node.h" 37 #include "weborigin/SecurityOrigin.h" 38 #include "wtf/text/StringBuilder.h" 39 40 namespace WebCore { 41 42 class StyleSheetCSSRuleList : public CSSRuleList { 43 public: 44 StyleSheetCSSRuleList(CSSStyleSheet* sheet) : m_styleSheet(sheet) { } 45 46 private: 47 virtual void ref() { m_styleSheet->ref(); } 48 virtual void deref() { m_styleSheet->deref(); } 49 50 virtual unsigned length() const { return m_styleSheet->length(); } 51 virtual CSSRule* item(unsigned index) const { return m_styleSheet->item(index); } 52 53 virtual CSSStyleSheet* styleSheet() const { return m_styleSheet; } 54 55 CSSStyleSheet* m_styleSheet; 56 }; 57 58 #if !ASSERT_DISABLED 59 static bool isAcceptableCSSStyleSheetParent(Node* parentNode) 60 { 61 // Only these nodes can be parents of StyleSheets, and they need to call clearOwnerNode() when moved out of document. 62 return !parentNode 63 || parentNode->isDocumentNode() 64 || parentNode->hasTagName(HTMLNames::linkTag) 65 || parentNode->hasTagName(HTMLNames::styleTag) 66 || parentNode->hasTagName(SVGNames::styleTag) 67 || parentNode->nodeType() == Node::PROCESSING_INSTRUCTION_NODE; 68 } 69 #endif 70 71 PassRefPtr<CSSStyleSheet> CSSStyleSheet::create(PassRefPtr<StyleSheetContents> sheet, CSSImportRule* ownerRule) 72 { 73 return adoptRef(new CSSStyleSheet(sheet, ownerRule)); 74 } 75 76 PassRefPtr<CSSStyleSheet> CSSStyleSheet::create(PassRefPtr<StyleSheetContents> sheet, Node* ownerNode) 77 { 78 return adoptRef(new CSSStyleSheet(sheet, ownerNode, false, TextPosition::minimumPosition())); 79 } 80 81 PassRefPtr<CSSStyleSheet> CSSStyleSheet::createInline(Node* ownerNode, const KURL& baseURL, const TextPosition& startPosition, const String& encoding) 82 { 83 CSSParserContext parserContext(ownerNode->document(), baseURL, encoding); 84 RefPtr<StyleSheetContents> sheet = StyleSheetContents::create(baseURL.string(), parserContext); 85 return adoptRef(new CSSStyleSheet(sheet.release(), ownerNode, true, startPosition)); 86 } 87 88 CSSStyleSheet::CSSStyleSheet(PassRefPtr<StyleSheetContents> contents, CSSImportRule* ownerRule) 89 : m_contents(contents) 90 , m_isInlineStylesheet(false) 91 , m_isDisabled(false) 92 , m_ownerNode(0) 93 , m_ownerRule(ownerRule) 94 , m_startPosition(TextPosition::minimumPosition()) 95 { 96 m_contents->registerClient(this); 97 } 98 99 CSSStyleSheet::CSSStyleSheet(PassRefPtr<StyleSheetContents> contents, Node* ownerNode, bool isInlineStylesheet, const TextPosition& startPosition) 100 : m_contents(contents) 101 , m_isInlineStylesheet(isInlineStylesheet) 102 , m_isDisabled(false) 103 , m_ownerNode(ownerNode) 104 , m_ownerRule(0) 105 , m_startPosition(startPosition) 106 { 107 ASSERT(isAcceptableCSSStyleSheetParent(ownerNode)); 108 m_contents->registerClient(this); 109 } 110 111 CSSStyleSheet::~CSSStyleSheet() 112 { 113 // For style rules outside the document, .parentStyleSheet can become null even if the style rule 114 // is still observable from JavaScript. This matches the behavior of .parentNode for nodes, but 115 // it's not ideal because it makes the CSSOM's behavior depend on the timing of garbage collection. 116 for (unsigned i = 0; i < m_childRuleCSSOMWrappers.size(); ++i) { 117 if (m_childRuleCSSOMWrappers[i]) 118 m_childRuleCSSOMWrappers[i]->setParentStyleSheet(0); 119 } 120 if (m_mediaCSSOMWrapper) 121 m_mediaCSSOMWrapper->clearParentStyleSheet(); 122 123 m_contents->unregisterClient(this); 124 } 125 126 void CSSStyleSheet::willMutateRules() 127 { 128 // If we are the only client it is safe to mutate. 129 if (m_contents->hasOneClient() && !m_contents->isInMemoryCache()) { 130 m_contents->setMutable(); 131 return; 132 } 133 // Only cacheable stylesheets should have multiple clients. 134 ASSERT(m_contents->isCacheable()); 135 136 // Copy-on-write. 137 m_contents->unregisterClient(this); 138 m_contents = m_contents->copy(); 139 m_contents->registerClient(this); 140 141 m_contents->setMutable(); 142 143 // Any existing CSSOM wrappers need to be connected to the copied child rules. 144 reattachChildRuleCSSOMWrappers(); 145 } 146 147 void CSSStyleSheet::didMutateRules() 148 { 149 ASSERT(m_contents->isMutable()); 150 ASSERT(m_contents->hasOneClient()); 151 152 didMutate(); 153 } 154 155 void CSSStyleSheet::didMutate() 156 { 157 Document* owner = ownerDocument(); 158 if (!owner) 159 return; 160 owner->modifiedStyleSheet(this); 161 } 162 163 void CSSStyleSheet::reattachChildRuleCSSOMWrappers() 164 { 165 for (unsigned i = 0; i < m_childRuleCSSOMWrappers.size(); ++i) { 166 if (!m_childRuleCSSOMWrappers[i]) 167 continue; 168 m_childRuleCSSOMWrappers[i]->reattach(m_contents->ruleAt(i)); 169 } 170 } 171 172 void CSSStyleSheet::setDisabled(bool disabled) 173 { 174 if (disabled == m_isDisabled) 175 return; 176 m_isDisabled = disabled; 177 178 didMutate(); 179 } 180 181 void CSSStyleSheet::setMediaQueries(PassRefPtr<MediaQuerySet> mediaQueries) 182 { 183 m_mediaQueries = mediaQueries; 184 if (m_mediaCSSOMWrapper && m_mediaQueries) 185 m_mediaCSSOMWrapper->reattach(m_mediaQueries.get()); 186 187 // Add warning message to inspector whenever dpi/dpcm values are used for "screen" media. 188 reportMediaQueryWarningIfNeeded(ownerDocument(), m_mediaQueries.get()); 189 } 190 191 unsigned CSSStyleSheet::length() const 192 { 193 return m_contents->ruleCount(); 194 } 195 196 CSSRule* CSSStyleSheet::item(unsigned index) 197 { 198 unsigned ruleCount = length(); 199 if (index >= ruleCount) 200 return 0; 201 202 if (m_childRuleCSSOMWrappers.isEmpty()) 203 m_childRuleCSSOMWrappers.grow(ruleCount); 204 ASSERT(m_childRuleCSSOMWrappers.size() == ruleCount); 205 206 RefPtr<CSSRule>& cssRule = m_childRuleCSSOMWrappers[index]; 207 if (!cssRule) { 208 if (index == 0 && m_contents->hasCharsetRule()) { 209 ASSERT(!m_contents->ruleAt(0)); 210 cssRule = CSSCharsetRule::create(this, m_contents->encodingFromCharsetRule()); 211 } else 212 cssRule = m_contents->ruleAt(index)->createCSSOMWrapper(this); 213 } 214 return cssRule.get(); 215 } 216 217 bool CSSStyleSheet::canAccessRules() const 218 { 219 if (m_isInlineStylesheet) 220 return true; 221 KURL baseURL = m_contents->baseURL(); 222 if (baseURL.isEmpty()) 223 return true; 224 Document* document = ownerDocument(); 225 if (!document) 226 return true; 227 if (document->securityOrigin()->canRequest(baseURL)) 228 return true; 229 return false; 230 } 231 232 PassRefPtr<CSSRuleList> CSSStyleSheet::rules() 233 { 234 if (!canAccessRules()) 235 return 0; 236 // IE behavior. 237 RefPtr<StaticCSSRuleList> nonCharsetRules = StaticCSSRuleList::create(); 238 unsigned ruleCount = length(); 239 for (unsigned i = 0; i < ruleCount; ++i) { 240 CSSRule* rule = item(i); 241 if (rule->type() == CSSRule::CHARSET_RULE) 242 continue; 243 nonCharsetRules->rules().append(rule); 244 } 245 return nonCharsetRules.release(); 246 } 247 248 unsigned CSSStyleSheet::insertRule(const String& ruleString, unsigned index, ExceptionState& es) 249 { 250 ASSERT(m_childRuleCSSOMWrappers.isEmpty() || m_childRuleCSSOMWrappers.size() == m_contents->ruleCount()); 251 252 if (index > length()) { 253 es.throwDOMException(IndexSizeError); 254 return 0; 255 } 256 CSSParser p(m_contents->parserContext(), UseCounter::getFrom(this)); 257 RefPtr<StyleRuleBase> rule = p.parseRule(m_contents.get(), ruleString); 258 259 if (!rule) { 260 es.throwDOMException(SyntaxError); 261 return 0; 262 } 263 RuleMutationScope mutationScope(this); 264 265 bool success = m_contents->wrapperInsertRule(rule, index); 266 if (!success) { 267 es.throwDOMException(HierarchyRequestError); 268 return 0; 269 } 270 if (!m_childRuleCSSOMWrappers.isEmpty()) 271 m_childRuleCSSOMWrappers.insert(index, RefPtr<CSSRule>()); 272 273 return index; 274 } 275 276 void CSSStyleSheet::deleteRule(unsigned index, ExceptionState& es) 277 { 278 ASSERT(m_childRuleCSSOMWrappers.isEmpty() || m_childRuleCSSOMWrappers.size() == m_contents->ruleCount()); 279 280 if (index >= length()) { 281 es.throwDOMException(IndexSizeError); 282 return; 283 } 284 RuleMutationScope mutationScope(this); 285 286 m_contents->wrapperDeleteRule(index); 287 288 if (!m_childRuleCSSOMWrappers.isEmpty()) { 289 if (m_childRuleCSSOMWrappers[index]) 290 m_childRuleCSSOMWrappers[index]->setParentStyleSheet(0); 291 m_childRuleCSSOMWrappers.remove(index); 292 } 293 } 294 295 int CSSStyleSheet::addRule(const String& selector, const String& style, int index, ExceptionState& es) 296 { 297 StringBuilder text; 298 text.append(selector); 299 text.appendLiteral(" { "); 300 text.append(style); 301 if (!style.isEmpty()) 302 text.append(' '); 303 text.append('}'); 304 insertRule(text.toString(), index, es); 305 306 // As per Microsoft documentation, always return -1. 307 return -1; 308 } 309 310 int CSSStyleSheet::addRule(const String& selector, const String& style, ExceptionState& es) 311 { 312 return addRule(selector, style, length(), es); 313 } 314 315 316 PassRefPtr<CSSRuleList> CSSStyleSheet::cssRules() 317 { 318 if (!canAccessRules()) 319 return 0; 320 if (!m_ruleListCSSOMWrapper) 321 m_ruleListCSSOMWrapper = adoptPtr(new StyleSheetCSSRuleList(this)); 322 return m_ruleListCSSOMWrapper.get(); 323 } 324 325 String CSSStyleSheet::href() const 326 { 327 return m_contents->originalURL(); 328 } 329 330 KURL CSSStyleSheet::baseURL() const 331 { 332 return m_contents->baseURL(); 333 } 334 335 bool CSSStyleSheet::isLoading() const 336 { 337 return m_contents->isLoading(); 338 } 339 340 MediaList* CSSStyleSheet::media() const 341 { 342 if (!m_mediaQueries) 343 return 0; 344 345 if (!m_mediaCSSOMWrapper) 346 m_mediaCSSOMWrapper = MediaList::create(m_mediaQueries.get(), const_cast<CSSStyleSheet*>(this)); 347 return m_mediaCSSOMWrapper.get(); 348 } 349 350 CSSStyleSheet* CSSStyleSheet::parentStyleSheet() const 351 { 352 return m_ownerRule ? m_ownerRule->parentStyleSheet() : 0; 353 } 354 355 Document* CSSStyleSheet::ownerDocument() const 356 { 357 const CSSStyleSheet* root = this; 358 while (root->parentStyleSheet()) 359 root = root->parentStyleSheet(); 360 return root->ownerNode() ? root->ownerNode()->document() : 0; 361 } 362 363 void CSSStyleSheet::clearChildRuleCSSOMWrappers() 364 { 365 m_childRuleCSSOMWrappers.clear(); 366 } 367 368 } 369