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 "StylePropertySet.h"
40 #include "StyleRule.h"
41 #include "TextEncoding.h"
42 #include <wtf/Deque.h>
46 class StyleSheetCSSRuleList : public CSSRuleList {
48 StyleSheetCSSRuleList(CSSStyleSheet* sheet) : m_styleSheet(sheet) { }
51 virtual void ref() { m_styleSheet->ref(); }
52 virtual void deref() { m_styleSheet->deref(); }
54 virtual unsigned length() const { return m_styleSheet->length(); }
55 virtual CSSRule* item(unsigned index) const { return m_styleSheet->item(index); }
57 virtual CSSStyleSheet* styleSheet() const { return m_styleSheet; }
59 CSSStyleSheet* m_styleSheet;
63 static bool isAcceptableCSSStyleSheetParent(Node* parentNode)
65 // Only these nodes can be parents of StyleSheets, and they need to call clearOwnerNode() when moved out of document.
67 || parentNode->isDocumentNode()
68 || parentNode->hasTagName(HTMLNames::linkTag)
69 || parentNode->hasTagName(HTMLNames::styleTag)
71 || parentNode->hasTagName(SVGNames::styleTag)
73 || parentNode->nodeType() == Node::PROCESSING_INSTRUCTION_NODE;
77 CSSStyleSheet::CSSStyleSheet(Node* parentNode, const String& href, const KURL& baseURL, const String& charset)
78 : StyleSheet(parentNode, href, baseURL)
81 , m_loadCompleted(false)
82 , m_cssParserMode(CSSQuirksMode)
83 , m_isUserStyleSheet(false)
84 , m_hasSyntacticallyValidCSSHeader(true)
85 , m_didLoadErrorOccur(false)
87 ASSERT(isAcceptableCSSStyleSheetParent(parentNode));
90 CSSStyleSheet::CSSStyleSheet(StyleRuleImport* ownerRule, const String& href, const KURL& baseURL, const String& charset)
91 : StyleSheet(href, baseURL)
92 , m_ownerRule(ownerRule)
94 , m_loadCompleted(false)
95 , m_cssParserMode((ownerRule && ownerRule->parentStyleSheet()) ? ownerRule->parentStyleSheet()->cssParserMode() : CSSStrictMode)
96 , m_hasSyntacticallyValidCSSHeader(true)
97 , m_didLoadErrorOccur(false)
99 CSSStyleSheet* parentSheet = ownerRule ? ownerRule->parentStyleSheet() : 0;
100 m_isUserStyleSheet = parentSheet ? parentSheet->isUserStyleSheet() : false;
103 CSSStyleSheet::~CSSStyleSheet()
108 void CSSStyleSheet::parserAppendRule(PassRefPtr<StyleRuleBase> rule)
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();
118 m_childRules.append(rule);
121 PassRefPtr<CSSRule> CSSStyleSheet::createChildRuleCSSOMWrapper(unsigned index)
123 ASSERT(index < length());
125 unsigned childVectorIndex = index;
126 if (hasCharsetRule()) {
128 return CSSCharsetRule::create(this, m_encodingFromCharsetRule);
131 if (childVectorIndex < m_importRules.size())
132 return m_importRules[childVectorIndex]->createCSSOMWrapper(this);
134 childVectorIndex -= m_importRules.size();
135 return m_childRules[childVectorIndex]->createCSSOMWrapper(this);
138 unsigned CSSStyleSheet::length() const
141 result += hasCharsetRule() ? 1 : 0;
142 result += m_importRules.size();
143 result += m_childRules.size();
147 CSSRule* CSSStyleSheet::item(unsigned index)
149 unsigned ruleCount = length();
150 if (index >= ruleCount)
153 if (m_childRuleCSSOMWrappers.isEmpty())
154 m_childRuleCSSOMWrappers.grow(ruleCount);
155 ASSERT(m_childRuleCSSOMWrappers.size() == ruleCount);
157 RefPtr<CSSRule>& cssRule = m_childRuleCSSOMWrappers[index];
159 cssRule = createChildRuleCSSOMWrapper(index);
160 return cssRule.get();
163 void CSSStyleSheet::clearCharsetRule()
165 m_encodingFromCharsetRule = String();
168 void CSSStyleSheet::clearRules()
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);
177 m_childRuleCSSOMWrappers.clear();
179 for (unsigned i = 0; i < m_importRules.size(); ++i) {
180 ASSERT(m_importRules.at(i)->parentStyleSheet() == this);
181 m_importRules[i]->clearParentStyleSheet();
183 m_importRules.clear();
184 m_childRules.clear();
188 void CSSStyleSheet::parserSetEncodingFromCharsetRule(const String& encoding)
190 // Parser enforces that there is ever only one @charset.
191 ASSERT(m_encodingFromCharsetRule.isNull());
192 m_encodingFromCharsetRule = encoding;
195 PassRefPtr<CSSRuleList> CSSStyleSheet::rules()
197 KURL url = finalURL();
198 Document* document = findDocument();
199 if (!url.isEmpty() && document && !document->securityOrigin()->canRequest(url))
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())
208 nonCharsetRules->rules().append(rule);
210 return nonCharsetRules.release();
213 unsigned CSSStyleSheet::insertRule(const String& ruleString, unsigned index, ExceptionCode& ec)
216 if (index > length()) {
220 CSSParser p(cssParserMode());
221 RefPtr<StyleRuleBase> rule = p.parseRule(this, ruleString);
227 // Parser::parseRule doesn't currently allow @charset so we don't need to deal with it.
228 ASSERT(!rule->isCharsetRule());
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;
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;
247 m_importRules.insert(childVectorIndex, static_cast<StyleRuleImport*>(rule.get()));
248 m_importRules[childVectorIndex]->requestStyleSheet();
251 // Inserting @import rule after a non-import rule is not allowed.
252 if (rule->isImportRule()) {
253 ec = HIERARCHY_REQUEST_ERR;
256 childVectorIndex -= m_importRules.size();
258 m_childRules.insert(childVectorIndex, rule.release());
261 if (!m_childRuleCSSOMWrappers.isEmpty())
262 m_childRuleCSSOMWrappers.insert(index, RefPtr<CSSRule>());
264 // FIXME: Stylesheet doesn't actually change meaningfully before the imported sheets are loaded.
269 int CSSStyleSheet::addRule(const String& selector, const String& style, int index, ExceptionCode& ec)
271 insertRule(selector + " { " + style + " }", index, ec);
273 // As per Microsoft documentation, always return -1.
277 int CSSStyleSheet::addRule(const String& selector, const String& style, ExceptionCode& ec)
279 return addRule(selector, style, length(), ec);
282 PassRefPtr<CSSRuleList> CSSStyleSheet::cssRules()
284 KURL url = finalURL();
285 Document* document = findDocument();
286 if (!url.isEmpty() && document && !document->securityOrigin()->canRequest(url))
288 if (!m_ruleListCSSOMWrapper)
289 m_ruleListCSSOMWrapper = adoptPtr(new StyleSheetCSSRuleList(this));
290 return m_ruleListCSSOMWrapper.get();
293 void CSSStyleSheet::deleteRule(unsigned index, ExceptionCode& ec)
295 if (index >= length()) {
301 unsigned childVectorIndex = index;
302 if (hasCharsetRule()) {
303 if (childVectorIndex == 0) {
309 if (childVectorIndex < m_importRules.size()) {
310 m_importRules[childVectorIndex]->clearParentStyleSheet();
311 m_importRules.remove(childVectorIndex);
314 childVectorIndex -= m_importRules.size();
316 m_childRules.remove(childVectorIndex);
319 if (!m_childRuleCSSOMWrappers.isEmpty()) {
320 m_childRuleCSSOMWrappers[index]->setParentStyleSheet(0);
321 m_childRuleCSSOMWrappers.remove(index);
327 void CSSStyleSheet::addNamespace(CSSParser* p, const AtomicString& prefix, const AtomicString& uri)
332 m_namespaces = adoptPtr(new CSSNamespace(prefix, uri, m_namespaces.release()));
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;
340 const AtomicString& CSSStyleSheet::determineNamespace(const AtomicString& prefix)
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.
347 if (CSSNamespace* namespaceForPrefix = m_namespaces->namespaceForPrefix(prefix))
348 return namespaceForPrefix->uri;
350 return nullAtom; // Assume we won't match any namespaces.
353 bool CSSStyleSheet::parseString(const String &string, CSSParserMode cssParserMode)
355 return parseStringAtLine(string, cssParserMode, 0);
358 bool CSSStyleSheet::parseStringAtLine(const String& string, CSSParserMode cssParserMode, int startLineNumber)
360 setCSSParserMode(cssParserMode);
361 CSSParser p(cssParserMode);
362 p.parseSheet(this, string, startLineNumber);
366 bool CSSStyleSheet::isLoading()
368 for (unsigned i = 0; i < m_importRules.size(); ++i) {
369 if (m_importRules[i]->isLoading())
375 void CSSStyleSheet::checkLoaded()
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();
387 RefPtr<Node> owner = ownerNode();
389 m_loadCompleted = true;
391 m_loadCompleted = owner->sheetLoaded();
393 owner->notifyLoadedSheetAndAllCriticalSubresources(m_didLoadErrorOccur);
397 void CSSStyleSheet::notifyLoadedSheet(const CachedCSSStyleSheet* sheet)
400 m_didLoadErrorOccur |= sheet->errorOccurred();
403 void CSSStyleSheet::startLoadingDynamicSheet()
405 if (Node* owner = ownerNode())
406 owner->startLoadingDynamicSheet();
409 Node* CSSStyleSheet::findStyleSheetOwnerNode() const
411 for (const CSSStyleSheet* sheet = this; sheet; sheet = sheet->parentStyleSheet()) {
412 if (Node* ownerNode = sheet->ownerNode())
418 Document* CSSStyleSheet::findDocument()
420 Node* ownerNode = findStyleSheetOwnerNode();
422 return ownerNode ? ownerNode->document() : 0;
425 MediaList* CSSStyleSheet::media() const
429 return m_mediaQueries->ensureMediaList(const_cast<CSSStyleSheet*>(this));
432 void CSSStyleSheet::setMediaQueries(PassRefPtr<MediaQuerySet> mediaQueries)
434 m_mediaQueries = mediaQueries;
437 void CSSStyleSheet::styleSheetChanged()
439 CSSStyleSheet* rootSheet = this;
440 while (CSSStyleSheet* parent = rootSheet->parentStyleSheet())
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.
447 if (Document* documentToUpdate = rootSheet->findDocument())
448 documentToUpdate->styleSelectorChanged(DeferRecalcStyle);
451 KURL CSSStyleSheet::completeURL(const String& url) const
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&)
458 if (m_charset.isEmpty())
459 return KURL(baseURL(), url);
460 const TextEncoding encoding = TextEncoding(m_charset);
461 return KURL(baseURL(), url, encoding);
464 void CSSStyleSheet::addSubresourceStyleURLs(ListHashSet<KURL>& urls)
466 Deque<CSSStyleSheet*> styleSheetQueue;
467 styleSheetQueue.append(this);
469 while (!styleSheetQueue.isEmpty()) {
470 CSSStyleSheet* styleSheet = styleSheetQueue.takeFirst();
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());
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);
489 CSSImportRule* CSSStyleSheet::ensureCSSOMWrapper(StyleRuleImport* importRule)
491 for (unsigned i = 0; i < m_importRules.size(); ++i) {
492 if (m_importRules[i] == importRule)
493 return static_cast<CSSImportRule*>(item(i));
495 ASSERT_NOT_REACHED();
499 CSSImportRule* CSSStyleSheet::ownerRule() const
501 return parentStyleSheet() ? parentStyleSheet()->ensureCSSOMWrapper(m_ownerRule) : 0;
504 CSSStyleSheet* CSSStyleSheet::parentStyleSheet() const
506 ASSERT(isCSSStyleSheet());
507 return m_ownerRule ? m_ownerRule->parentStyleSheet() : 0;