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