2 * (C) 1999-2003 Lars Knoll (knoll@kde.org)
3 * Copyright (C) 2004, 2006, 2007 Apple Inc. All rights reserved.
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.
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.
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.
22 #include "CSSStyleSheet.h"
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"
33 #include "ExceptionCode.h"
34 #include "HTMLNames.h"
35 #include "MediaList.h"
38 #include "SecurityOrigin.h"
39 #include "StyleRule.h"
40 #include "TextEncoding.h"
41 #include <wtf/Deque.h>
45 class StyleSheetCSSRuleList : public CSSRuleList {
47 StyleSheetCSSRuleList(CSSStyleSheet* sheet) : m_styleSheet(sheet) { }
50 virtual void ref() { m_styleSheet->ref(); }
51 virtual void deref() { m_styleSheet->deref(); }
53 virtual unsigned length() const { return m_styleSheet->length(); }
54 virtual CSSRule* item(unsigned index) const { return m_styleSheet->item(index); }
56 virtual CSSStyleSheet* styleSheet() const { return m_styleSheet; }
58 CSSStyleSheet* m_styleSheet;
62 static bool isAcceptableCSSStyleSheetParent(Node* parentNode)
64 // Only these nodes can be parents of StyleSheets, and they need to call clearOwnerNode() when moved out of document.
66 || parentNode->isDocumentNode()
67 || parentNode->hasTagName(HTMLNames::linkTag)
68 || parentNode->hasTagName(HTMLNames::styleTag)
70 || parentNode->hasTagName(SVGNames::styleTag)
72 || parentNode->nodeType() == Node::PROCESSING_INSTRUCTION_NODE;
76 CSSStyleSheet::CSSStyleSheet(Node* parentNode, const String& href, const KURL& baseURL, const String& charset)
77 : StyleSheet(parentNode, href, baseURL)
79 , m_loadCompleted(false)
80 , m_cssParserMode(CSSQuirksMode)
81 , m_isUserStyleSheet(false)
82 , m_hasSyntacticallyValidCSSHeader(true)
83 , m_didLoadErrorOccur(false)
85 ASSERT(isAcceptableCSSStyleSheetParent(parentNode));
88 CSSStyleSheet::CSSStyleSheet(CSSImportRule* ownerRule, const String& href, const KURL& baseURL, const String& charset)
89 : StyleSheet(ownerRule, href, baseURL)
91 , m_loadCompleted(false)
92 , m_cssParserMode(ownerRule ? ownerRule->cssParserMode() : CSSStrictMode)
93 , m_hasSyntacticallyValidCSSHeader(true)
94 , m_didLoadErrorOccur(false)
96 CSSStyleSheet* parentSheet = ownerRule ? ownerRule->parentStyleSheet() : 0;
97 m_isUserStyleSheet = parentSheet ? parentSheet->isUserStyleSheet() : false;
100 CSSStyleSheet::~CSSStyleSheet()
105 void CSSStyleSheet::parserAppendRule(PassRefPtr<CSSRule> rule)
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();
115 m_childRules.append(rule);
118 CSSCharsetRule* CSSStyleSheet::ensureCharsetRule()
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();
126 unsigned CSSStyleSheet::length() const
129 result += hasCharsetRule() ? 1 : 0;
130 result += m_importRules.size();
131 result += m_childRules.size();
135 CSSRule* CSSStyleSheet::item(unsigned index)
137 unsigned childVectorIndex = index;
138 if (hasCharsetRule()) {
140 return ensureCharsetRule();
143 if (childVectorIndex < m_importRules.size())
144 return m_importRules[childVectorIndex].get();
146 childVectorIndex -= m_importRules.size();
148 return childVectorIndex < m_childRules.size() ? m_childRules[childVectorIndex].get() : 0;
151 void CSSStyleSheet::clearCharsetRule()
153 m_encodingFromCharsetRule = String();
154 if (m_charsetRuleCSSOMWrapper) {
155 m_charsetRuleCSSOMWrapper->setParentStyleSheet(0);
156 m_charsetRuleCSSOMWrapper.clear();
160 void CSSStyleSheet::clearRules()
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);
169 m_childRules.clear();
171 for (unsigned i = 0; i < m_importRules.size(); ++i) {
172 ASSERT(m_importRules.at(i)->parentStyleSheet() == this);
173 m_importRules[i]->setParentStyleSheet(0);
175 m_importRules.clear();
180 void CSSStyleSheet::parserSetEncodingFromCharsetRule(const String& encoding)
182 // Parser enforces that there is ever only one @charset.
183 ASSERT(m_encodingFromCharsetRule.isNull());
184 m_encodingFromCharsetRule = encoding;
187 PassRefPtr<CSSRuleList> CSSStyleSheet::rules()
189 KURL url = finalURL();
190 Document* document = findDocument();
191 if (!url.isEmpty() && document && !document->securityOrigin()->canRequest(url))
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();
200 unsigned CSSStyleSheet::insertRule(const String& ruleString, unsigned index, ExceptionCode& ec)
203 if (index > length()) {
207 CSSParser p(cssParserMode());
208 RefPtr<CSSRule> rule = p.parseRule(this, ruleString);
214 // Parser::parseRule doesn't currently allow @charset so we don't need to deal with it.
215 ASSERT(!rule->isCharsetRule());
217 unsigned childVectorIndex = index;
218 // m_childRules does not contain @charset which is always in index 0 if it exists.
219 if (hasCharsetRule()) {
221 // Nothing can be inserted before @charset.
222 ec = HIERARCHY_REQUEST_ERR;
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;
234 m_importRules.insert(childVectorIndex, static_cast<CSSImportRule*>(rule.get()));
235 m_importRules[childVectorIndex]->requestStyleSheet();
237 // FIXME: Stylesheet doesn't actually change meaningfully before the imported sheets are loaded.
241 // Inserting @import rule after a non-import rule is not allowed.
242 if (rule->isImportRule()) {
243 ec = HIERARCHY_REQUEST_ERR;
246 childVectorIndex -= m_importRules.size();
248 m_childRules.insert(childVectorIndex, rule.release());
254 int CSSStyleSheet::addRule(const String& selector, const String& style, int index, ExceptionCode& ec)
256 insertRule(selector + " { " + style + " }", index, ec);
258 // As per Microsoft documentation, always return -1.
262 int CSSStyleSheet::addRule(const String& selector, const String& style, ExceptionCode& ec)
264 return addRule(selector, style, length(), ec);
267 PassRefPtr<CSSRuleList> CSSStyleSheet::cssRules()
269 KURL url = finalURL();
270 Document* document = findDocument();
271 if (!url.isEmpty() && document && !document->securityOrigin()->canRequest(url))
273 if (!m_ruleListCSSOMWrapper)
274 m_ruleListCSSOMWrapper = adoptPtr(new StyleSheetCSSRuleList(this));
275 return m_ruleListCSSOMWrapper.get();
278 void CSSStyleSheet::deleteRule(unsigned index, ExceptionCode& ec)
280 if (index >= length()) {
286 unsigned childVectorIndex = index;
287 if (hasCharsetRule()) {
295 if (childVectorIndex < m_importRules.size()) {
296 m_importRules[childVectorIndex]->setParentStyleSheet(0);
297 m_importRules.remove(childVectorIndex);
301 childVectorIndex -= m_importRules.size();
303 m_childRules[childVectorIndex]->setParentStyleSheet(0);
304 m_childRules.remove(childVectorIndex);
308 void CSSStyleSheet::addNamespace(CSSParser* p, const AtomicString& prefix, const AtomicString& uri)
313 m_namespaces = adoptPtr(new CSSNamespace(prefix, uri, m_namespaces.release()));
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;
321 const AtomicString& CSSStyleSheet::determineNamespace(const AtomicString& prefix)
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.
328 if (CSSNamespace* namespaceForPrefix = m_namespaces->namespaceForPrefix(prefix))
329 return namespaceForPrefix->uri;
331 return nullAtom; // Assume we won't match any namespaces.
334 bool CSSStyleSheet::parseString(const String &string, CSSParserMode cssParserMode)
336 return parseStringAtLine(string, cssParserMode, 0);
339 bool CSSStyleSheet::parseStringAtLine(const String& string, CSSParserMode cssParserMode, int startLineNumber)
341 setCSSParserMode(cssParserMode);
342 CSSParser p(cssParserMode);
343 p.parseSheet(this, string, startLineNumber);
347 bool CSSStyleSheet::isLoading()
349 for (unsigned i = 0; i < m_importRules.size(); ++i) {
350 if (m_importRules[i]->isLoading())
356 void CSSStyleSheet::checkLoaded()
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();
368 RefPtr<Node> owner = ownerNode();
370 m_loadCompleted = true;
372 m_loadCompleted = owner->sheetLoaded();
374 owner->notifyLoadedSheetAndAllCriticalSubresources(m_didLoadErrorOccur);
378 void CSSStyleSheet::notifyLoadedSheet(const CachedCSSStyleSheet* sheet)
381 m_didLoadErrorOccur |= sheet->errorOccurred();
384 void CSSStyleSheet::startLoadingDynamicSheet()
386 if (Node* owner = ownerNode())
387 owner->startLoadingDynamicSheet();
390 Node* CSSStyleSheet::findStyleSheetOwnerNode() const
392 for (const CSSStyleSheet* sheet = this; sheet; sheet = sheet->parentStyleSheet()) {
393 if (Node* ownerNode = sheet->ownerNode())
399 Document* CSSStyleSheet::findDocument()
401 Node* ownerNode = findStyleSheetOwnerNode();
403 return ownerNode ? ownerNode->document() : 0;
406 MediaList* CSSStyleSheet::media() const
410 return m_mediaQueries->ensureMediaList(const_cast<CSSStyleSheet*>(this));
413 void CSSStyleSheet::setMediaQueries(PassRefPtr<MediaQuerySet> mediaQueries)
415 m_mediaQueries = mediaQueries;
418 void CSSStyleSheet::styleSheetChanged()
420 CSSStyleSheet* rootSheet = this;
421 while (CSSStyleSheet* parent = rootSheet->parentStyleSheet())
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.
428 if (Document* documentToUpdate = rootSheet->findDocument())
429 documentToUpdate->styleSelectorChanged(DeferRecalcStyle);
432 KURL CSSStyleSheet::completeURL(const String& url) const
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&)
439 if (m_charset.isEmpty())
440 return KURL(baseURL(), url);
441 const TextEncoding encoding = TextEncoding(m_charset);
442 return KURL(baseURL(), url, encoding);
445 void CSSStyleSheet::addSubresourceStyleURLs(ListHashSet<KURL>& urls)
447 Deque<CSSStyleSheet*> styleSheetQueue;
448 styleSheetQueue.append(this);
450 while (!styleSheetQueue.isEmpty()) {
451 CSSStyleSheet* styleSheet = styleSheetQueue.takeFirst();
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);
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);