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