1 /*
2 * (C) 1999-2003 Lars Knoll (knoll@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:
create(CSSStyleSheet * sheet)50 static PassOwnPtrWillBeRawPtr<StyleSheetCSSRuleList> create(CSSStyleSheet* sheet)
51 {
52 return adoptPtrWillBeNoop(new StyleSheetCSSRuleList(sheet));
53 }
54
trace(Visitor * visitor)55 virtual void trace(Visitor* visitor) OVERRIDE
56 {
57 visitor->trace(m_styleSheet);
58 CSSRuleList::trace(visitor);
59 }
60
61 private:
StyleSheetCSSRuleList(CSSStyleSheet * sheet)62 StyleSheetCSSRuleList(CSSStyleSheet* sheet) : m_styleSheet(sheet) { }
63
64 #if !ENABLE(OILPAN)
ref()65 virtual void ref() OVERRIDE { m_styleSheet->ref(); }
deref()66 virtual void deref() OVERRIDE { m_styleSheet->deref(); }
67 #endif
68
length() const69 virtual unsigned length() const OVERRIDE { return m_styleSheet->length(); }
item(unsigned index) const70 virtual CSSRule* item(unsigned index) const OVERRIDE { return m_styleSheet->item(index); }
71
styleSheet() const72 virtual CSSStyleSheet* styleSheet() const OVERRIDE { return m_styleSheet; }
73
74 RawPtrWillBeMember<CSSStyleSheet> m_styleSheet;
75 };
76
77 #if ENABLE(ASSERT)
isAcceptableCSSStyleSheetParent(Node * parentNode)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
create(PassRefPtrWillBeRawPtr<StyleSheetContents> sheet,CSSImportRule * ownerRule)94 PassRefPtrWillBeRawPtr<CSSStyleSheet> CSSStyleSheet::create(PassRefPtrWillBeRawPtr<StyleSheetContents> sheet, CSSImportRule* ownerRule)
95 {
96 return adoptRefWillBeNoop(new CSSStyleSheet(sheet, ownerRule));
97 }
98
create(PassRefPtrWillBeRawPtr<StyleSheetContents> sheet,Node * ownerNode)99 PassRefPtrWillBeRawPtr<CSSStyleSheet> CSSStyleSheet::create(PassRefPtrWillBeRawPtr<StyleSheetContents> sheet, Node* ownerNode)
100 {
101 return adoptRefWillBeNoop(new CSSStyleSheet(sheet, ownerNode, false, TextPosition::minimumPosition()));
102 }
103
createInline(PassRefPtrWillBeRawPtr<StyleSheetContents> sheet,Node * ownerNode,const TextPosition & startPosition)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
createInline(Node * ownerNode,const KURL & baseURL,const TextPosition & startPosition,const String & encoding)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
CSSStyleSheet(PassRefPtrWillBeRawPtr<StyleSheetContents> contents,CSSImportRule * ownerRule)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
CSSStyleSheet(PassRefPtrWillBeRawPtr<StyleSheetContents> contents,Node * ownerNode,bool isInlineStylesheet,const TextPosition & startPosition)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
~CSSStyleSheet()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
willMutateRules()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
didMutateRules()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
didMutate(StyleSheetUpdateType updateType)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
reattachChildRuleCSSOMWrappers()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
setDisabled(bool disabled)220 void CSSStyleSheet::setDisabled(bool disabled)
221 {
222 if (disabled == m_isDisabled)
223 return;
224 m_isDisabled = disabled;
225
226 didMutate();
227 }
228
setMediaQueries(PassRefPtrWillBeRawPtr<MediaQuerySet> mediaQueries)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
length() const239 unsigned CSSStyleSheet::length() const
240 {
241 return m_contents->ruleCount();
242 }
243
item(unsigned index)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
clearOwnerNode()265 void CSSStyleSheet::clearOwnerNode()
266 {
267 didMutate(EntireStyleSheetUpdate);
268 if (m_ownerNode)
269 m_contents->unregisterClient(this);
270 m_ownerNode = nullptr;
271 }
272
canAccessRules() const273 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
rules()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
insertRule(const String & ruleString,unsigned index,ExceptionState & exceptionState)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
insertRule(const String & rule,ExceptionState & exceptionState)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
deleteRule(unsigned index,ExceptionState & exceptionState)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
addRule(const String & selector,const String & style,int index,ExceptionState & exceptionState)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
addRule(const String & selector,const String & style,ExceptionState & exceptionState)372 int CSSStyleSheet::addRule(const String& selector, const String& style, ExceptionState& exceptionState)
373 {
374 return addRule(selector, style, length(), exceptionState);
375 }
376
377
cssRules()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
href() const387 String CSSStyleSheet::href() const
388 {
389 return m_contents->originalURL();
390 }
391
baseURL() const392 KURL CSSStyleSheet::baseURL() const
393 {
394 return m_contents->baseURL();
395 }
396
isLoading() const397 bool CSSStyleSheet::isLoading() const
398 {
399 return m_contents->isLoading();
400 }
401
media() const402 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
parentStyleSheet() const412 CSSStyleSheet* CSSStyleSheet::parentStyleSheet() const
413 {
414 return m_ownerRule ? m_ownerRule->parentStyleSheet() : 0;
415 }
416
ownerDocument() const417 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
clearChildRuleCSSOMWrappers()425 void CSSStyleSheet::clearChildRuleCSSOMWrappers()
426 {
427 m_childRuleCSSOMWrappers.clear();
428 }
429
sheetLoaded()430 bool CSSStyleSheet::sheetLoaded()
431 {
432 ASSERT(m_ownerNode);
433 setLoadCompleted(m_ownerNode->sheetLoaded());
434 return m_loadCompleted;
435 }
436
startLoadingDynamicSheet()437 void CSSStyleSheet::startLoadingDynamicSheet()
438 {
439 setLoadCompleted(false);
440 m_ownerNode->startLoadingDynamicSheet();
441 }
442
setLoadCompleted(bool completed)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
trace(Visitor * visitor)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