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
46namespace blink {
47
48class StyleSheetCSSRuleList FINAL : public CSSRuleList {
49public:
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
61private:
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)
78static 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
94PassRefPtrWillBeRawPtr<CSSStyleSheet> CSSStyleSheet::create(PassRefPtrWillBeRawPtr<StyleSheetContents> sheet, CSSImportRule* ownerRule)
95{
96    return adoptRefWillBeNoop(new CSSStyleSheet(sheet, ownerRule));
97}
98
99PassRefPtrWillBeRawPtr<CSSStyleSheet> CSSStyleSheet::create(PassRefPtrWillBeRawPtr<StyleSheetContents> sheet, Node* ownerNode)
100{
101    return adoptRefWillBeNoop(new CSSStyleSheet(sheet, ownerNode, false, TextPosition::minimumPosition()));
102}
103
104PassRefPtrWillBeRawPtr<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
110PassRefPtrWillBeRawPtr<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
117CSSStyleSheet::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
129CSSStyleSheet::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
142CSSStyleSheet::~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
164void 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
190void CSSStyleSheet::didMutateRules()
191{
192    ASSERT(m_contents->isMutable());
193    ASSERT(m_contents->clientSize() <= 1);
194
195    InspectorInstrumentation::didMutateRules(this);
196    didMutate(PartialRuleUpdate);
197}
198
199void 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
211void 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
220void CSSStyleSheet::setDisabled(bool disabled)
221{
222    if (disabled == m_isDisabled)
223        return;
224    m_isDisabled = disabled;
225
226    didMutate();
227}
228
229void 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
239unsigned CSSStyleSheet::length() const
240{
241    return m_contents->ruleCount();
242}
243
244CSSRule* 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
265void CSSStyleSheet::clearOwnerNode()
266{
267    didMutate(EntireStyleSheetUpdate);
268    if (m_ownerNode)
269        m_contents->unregisterClient(this);
270    m_ownerNode = nullptr;
271}
272
273bool 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
288PassRefPtrWillBeRawPtr<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
304unsigned 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
332unsigned CSSStyleSheet::insertRule(const String& rule, ExceptionState& exceptionState)
333{
334    UseCounter::countDeprecation(callingExecutionContext(V8PerIsolateData::mainThreadIsolate()), UseCounter::CSSStyleSheetInsertRuleOptionalArg);
335    return insertRule(rule, 0, exceptionState);
336}
337
338void 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
357int 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
372int CSSStyleSheet::addRule(const String& selector, const String& style, ExceptionState& exceptionState)
373{
374    return addRule(selector, style, length(), exceptionState);
375}
376
377
378PassRefPtrWillBeRawPtr<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
387String CSSStyleSheet::href() const
388{
389    return m_contents->originalURL();
390}
391
392KURL CSSStyleSheet::baseURL() const
393{
394    return m_contents->baseURL();
395}
396
397bool CSSStyleSheet::isLoading() const
398{
399    return m_contents->isLoading();
400}
401
402MediaList* 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
412CSSStyleSheet* CSSStyleSheet::parentStyleSheet() const
413{
414    return m_ownerRule ? m_ownerRule->parentStyleSheet() : 0;
415}
416
417Document* 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
425void CSSStyleSheet::clearChildRuleCSSOMWrappers()
426{
427    m_childRuleCSSOMWrappers.clear();
428}
429
430bool CSSStyleSheet::sheetLoaded()
431{
432    ASSERT(m_ownerNode);
433    setLoadCompleted(m_ownerNode->sheetLoaded());
434    return m_loadCompleted;
435}
436
437void CSSStyleSheet::startLoadingDynamicSheet()
438{
439    setLoadCompleted(false);
440    m_ownerNode->startLoadingDynamicSheet();
441}
442
443void 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
456void 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