1/*
2 * (C) 1999-2003 Lars Knoll (knoll@kde.org)
3 * Copyright (C) 2004, 2006, 2007, 2012, 2013 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 "CSSStyleSheet.h"
23
24#include "CSSCharsetRule.h"
25#include "CSSFontFaceRule.h"
26#include "CSSImportRule.h"
27#include "CSSParser.h"
28#include "CSSRuleList.h"
29#include "CSSStyleRule.h"
30#include "CachedCSSStyleSheet.h"
31#include "Document.h"
32#include "DocumentStyleSheetCollection.h"
33#include "ExceptionCode.h"
34#include "HTMLNames.h"
35#include "MediaList.h"
36#include "Node.h"
37#include "SVGNames.h"
38#include "SecurityOrigin.h"
39#include "StyleRule.h"
40#include "StyleSheetContents.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#if ENABLE(SVG)
70        || parentNode->hasTagName(SVGNames::styleTag)
71#endif
72        || parentNode->nodeType() == Node::PROCESSING_INSTRUCTION_NODE;
73}
74#endif
75
76PassRefPtr<CSSStyleSheet> CSSStyleSheet::create(PassRefPtr<StyleSheetContents> sheet, CSSImportRule* ownerRule)
77{
78    return adoptRef(new CSSStyleSheet(sheet, ownerRule));
79}
80
81PassRefPtr<CSSStyleSheet> CSSStyleSheet::create(PassRefPtr<StyleSheetContents> sheet, Node* ownerNode)
82{
83    return adoptRef(new CSSStyleSheet(sheet, ownerNode, false));
84}
85
86PassRefPtr<CSSStyleSheet> CSSStyleSheet::createInline(Node* ownerNode, const KURL& baseURL, const String& encoding)
87{
88    CSSParserContext parserContext(ownerNode->document(), baseURL, encoding);
89    RefPtr<StyleSheetContents> sheet = StyleSheetContents::create(baseURL.string(), parserContext);
90    return adoptRef(new CSSStyleSheet(sheet.release(), ownerNode, true));
91}
92
93CSSStyleSheet::CSSStyleSheet(PassRefPtr<StyleSheetContents> contents, CSSImportRule* ownerRule)
94    : m_contents(contents)
95    , m_isInlineStylesheet(false)
96    , m_isDisabled(false)
97    , m_ownerNode(0)
98    , m_ownerRule(ownerRule)
99{
100    m_contents->registerClient(this);
101}
102
103CSSStyleSheet::CSSStyleSheet(PassRefPtr<StyleSheetContents> contents, Node* ownerNode, bool isInlineStylesheet)
104    : m_contents(contents)
105    , m_isInlineStylesheet(isInlineStylesheet)
106    , m_isDisabled(false)
107    , m_ownerNode(ownerNode)
108    , m_ownerRule(0)
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    if (m_mediaCSSOMWrapper)
124        m_mediaCSSOMWrapper->clearParentStyleSheet();
125
126    m_contents->unregisterClient(this);
127}
128
129CSSStyleSheet::WhetherContentsWereClonedForMutation CSSStyleSheet::willMutateRules()
130{
131    // If we are the only client it is safe to mutate.
132    if (m_contents->hasOneClient() && !m_contents->isInMemoryCache()) {
133        m_contents->setMutable();
134        return ContentsWereNotClonedForMutation;
135    }
136    // Only cacheable stylesheets should have multiple clients.
137    ASSERT(m_contents->isCacheable());
138
139    // Copy-on-write.
140    m_contents->unregisterClient(this);
141    m_contents = m_contents->copy();
142    m_contents->registerClient(this);
143
144    m_contents->setMutable();
145
146    // Any existing CSSOM wrappers need to be connected to the copied child rules.
147    reattachChildRuleCSSOMWrappers();
148
149    return ContentsWereClonedForMutation;
150}
151
152void CSSStyleSheet::didMutateRuleFromCSSStyleDeclaration()
153{
154    ASSERT(m_contents->isMutable());
155    ASSERT(m_contents->hasOneClient());
156    didMutate();
157}
158
159void CSSStyleSheet::didMutateRules(RuleMutationType mutationType, WhetherContentsWereClonedForMutation contentsWereClonedForMutation)
160{
161    ASSERT(m_contents->isMutable());
162    ASSERT(m_contents->hasOneClient());
163
164    Document* owner = ownerDocument();
165    if (!owner)
166        return;
167
168    if (mutationType == RuleInsertion && !contentsWereClonedForMutation && !owner->styleSheetCollection()->activeStyleSheetsContains(this)) {
169        owner->scheduleOptimizedStyleSheetUpdate();
170        return;
171    }
172
173    owner->styleResolverChanged(DeferRecalcStyle);
174}
175
176void CSSStyleSheet::didMutate()
177{
178    Document* owner = ownerDocument();
179    if (!owner)
180        return;
181    owner->styleResolverChanged(DeferRecalcStyle);
182}
183
184void CSSStyleSheet::clearOwnerNode()
185{
186    Document* owner = ownerDocument();
187    m_ownerNode = 0;
188    if (!owner)
189        return;
190    owner->styleResolverChanged(DeferRecalcStyleIfNeeded);
191}
192
193void CSSStyleSheet::reattachChildRuleCSSOMWrappers()
194{
195    for (unsigned i = 0; i < m_childRuleCSSOMWrappers.size(); ++i) {
196        if (!m_childRuleCSSOMWrappers[i])
197            continue;
198        m_childRuleCSSOMWrappers[i]->reattach(m_contents->ruleAt(i));
199    }
200}
201
202void CSSStyleSheet::setDisabled(bool disabled)
203{
204    if (disabled == m_isDisabled)
205        return;
206    m_isDisabled = disabled;
207
208    didMutate();
209}
210
211void CSSStyleSheet::setMediaQueries(PassRefPtr<MediaQuerySet> mediaQueries)
212{
213    m_mediaQueries = mediaQueries;
214    if (m_mediaCSSOMWrapper && m_mediaQueries)
215        m_mediaCSSOMWrapper->reattach(m_mediaQueries.get());
216
217#if ENABLE(RESOLUTION_MEDIA_QUERY)
218    // Add warning message to inspector whenever dpi/dpcm values are used for "screen" media.
219    reportMediaQueryWarningIfNeeded(ownerDocument(), m_mediaQueries.get());
220#endif
221}
222
223unsigned CSSStyleSheet::length() const
224{
225    return m_contents->ruleCount();
226}
227
228CSSRule* CSSStyleSheet::item(unsigned index)
229{
230    unsigned ruleCount = length();
231    if (index >= ruleCount)
232        return 0;
233
234    if (m_childRuleCSSOMWrappers.isEmpty())
235        m_childRuleCSSOMWrappers.grow(ruleCount);
236    ASSERT(m_childRuleCSSOMWrappers.size() == ruleCount);
237
238    RefPtr<CSSRule>& cssRule = m_childRuleCSSOMWrappers[index];
239    if (!cssRule) {
240        if (index == 0 && m_contents->hasCharsetRule()) {
241            ASSERT(!m_contents->ruleAt(0));
242            cssRule = CSSCharsetRule::create(this, m_contents->encodingFromCharsetRule());
243        } else
244            cssRule = m_contents->ruleAt(index)->createCSSOMWrapper(this);
245    }
246    return cssRule.get();
247}
248
249bool CSSStyleSheet::canAccessRules() const
250{
251    if (m_isInlineStylesheet)
252        return true;
253    KURL baseURL = m_contents->baseURL();
254    if (baseURL.isEmpty())
255        return true;
256    Document* document = ownerDocument();
257    if (!document)
258        return true;
259    if (document->securityOrigin()->canRequest(baseURL))
260        return true;
261    return false;
262}
263
264PassRefPtr<CSSRuleList> CSSStyleSheet::rules()
265{
266    if (!canAccessRules())
267        return 0;
268    // IE behavior.
269    RefPtr<StaticCSSRuleList> nonCharsetRules = StaticCSSRuleList::create();
270    unsigned ruleCount = length();
271    for (unsigned i = 0; i < ruleCount; ++i) {
272        CSSRule* rule = item(i);
273        if (rule->type() == CSSRule::CHARSET_RULE)
274            continue;
275        nonCharsetRules->rules().append(rule);
276    }
277    return nonCharsetRules.release();
278}
279
280unsigned CSSStyleSheet::insertRule(const String& ruleString, unsigned index, ExceptionCode& ec)
281{
282    ASSERT(m_childRuleCSSOMWrappers.isEmpty() || m_childRuleCSSOMWrappers.size() == m_contents->ruleCount());
283
284    ec = 0;
285    if (index > length()) {
286        ec = INDEX_SIZE_ERR;
287        return 0;
288    }
289    CSSParser p(m_contents->parserContext());
290    RefPtr<StyleRuleBase> rule = p.parseRule(m_contents.get(), ruleString);
291
292    if (!rule) {
293        ec = SYNTAX_ERR;
294        return 0;
295    }
296
297    RuleMutationScope mutationScope(this, RuleInsertion);
298
299    bool success = m_contents->wrapperInsertRule(rule, index);
300    if (!success) {
301        ec = HIERARCHY_REQUEST_ERR;
302        return 0;
303    }
304    if (!m_childRuleCSSOMWrappers.isEmpty())
305        m_childRuleCSSOMWrappers.insert(index, RefPtr<CSSRule>());
306
307    return index;
308}
309
310void CSSStyleSheet::deleteRule(unsigned index, ExceptionCode& ec)
311{
312    ASSERT(m_childRuleCSSOMWrappers.isEmpty() || m_childRuleCSSOMWrappers.size() == m_contents->ruleCount());
313
314    ec = 0;
315    if (index >= length()) {
316        ec = INDEX_SIZE_ERR;
317        return;
318    }
319    RuleMutationScope mutationScope(this);
320
321    m_contents->wrapperDeleteRule(index);
322
323    if (!m_childRuleCSSOMWrappers.isEmpty()) {
324        if (m_childRuleCSSOMWrappers[index])
325            m_childRuleCSSOMWrappers[index]->setParentStyleSheet(0);
326        m_childRuleCSSOMWrappers.remove(index);
327    }
328}
329
330int CSSStyleSheet::addRule(const String& selector, const String& style, int index, ExceptionCode& ec)
331{
332    StringBuilder text;
333    text.append(selector);
334    text.appendLiteral(" { ");
335    text.append(style);
336    if (!style.isEmpty())
337        text.append(' ');
338    text.append('}');
339    insertRule(text.toString(), index, ec);
340
341    // As per Microsoft documentation, always return -1.
342    return -1;
343}
344
345int CSSStyleSheet::addRule(const String& selector, const String& style, ExceptionCode& ec)
346{
347    return addRule(selector, style, length(), ec);
348}
349
350
351PassRefPtr<CSSRuleList> CSSStyleSheet::cssRules()
352{
353    if (!canAccessRules())
354        return 0;
355    if (!m_ruleListCSSOMWrapper)
356        m_ruleListCSSOMWrapper = adoptPtr(new StyleSheetCSSRuleList(this));
357    return m_ruleListCSSOMWrapper.get();
358}
359
360String CSSStyleSheet::href() const
361{
362    return m_contents->originalURL();
363}
364
365KURL CSSStyleSheet::baseURL() const
366{
367    return m_contents->baseURL();
368}
369
370bool CSSStyleSheet::isLoading() const
371{
372    return m_contents->isLoading();
373}
374
375MediaList* CSSStyleSheet::media() const
376{
377    if (!m_mediaQueries)
378        return 0;
379
380    if (!m_mediaCSSOMWrapper)
381        m_mediaCSSOMWrapper = MediaList::create(m_mediaQueries.get(), const_cast<CSSStyleSheet*>(this));
382    return m_mediaCSSOMWrapper.get();
383}
384
385CSSStyleSheet* CSSStyleSheet::parentStyleSheet() const
386{
387    return m_ownerRule ? m_ownerRule->parentStyleSheet() : 0;
388}
389
390Document* CSSStyleSheet::ownerDocument() const
391{
392    const CSSStyleSheet* root = this;
393    while (root->parentStyleSheet())
394        root = root->parentStyleSheet();
395    return root->ownerNode() ? root->ownerNode()->document() : 0;
396}
397
398void CSSStyleSheet::clearChildRuleCSSOMWrappers()
399{
400    m_childRuleCSSOMWrappers.clear();
401}
402
403CSSStyleSheet::RuleMutationScope::RuleMutationScope(CSSStyleSheet* sheet, RuleMutationType mutationType)
404    : m_styleSheet(sheet)
405    , m_mutationType(mutationType)
406{
407    ASSERT(m_styleSheet);
408    m_contentsWereClonedForMutation = m_styleSheet->willMutateRules();
409}
410
411CSSStyleSheet::RuleMutationScope::RuleMutationScope(CSSRule* rule)
412    : m_styleSheet(rule ? rule->parentStyleSheet() : 0)
413    , m_mutationType(OtherMutation)
414    , m_contentsWereClonedForMutation(ContentsWereNotClonedForMutation)
415{
416    if (m_styleSheet)
417        m_contentsWereClonedForMutation = m_styleSheet->willMutateRules();
418}
419
420CSSStyleSheet::RuleMutationScope::~RuleMutationScope()
421{
422    if (m_styleSheet)
423        m_styleSheet->didMutateRules(m_mutationType, m_contentsWereClonedForMutation);
424}
425
426}
427