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
43namespace WebCore {
44
45class StyleSheetCSSRuleList : public CSSRuleList {
46public:
47    StyleSheetCSSRuleList(CSSStyleSheet* sheet) : m_styleSheet(sheet) { }
48
49private:
50    virtual void ref() { m_styleSheet->ref(); }
51    virtual void deref() { m_styleSheet->deref(); }
52
53    virtual unsigned length() const { return m_styleSheet->length(); }
54    virtual CSSRule* item(unsigned index) const { return m_styleSheet->item(index); }
55
56    virtual CSSStyleSheet* styleSheet() const { return m_styleSheet; }
57
58    CSSStyleSheet* m_styleSheet;
59};
60
61#if !ASSERT_DISABLED
62static 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
74PassRefPtr<CSSStyleSheet> CSSStyleSheet::create(PassRefPtr<StyleSheetContents> sheet, CSSImportRule* ownerRule)
75{
76    return adoptRef(new CSSStyleSheet(sheet, ownerRule));
77}
78
79PassRefPtr<CSSStyleSheet> CSSStyleSheet::create(PassRefPtr<StyleSheetContents> sheet, Node* ownerNode)
80{
81    return adoptRef(new CSSStyleSheet(sheet, ownerNode, false, TextPosition::minimumPosition()));
82}
83
84PassRefPtr<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
91CSSStyleSheet::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
102CSSStyleSheet::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
114CSSStyleSheet::~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
133void 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
156void 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
182void CSSStyleSheet::didMutateRules()
183{
184    ASSERT(m_contents->isMutable());
185    ASSERT(m_contents->hasOneClient());
186
187    InspectorInstrumentation::didMutateRules(this);
188    didMutate(PartialRuleUpdate);
189}
190
191void 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
203void CSSStyleSheet::registerExtraChildRuleCSSOMWrapper(PassRefPtr<CSSRule> rule)
204{
205    m_extraChildRuleCSSOMWrappers.append(rule);
206}
207
208void 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
221void CSSStyleSheet::setDisabled(bool disabled)
222{
223    if (disabled == m_isDisabled)
224        return;
225    m_isDisabled = disabled;
226
227    didMutate();
228}
229
230void 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
240unsigned CSSStyleSheet::length() const
241{
242    return m_contents->ruleCount();
243}
244
245CSSRule* 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
266bool 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
281PassRefPtr<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
297unsigned 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
325unsigned CSSStyleSheet::insertRule(const String& rule, ExceptionState& exceptionState)
326{
327    UseCounter::countDeprecation(activeExecutionContext(), UseCounter::CSSStyleSheetInsertRuleOptionalArg);
328    return insertRule(rule, 0, exceptionState);
329}
330
331void 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
350int 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
365int CSSStyleSheet::addRule(const String& selector, const String& style, ExceptionState& exceptionState)
366{
367    return addRule(selector, style, length(), exceptionState);
368}
369
370
371PassRefPtr<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
380String CSSStyleSheet::href() const
381{
382    return m_contents->originalURL();
383}
384
385KURL CSSStyleSheet::baseURL() const
386{
387    return m_contents->baseURL();
388}
389
390bool CSSStyleSheet::isLoading() const
391{
392    return m_contents->isLoading();
393}
394
395MediaList* 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
405CSSStyleSheet* CSSStyleSheet::parentStyleSheet() const
406{
407    return m_ownerRule ? m_ownerRule->parentStyleSheet() : 0;
408}
409
410Document* 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
418void CSSStyleSheet::clearChildRuleCSSOMWrappers()
419{
420    m_childRuleCSSOMWrappers.clear();
421}
422
423}
424