3200b15e7ead2903a863e3fc22de89a7c6a0eca9
[WebKit-https.git] / Source / WebCore / css / CSSStyleSheet.cpp
1 /*
2  * (C) 1999-2003 Lars Knoll (knoll@kde.org)
3  * Copyright (C) 2004, 2006, 2007 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 "CSSNamespace.h"
28 #include "CSSParser.h"
29 #include "CSSRuleList.h"
30 #include "CSSStyleRule.h"
31 #include "CachedCSSStyleSheet.h"
32 #include "Document.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 "TextEncoding.h"
41 #include <wtf/Deque.h>
42
43 namespace WebCore {
44
45 class StyleSheetCSSRuleList : public CSSRuleList {
46 public:
47     StyleSheetCSSRuleList(CSSStyleSheet* sheet) : m_styleSheet(sheet) { }
48     
49 private:
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
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 #if ENABLE(SVG)
70         || parentNode->hasTagName(SVGNames::styleTag)
71 #endif
72         || parentNode->nodeType() == Node::PROCESSING_INSTRUCTION_NODE;
73 }
74 #endif
75
76 CSSStyleSheet::CSSStyleSheet(Node* parentNode, const String& href, const KURL& baseURL, const String& charset)
77     : StyleSheet(parentNode, href, baseURL)
78     , m_charset(charset)
79     , m_loadCompleted(false)
80     , m_cssParserMode(CSSQuirksMode)
81     , m_isUserStyleSheet(false)
82     , m_hasSyntacticallyValidCSSHeader(true)
83     , m_didLoadErrorOccur(false)
84 {
85     ASSERT(isAcceptableCSSStyleSheetParent(parentNode));
86 }
87
88 CSSStyleSheet::CSSStyleSheet(CSSImportRule* ownerRule, const String& href, const KURL& baseURL, const String& charset)
89     : StyleSheet(ownerRule, href, baseURL)
90     , m_charset(charset)
91     , m_loadCompleted(false)
92     , m_cssParserMode(ownerRule ? ownerRule->cssParserMode() : CSSStrictMode)
93     , m_hasSyntacticallyValidCSSHeader(true)
94     , m_didLoadErrorOccur(false)
95 {
96     CSSStyleSheet* parentSheet = ownerRule ? ownerRule->parentStyleSheet() : 0;
97     m_isUserStyleSheet = parentSheet ? parentSheet->isUserStyleSheet() : false;
98 }
99
100 CSSStyleSheet::~CSSStyleSheet()
101 {
102     clearRules();
103 }
104
105 void CSSStyleSheet::parserAppendRule(PassRefPtr<CSSRule> rule)
106 {
107     ASSERT(!rule->isCharsetRule());
108     if (rule->isImportRule()) {
109         // Parser enforces that @import rules come before anything else except @charset.
110         ASSERT(m_childRules.isEmpty());
111         m_importRules.append(static_cast<CSSImportRule*>(rule.get()));
112         m_importRules.last()->requestStyleSheet();
113         return;
114     }
115     m_childRules.append(rule);
116 }
117
118 CSSCharsetRule* CSSStyleSheet::ensureCharsetRule()
119 {
120     // Note that mutating charset has absolutely no effect.
121     if (!m_charsetRuleCSSOMWrapper)
122         m_charsetRuleCSSOMWrapper = CSSCharsetRule::create(this, m_encodingFromCharsetRule);
123     return m_charsetRuleCSSOMWrapper.get();
124 }
125
126 unsigned CSSStyleSheet::length() const
127 {
128     unsigned result = 0;
129     result += hasCharsetRule() ? 1 : 0;
130     result += m_importRules.size();
131     result += m_childRules.size();
132     return result;
133 }
134
135 CSSRule* CSSStyleSheet::item(unsigned index)
136 {
137     unsigned childVectorIndex = index;
138     if (hasCharsetRule()) {
139         if (index == 0)
140             return ensureCharsetRule();
141         --childVectorIndex;
142     }
143     if (childVectorIndex < m_importRules.size())
144         return m_importRules[childVectorIndex].get();
145
146     childVectorIndex -= m_importRules.size();
147
148     return childVectorIndex < m_childRules.size() ? m_childRules[childVectorIndex].get() : 0; 
149 }
150
151 void CSSStyleSheet::clearCharsetRule()
152 {
153     m_encodingFromCharsetRule = String();
154     if (m_charsetRuleCSSOMWrapper) {
155         m_charsetRuleCSSOMWrapper->setParentStyleSheet(0);
156         m_charsetRuleCSSOMWrapper.clear();
157     }
158 }
159
160 void CSSStyleSheet::clearRules()
161 {
162     // For style rules outside the document, .parentStyleSheet can become null even if the style rule
163     // is still observable from JavaScript. This matches the behavior of .parentNode for nodes, but
164     // it's not ideal because it makes the CSSOM's behavior depend on the timing of garbage collection.
165     for (unsigned i = 0; i < m_childRules.size(); ++i) {
166         ASSERT(m_childRules[i]->parentStyleSheet() == this);
167         m_childRules[i]->setParentStyleSheet(0);
168     }
169     m_childRules.clear();
170
171     for (unsigned i = 0; i < m_importRules.size(); ++i) {
172         ASSERT(m_importRules.at(i)->parentStyleSheet() == this);
173         m_importRules[i]->setParentStyleSheet(0);
174     }
175     m_importRules.clear();
176
177     clearCharsetRule();
178 }
179
180 void CSSStyleSheet::parserSetEncodingFromCharsetRule(const String& encoding)
181 {
182     // Parser enforces that there is ever only one @charset.
183     ASSERT(m_encodingFromCharsetRule.isNull());
184     m_encodingFromCharsetRule = encoding; 
185 }
186
187 PassRefPtr<CSSRuleList> CSSStyleSheet::rules()
188 {
189     KURL url = finalURL();
190     Document* document = findDocument();
191     if (!url.isEmpty() && document && !document->securityOrigin()->canRequest(url))
192         return 0;
193     // IE behavior.
194     RefPtr<StaticCSSRuleList> nonCharsetRules = StaticCSSRuleList::create();
195     nonCharsetRules->rules().append(m_importRules.data(), m_importRules.size());
196     nonCharsetRules->rules().append(m_childRules.data(), m_childRules.size());
197     return nonCharsetRules.release();
198 }
199
200 unsigned CSSStyleSheet::insertRule(const String& ruleString, unsigned index, ExceptionCode& ec)
201 {
202     ec = 0;
203     if (index > length()) {
204         ec = INDEX_SIZE_ERR;
205         return 0;
206     }
207     CSSParser p(cssParserMode());
208     RefPtr<CSSRule> rule = p.parseRule(this, ruleString);
209
210     if (!rule) {
211         ec = SYNTAX_ERR;
212         return 0;
213     }
214     // Parser::parseRule doesn't currently allow @charset so we don't need to deal with it.
215     ASSERT(!rule->isCharsetRule());
216     
217     unsigned childVectorIndex = index;
218     // m_childRules does not contain @charset which is always in index 0 if it exists.
219     if (hasCharsetRule()) {
220         if (index == 0) {
221             // Nothing can be inserted before @charset.
222             ec = HIERARCHY_REQUEST_ERR;
223             return 0;
224         }
225         --childVectorIndex;
226     }
227     
228     if (childVectorIndex < m_importRules.size() || (childVectorIndex == m_importRules.size() && rule->isImportRule())) {
229         // Inserting non-import rule before @import is not allowed.
230         if (!rule->isImportRule()) {
231             ec = HIERARCHY_REQUEST_ERR;
232             return 0;
233         }
234         m_importRules.insert(childVectorIndex, static_cast<CSSImportRule*>(rule.get()));
235         m_importRules[childVectorIndex]->requestStyleSheet();
236
237         // FIXME: Stylesheet doesn't actually change meaningfully before the imported sheets are loaded.
238         styleSheetChanged();
239         return index;
240     }
241     // Inserting @import rule after a non-import rule is not allowed.
242     if (rule->isImportRule()) {
243         ec = HIERARCHY_REQUEST_ERR;
244         return 0;
245     }
246     childVectorIndex -= m_importRules.size();
247  
248     m_childRules.insert(childVectorIndex, rule.release());
249
250     styleSheetChanged();
251     return index;
252 }
253
254 int CSSStyleSheet::addRule(const String& selector, const String& style, int index, ExceptionCode& ec)
255 {
256     insertRule(selector + " { " + style + " }", index, ec);
257
258     // As per Microsoft documentation, always return -1.
259     return -1;
260 }
261
262 int CSSStyleSheet::addRule(const String& selector, const String& style, ExceptionCode& ec)
263 {
264     return addRule(selector, style, length(), ec);
265 }
266
267 PassRefPtr<CSSRuleList> CSSStyleSheet::cssRules()
268 {
269     KURL url = finalURL();
270     Document* document = findDocument();
271     if (!url.isEmpty() && document && !document->securityOrigin()->canRequest(url))
272         return 0;
273     if (!m_ruleListCSSOMWrapper)
274         m_ruleListCSSOMWrapper = adoptPtr(new StyleSheetCSSRuleList(this));
275     return m_ruleListCSSOMWrapper.get();
276 }
277
278 void CSSStyleSheet::deleteRule(unsigned index, ExceptionCode& ec)
279 {
280     if (index >= length()) {
281         ec = INDEX_SIZE_ERR;
282         return;
283     }
284     
285     ec = 0;
286     unsigned childVectorIndex = index;
287     if (hasCharsetRule()) {
288         if (index == 0) {
289             clearCharsetRule();
290             styleSheetChanged();
291             return;
292         }
293         --childVectorIndex;
294     }
295     if (childVectorIndex < m_importRules.size()) {
296         m_importRules[childVectorIndex]->setParentStyleSheet(0);
297         m_importRules.remove(childVectorIndex);
298         styleSheetChanged();
299         return;
300     }
301     childVectorIndex -= m_importRules.size();
302
303     m_childRules[childVectorIndex]->setParentStyleSheet(0);
304     m_childRules.remove(childVectorIndex);
305     styleSheetChanged();
306 }
307
308 void CSSStyleSheet::addNamespace(CSSParser* p, const AtomicString& prefix, const AtomicString& uri)
309 {
310     if (uri.isNull())
311         return;
312
313     m_namespaces = adoptPtr(new CSSNamespace(prefix, uri, m_namespaces.release()));
314
315     if (prefix.isEmpty())
316         // Set the default namespace on the parser so that selectors that omit namespace info will
317         // be able to pick it up easily.
318         p->m_defaultNamespace = uri;
319 }
320
321 const AtomicString& CSSStyleSheet::determineNamespace(const AtomicString& prefix)
322 {
323     if (prefix.isNull())
324         return nullAtom; // No namespace. If an element/attribute has a namespace, we won't match it.
325     if (prefix == starAtom)
326         return starAtom; // We'll match any namespace.
327     if (m_namespaces) {
328         if (CSSNamespace* namespaceForPrefix = m_namespaces->namespaceForPrefix(prefix))
329             return namespaceForPrefix->uri;
330     }
331     return nullAtom; // Assume we won't match any namespaces.
332 }
333
334 bool CSSStyleSheet::parseString(const String &string, CSSParserMode cssParserMode)
335 {
336     return parseStringAtLine(string, cssParserMode, 0);
337 }
338
339 bool CSSStyleSheet::parseStringAtLine(const String& string, CSSParserMode cssParserMode, int startLineNumber)
340 {
341     setCSSParserMode(cssParserMode);
342     CSSParser p(cssParserMode);
343     p.parseSheet(this, string, startLineNumber);
344     return true;
345 }
346
347 bool CSSStyleSheet::isLoading()
348 {
349     for (unsigned i = 0; i < m_importRules.size(); ++i) {
350         if (m_importRules[i]->isLoading())
351             return true;
352     }
353     return false;
354 }
355
356 void CSSStyleSheet::checkLoaded()
357 {
358     if (isLoading())
359         return;
360
361     // Avoid |this| being deleted by scripts that run via
362     // ScriptableDocumentParser::executeScriptsWaitingForStylesheets().
363     // See <rdar://problem/6622300>.
364     RefPtr<CSSStyleSheet> protector(this);
365     if (CSSStyleSheet* styleSheet = parentStyleSheet())
366         styleSheet->checkLoaded();
367
368     RefPtr<Node> owner = ownerNode();
369     if (!owner)
370         m_loadCompleted = true;
371     else {
372         m_loadCompleted = owner->sheetLoaded();
373         if (m_loadCompleted)
374             owner->notifyLoadedSheetAndAllCriticalSubresources(m_didLoadErrorOccur);
375     }
376 }
377
378 void CSSStyleSheet::notifyLoadedSheet(const CachedCSSStyleSheet* sheet)
379 {
380     ASSERT(sheet);
381     m_didLoadErrorOccur |= sheet->errorOccurred();
382 }
383
384 void CSSStyleSheet::startLoadingDynamicSheet()
385 {
386     if (Node* owner = ownerNode())
387         owner->startLoadingDynamicSheet();
388 }
389
390 Node* CSSStyleSheet::findStyleSheetOwnerNode() const
391 {
392     for (const CSSStyleSheet* sheet = this; sheet; sheet = sheet->parentStyleSheet()) {
393         if (Node* ownerNode = sheet->ownerNode())
394             return ownerNode;
395     }
396     return 0;
397 }
398
399 Document* CSSStyleSheet::findDocument()
400 {
401     Node* ownerNode = findStyleSheetOwnerNode();
402
403     return ownerNode ? ownerNode->document() : 0;
404 }
405     
406 MediaList* CSSStyleSheet::media() const 
407
408     if (!m_mediaQueries)
409         return 0;
410     return m_mediaQueries->ensureMediaList(const_cast<CSSStyleSheet*>(this));
411 }
412
413 void CSSStyleSheet::setMediaQueries(PassRefPtr<MediaQuerySet> mediaQueries)
414 {
415     m_mediaQueries = mediaQueries;
416 }
417
418 void CSSStyleSheet::styleSheetChanged()
419 {
420     CSSStyleSheet* rootSheet = this;
421     while (CSSStyleSheet* parent = rootSheet->parentStyleSheet())
422         rootSheet = parent;
423
424     /* FIXME: We don't need to do everything updateStyleSelector does,
425      * basically we just need to recreate the document's selector with the
426      * already existing style sheets.
427      */
428     if (Document* documentToUpdate = rootSheet->findDocument())
429         documentToUpdate->styleSelectorChanged(DeferRecalcStyle);
430 }
431
432 KURL CSSStyleSheet::completeURL(const String& url) const
433 {
434     // Always return a null URL when passed a null string.
435     // FIXME: Should we change the KURL constructor to have this behavior?
436     // See also Document::completeURL(const String&)
437     if (url.isNull())
438         return KURL();
439     if (m_charset.isEmpty())
440         return KURL(baseURL(), url);
441     const TextEncoding encoding = TextEncoding(m_charset);
442     return KURL(baseURL(), url, encoding);
443 }
444
445 void CSSStyleSheet::addSubresourceStyleURLs(ListHashSet<KURL>& urls)
446 {
447     Deque<CSSStyleSheet*> styleSheetQueue;
448     styleSheetQueue.append(this);
449
450     while (!styleSheetQueue.isEmpty()) {
451         CSSStyleSheet* styleSheet = styleSheetQueue.takeFirst();
452         
453         for (unsigned i = 0; i < styleSheet->m_importRules.size(); ++i) {
454             CSSImportRule* importRule = styleSheet->m_importRules[i].get();
455             if (importRule->styleSheet())
456                 styleSheetQueue.append(importRule->styleSheet());
457             importRule->addSubresourceStyleURLs(urls);
458         }
459
460         for (unsigned i = 0; i < styleSheet->m_childRules.size(); ++i) {
461             CSSRule* rule = styleSheet->m_childRules[i].get();
462             if (rule->isFontFaceRule())
463                 static_cast<CSSFontFaceRule*>(rule)->addSubresourceStyleURLs(urls);
464             else if (rule->isStyleRule())
465                 static_cast<CSSStyleRule*>(rule)->styleRule()->addSubresourceStyleURLs(urls, this);
466         }
467     }
468 }
469
470 }