/* * (C) 1999-2003 Lars Knoll (knoll@kde.org) * Copyright (C) 2004, 2006, 2007, 2012 Apple Inc. All rights reserved. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "config.h" #include "CSSStyleSheet.h" #include "CSSCharsetRule.h" #include "CSSFontFaceRule.h" #include "CSSImportRule.h" #include "CSSParser.h" #include "CSSRuleList.h" #include "CSSStyleRule.h" #include "CachedCSSStyleSheet.h" #include "Document.h" #include "ExceptionCode.h" #include "HTMLNames.h" #include "MediaList.h" #include "MemoryInstrumentation.h" #include "Node.h" #include "SVGNames.h" #include "SecurityOrigin.h" #include "StyleRule.h" #include "StyleSheetContents.h" namespace WebCore { class StyleSheetCSSRuleList : public CSSRuleList { public: StyleSheetCSSRuleList(CSSStyleSheet* sheet) : m_styleSheet(sheet) { } private: virtual void ref() { m_styleSheet->ref(); } virtual void deref() { m_styleSheet->deref(); } virtual unsigned length() const { return m_styleSheet->length(); } virtual CSSRule* item(unsigned index) const { return m_styleSheet->item(index); } virtual CSSStyleSheet* styleSheet() const { return m_styleSheet; } virtual void reportMemoryUsage(MemoryObjectInfo* memoryObjectInfo) const OVERRIDE { MemoryClassInfo info(memoryObjectInfo, this, MemoryInstrumentation::CSS); info.addInstrumentedMember(m_styleSheet); } CSSStyleSheet* m_styleSheet; }; #if !ASSERT_DISABLED static bool isAcceptableCSSStyleSheetParent(Node* parentNode) { // Only these nodes can be parents of StyleSheets, and they need to call clearOwnerNode() when moved out of document. return !parentNode || parentNode->isDocumentNode() || parentNode->hasTagName(HTMLNames::linkTag) || parentNode->hasTagName(HTMLNames::styleTag) #if ENABLE(SVG) || parentNode->hasTagName(SVGNames::styleTag) #endif || parentNode->nodeType() == Node::PROCESSING_INSTRUCTION_NODE; } #endif PassRefPtr CSSStyleSheet::create(PassRefPtr sheet, CSSImportRule* ownerRule) { return adoptRef(new CSSStyleSheet(sheet, ownerRule)); } PassRefPtr CSSStyleSheet::create(PassRefPtr sheet, Node* ownerNode) { return adoptRef(new CSSStyleSheet(sheet, ownerNode, false)); } PassRefPtr CSSStyleSheet::createInline(Node* ownerNode, const KURL& baseURL, const String& encoding) { CSSParserContext parserContext(ownerNode->document(), baseURL, encoding); RefPtr sheet = StyleSheetContents::create(baseURL.string(), parserContext); return adoptRef(new CSSStyleSheet(sheet.release(), ownerNode, true)); } CSSStyleSheet::CSSStyleSheet(PassRefPtr contents, CSSImportRule* ownerRule) : m_contents(contents) , m_isInlineStylesheet(false) , m_isDisabled(false) , m_ownerNode(0) , m_ownerRule(ownerRule) { m_contents->registerClient(this); } CSSStyleSheet::CSSStyleSheet(PassRefPtr contents, Node* ownerNode, bool isInlineStylesheet) : m_contents(contents) , m_isInlineStylesheet(isInlineStylesheet) , m_isDisabled(false) , m_ownerNode(ownerNode) , m_ownerRule(0) { ASSERT(isAcceptableCSSStyleSheetParent(ownerNode)); m_contents->registerClient(this); } CSSStyleSheet::~CSSStyleSheet() { // For style rules outside the document, .parentStyleSheet can become null even if the style rule // is still observable from JavaScript. This matches the behavior of .parentNode for nodes, but // it's not ideal because it makes the CSSOM's behavior depend on the timing of garbage collection. for (unsigned i = 0; i < m_childRuleCSSOMWrappers.size(); ++i) { if (m_childRuleCSSOMWrappers[i]) m_childRuleCSSOMWrappers[i]->setParentStyleSheet(0); } if (m_mediaCSSOMWrapper) m_mediaCSSOMWrapper->clearParentStyleSheet(); m_contents->unregisterClient(this); } void CSSStyleSheet::willMutateRules() { // If we are the only client it is safe to mutate. if (m_contents->hasOneClient() && !m_contents->isInMemoryCache()) { m_contents->setMutable(); return; } // Only cacheable stylesheets should have multiple clients. ASSERT(m_contents->isCacheable()); // Copy-on-write. m_contents->unregisterClient(this); m_contents = m_contents->copy(); m_contents->registerClient(this); m_contents->setMutable(); // Any existing CSSOM wrappers need to be connected to the copied child rules. reattachChildRuleCSSOMWrappers(); } void CSSStyleSheet::didMutateRules() { ASSERT(m_contents->isMutable()); ASSERT(m_contents->hasOneClient()); didMutate(); } void CSSStyleSheet::didMutate() { Document* owner = ownerDocument(); if (!owner) return; owner->styleResolverChanged(DeferRecalcStyle); } void CSSStyleSheet::reattachChildRuleCSSOMWrappers() { for (unsigned i = 0; i < m_childRuleCSSOMWrappers.size(); ++i) { if (!m_childRuleCSSOMWrappers[i]) continue; m_childRuleCSSOMWrappers[i]->reattach(m_contents->ruleAt(i)); } } void CSSStyleSheet::reportMemoryUsage(MemoryObjectInfo* memoryObjectInfo) const { MemoryClassInfo info(memoryObjectInfo, this, MemoryInstrumentation::CSS); info.addInstrumentedMember(m_contents); info.addInstrumentedMember(m_title); info.addInstrumentedMember(m_mediaQueries); info.addInstrumentedMember(m_ownerNode); info.addInstrumentedMember(m_ownerRule); info.addInstrumentedMember(m_mediaCSSOMWrapper); info.addInstrumentedVector(m_childRuleCSSOMWrappers); } void CSSStyleSheet::setDisabled(bool disabled) { if (disabled == m_isDisabled) return; m_isDisabled = disabled; didMutate(); } void CSSStyleSheet::setMediaQueries(PassRefPtr mediaQueries) { m_mediaQueries = mediaQueries; } unsigned CSSStyleSheet::length() const { return m_contents->ruleCount(); } CSSRule* CSSStyleSheet::item(unsigned index) { unsigned ruleCount = length(); if (index >= ruleCount) return 0; if (m_childRuleCSSOMWrappers.isEmpty()) m_childRuleCSSOMWrappers.grow(ruleCount); ASSERT(m_childRuleCSSOMWrappers.size() == ruleCount); RefPtr& cssRule = m_childRuleCSSOMWrappers[index]; if (!cssRule) { if (index == 0 && m_contents->hasCharsetRule()) { ASSERT(!m_contents->ruleAt(0)); cssRule = CSSCharsetRule::create(this, m_contents->encodingFromCharsetRule()); } else cssRule = m_contents->ruleAt(index)->createCSSOMWrapper(this); } return cssRule.get(); } bool CSSStyleSheet::canAccessRules() const { if (m_isInlineStylesheet) return true; KURL baseURL = m_contents->baseURL(); if (baseURL.isEmpty()) return true; Document* document = ownerDocument(); if (!document) return true; if (document->securityOrigin()->canRequest(baseURL)) return true; return false; } PassRefPtr CSSStyleSheet::rules() { if (!canAccessRules()) return 0; // IE behavior. RefPtr nonCharsetRules = StaticCSSRuleList::create(); unsigned ruleCount = length(); for (unsigned i = 0; i < ruleCount; ++i) { CSSRule* rule = item(i); if (rule->isCharsetRule()) continue; nonCharsetRules->rules().append(rule); } return nonCharsetRules.release(); } unsigned CSSStyleSheet::insertRule(const String& ruleString, unsigned index, ExceptionCode& ec) { ASSERT(m_childRuleCSSOMWrappers.isEmpty() || m_childRuleCSSOMWrappers.size() == m_contents->ruleCount()); ec = 0; if (index > length()) { ec = INDEX_SIZE_ERR; return 0; } CSSParser p(m_contents->parserContext()); RefPtr rule = p.parseRule(m_contents.get(), ruleString); if (!rule) { ec = SYNTAX_ERR; return 0; } RuleMutationScope mutationScope(this); bool success = m_contents->wrapperInsertRule(rule, index); if (!success) { ec = HIERARCHY_REQUEST_ERR; return 0; } if (!m_childRuleCSSOMWrappers.isEmpty()) m_childRuleCSSOMWrappers.insert(index, RefPtr()); return index; } void CSSStyleSheet::deleteRule(unsigned index, ExceptionCode& ec) { ASSERT(m_childRuleCSSOMWrappers.isEmpty() || m_childRuleCSSOMWrappers.size() == m_contents->ruleCount()); ec = 0; if (index >= length()) { ec = INDEX_SIZE_ERR; return; } RuleMutationScope mutationScope(this); m_contents->wrapperDeleteRule(index); if (!m_childRuleCSSOMWrappers.isEmpty()) { if (m_childRuleCSSOMWrappers[index]) m_childRuleCSSOMWrappers[index]->setParentStyleSheet(0); m_childRuleCSSOMWrappers.remove(index); } } int CSSStyleSheet::addRule(const String& selector, const String& style, int index, ExceptionCode& ec) { insertRule(selector + " { " + style + " }", index, ec); // As per Microsoft documentation, always return -1. return -1; } int CSSStyleSheet::addRule(const String& selector, const String& style, ExceptionCode& ec) { return addRule(selector, style, length(), ec); } PassRefPtr CSSStyleSheet::cssRules() { if (!canAccessRules()) return 0; if (!m_ruleListCSSOMWrapper) m_ruleListCSSOMWrapper = adoptPtr(new StyleSheetCSSRuleList(this)); return m_ruleListCSSOMWrapper.get(); } String CSSStyleSheet::href() const { return m_contents->originalURL(); } KURL CSSStyleSheet::baseURL() const { return m_contents->baseURL(); } bool CSSStyleSheet::isLoading() const { return m_contents->isLoading(); } MediaList* CSSStyleSheet::media() const { if (!m_mediaQueries) return 0; if (!m_mediaCSSOMWrapper) m_mediaCSSOMWrapper = MediaList::create(m_mediaQueries.get(), const_cast(this)); return m_mediaCSSOMWrapper.get(); } CSSStyleSheet* CSSStyleSheet::parentStyleSheet() const { return m_ownerRule ? m_ownerRule->parentStyleSheet() : 0; } Document* CSSStyleSheet::ownerDocument() const { const CSSStyleSheet* root = this; while (root->parentStyleSheet()) root = root->parentStyleSheet(); return root->ownerNode() ? root->ownerNode()->document() : 0; } void CSSStyleSheet::clearChildRuleCSSOMWrappers() { m_childRuleCSSOMWrappers.clear(); } }