#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 "Node.h"
#include "SVGNames.h"
#include "SecurityOrigin.h"
#include "StylePropertySet.h"
#include "StyleRule.h"
#include <wtf/Deque.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; }
CSSStyleSheet* m_styleSheet;
};
#if !ASSERT_DISABLED
static bool isAcceptableCSSStyleSheetParent(Node* parentNode)
{
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
unsigned StyleSheetInternal::estimatedSizeInBytes() const
{
unsigned size = sizeof(*this);
size += ruleCount() * StyleRule::averageSizeInBytes();
for (unsigned i = 0; i < m_importRules.size(); ++i) {
if (StyleSheetInternal* sheet = m_importRules[i]->styleSheet())
size += sheet->estimatedSizeInBytes();
}
return size;
}
StyleSheetInternal::StyleSheetInternal(StyleRuleImport* ownerRule, const String& originalURL, const KURL& finalURL, const CSSParserContext& context)
: m_ownerRule(ownerRule)
, m_originalURL(originalURL)
, m_finalURL(finalURL)
, m_loadCompleted(false)
, m_isUserStyleSheet(ownerRule && ownerRule->parentStyleSheet() && ownerRule->parentStyleSheet()->isUserStyleSheet())
, m_hasSyntacticallyValidCSSHeader(true)
, m_didLoadErrorOccur(false)
, m_usesRemUnits(false)
, m_hasMutated(false)
, m_parserContext(context)
{
}
StyleSheetInternal::StyleSheetInternal(const StyleSheetInternal& o)
: RefCounted<StyleSheetInternal>()
, m_ownerRule(0)
, m_originalURL(o.m_originalURL)
, m_finalURL(o.m_finalURL)
, m_encodingFromCharsetRule(o.m_encodingFromCharsetRule)
, m_importRules(o.m_importRules.size())
, m_childRules(o.m_childRules.size())
, m_namespaces(o.m_namespaces)
, m_loadCompleted(true)
, m_isUserStyleSheet(o.m_isUserStyleSheet)
, m_hasSyntacticallyValidCSSHeader(o.m_hasSyntacticallyValidCSSHeader)
, m_didLoadErrorOccur(false)
, m_usesRemUnits(o.m_usesRemUnits)
, m_hasMutated(false)
, m_parserContext(o.m_parserContext)
{
ASSERT(o.isCacheable());
ASSERT(o.m_importRules.isEmpty());
for (unsigned i = 0; i < m_childRules.size(); ++i)
m_childRules[i] = o.m_childRules[i]->copy();
}
StyleSheetInternal::~StyleSheetInternal()
{
clearRules();
}
bool StyleSheetInternal::isCacheable() const
{
if (!m_importRules.isEmpty())
return false;
if (!m_loadCompleted)
return false;
if (m_didLoadErrorOccur)
return false;
if (m_hasMutated)
return false;
if (!m_hasSyntacticallyValidCSSHeader)
return false;
return true;
}
void StyleSheetInternal::parserAppendRule(PassRefPtr<StyleRuleBase> rule)
{
ASSERT(!rule->isCharsetRule());
if (rule->isImportRule()) {
ASSERT(m_childRules.isEmpty());
m_importRules.append(static_cast<StyleRuleImport*>(rule.get()));
m_importRules.last()->setParentStyleSheet(this);
m_importRules.last()->requestStyleSheet();
return;
}
m_childRules.append(rule);
}
PassRefPtr<CSSRule> StyleSheetInternal::createChildRuleCSSOMWrapper(unsigned index, CSSStyleSheet* parentWrapper)
{
ASSERT(index < ruleCount());
unsigned childVectorIndex = index;
if (hasCharsetRule()) {
if (index == 0)
return CSSCharsetRule::create(parentWrapper, m_encodingFromCharsetRule);
--childVectorIndex;
}
if (childVectorIndex < m_importRules.size())
return m_importRules[childVectorIndex]->createCSSOMWrapper(parentWrapper);
childVectorIndex -= m_importRules.size();
return m_childRules[childVectorIndex]->createCSSOMWrapper(parentWrapper);
}
unsigned StyleSheetInternal::ruleCount() const
{
unsigned result = 0;
result += hasCharsetRule() ? 1 : 0;
result += m_importRules.size();
result += m_childRules.size();
return result;
}
void StyleSheetInternal::clearCharsetRule()
{
m_encodingFromCharsetRule = String();
}
void StyleSheetInternal::clearRules()
{
for (unsigned i = 0; i < m_importRules.size(); ++i) {
ASSERT(m_importRules.at(i)->parentStyleSheet() == this);
m_importRules[i]->clearParentStyleSheet();
}
m_importRules.clear();
m_childRules.clear();
clearCharsetRule();
}
void StyleSheetInternal::parserSetEncodingFromCharsetRule(const String& encoding)
{
ASSERT(m_encodingFromCharsetRule.isNull());
m_encodingFromCharsetRule = encoding;
}
bool StyleSheetInternal::wrapperInsertRule(PassRefPtr<StyleRuleBase> rule, unsigned index)
{
ASSERT(index <= ruleCount());
ASSERT(!rule->isCharsetRule());
unsigned childVectorIndex = index;
if (hasCharsetRule()) {
if (childVectorIndex == 0) {
return false;
}
--childVectorIndex;
}
if (childVectorIndex < m_importRules.size() || (childVectorIndex == m_importRules.size() && rule->isImportRule())) {
if (!rule->isImportRule())
return false;
m_importRules.insert(childVectorIndex, static_cast<StyleRuleImport*>(rule.get()));
m_importRules[childVectorIndex]->setParentStyleSheet(this);
m_importRules[childVectorIndex]->requestStyleSheet();
styleSheetChanged();
return true;
}
if (rule->isImportRule())
return false;
childVectorIndex -= m_importRules.size();
m_childRules.insert(childVectorIndex, rule);
styleSheetChanged();
return true;
}
void StyleSheetInternal::wrapperDeleteRule(unsigned index)
{
ASSERT(index < ruleCount());
unsigned childVectorIndex = index;
if (hasCharsetRule()) {
if (childVectorIndex == 0) {
clearCharsetRule();
styleSheetChanged();
return;
}
--childVectorIndex;
}
if (childVectorIndex < m_importRules.size()) {
m_importRules[childVectorIndex]->clearParentStyleSheet();
m_importRules.remove(childVectorIndex);
styleSheetChanged();
return;
}
childVectorIndex -= m_importRules.size();
m_childRules.remove(childVectorIndex);
styleSheetChanged();
}
void StyleSheetInternal::parserAddNamespace(const AtomicString& prefix, const AtomicString& uri)
{
if (uri.isNull() || prefix.isNull())
return;
m_namespaces.add(prefix, uri);
}
const AtomicString& StyleSheetInternal::determineNamespace(const AtomicString& prefix)
{
if (prefix.isNull())
return nullAtom; if (prefix == starAtom)
return starAtom; PrefixNamespaceURIMap::const_iterator it = m_namespaces.find(prefix);
if (it == m_namespaces.end())
return nullAtom;
return it->second;
}
void StyleSheetInternal::parseAuthorStyleSheet(const CachedCSSStyleSheet* cachedStyleSheet, const SecurityOrigin* securityOrigin)
{
bool enforceMIMEType = isStrictParserMode(m_parserContext.mode) && m_parserContext.enforcesCSSMIMETypeInNoQuirksMode;
bool hasValidMIMEType = false;
String sheetText = cachedStyleSheet->sheetText(enforceMIMEType, &hasValidMIMEType);
CSSParser p(parserContext());
p.parseSheet(this, sheetText, 0);
if (!hasValidMIMEType && !hasSyntacticallyValidCSSHeader()) {
bool isCrossOriginCSS = !securityOrigin || !securityOrigin->canRequest(finalURL());
if (isCrossOriginCSS) {
clearRules();
return;
}
}
if (m_parserContext.needsSiteSpecificQuirks && isStrictParserMode(m_parserContext.mode)) {
DEFINE_STATIC_LOCAL(const String, slashKHTMLFixesDotCss, ("/KHTMLFixes.css"));
DEFINE_STATIC_LOCAL(const String, mediaWikiKHTMLFixesStyleSheet, ("/* KHTML fix stylesheet */\n/* work around the horizontal scrollbars */\n#column-content { margin-left: 0; }\n\n"));
if (finalURL().string().endsWith(slashKHTMLFixesDotCss) && !sheetText.isNull() && mediaWikiKHTMLFixesStyleSheet.startsWith(sheetText)
&& sheetText.length() >= mediaWikiKHTMLFixesStyleSheet.length() - 1)
clearRules();
}
}
bool StyleSheetInternal::parseString(const String& sheetText)
{
return parseStringAtLine(sheetText, 0);
}
bool StyleSheetInternal::parseStringAtLine(const String& sheetText, int startLineNumber)
{
CSSParser p(parserContext());
p.parseSheet(this, sheetText, startLineNumber);
return true;
}
bool StyleSheetInternal::isLoading() const
{
for (unsigned i = 0; i < m_importRules.size(); ++i) {
if (m_importRules[i]->isLoading())
return true;
}
return false;
}
void StyleSheetInternal::checkLoaded()
{
if (isLoading())
return;
RefPtr<StyleSheetInternal> protector(this);
StyleSheetInternal* parentSheet = parentStyleSheet();
if (parentSheet) {
parentSheet->checkLoaded();
m_loadCompleted = true;
return;
}
RefPtr<Node> ownerNode = singleOwnerNode();
if (!ownerNode) {
m_loadCompleted = true;
return;
}
m_loadCompleted = ownerNode->sheetLoaded();
if (m_loadCompleted)
ownerNode->notifyLoadedSheetAndAllCriticalSubresources(m_didLoadErrorOccur);
}
void StyleSheetInternal::notifyLoadedSheet(const CachedCSSStyleSheet* sheet)
{
ASSERT(sheet);
m_didLoadErrorOccur |= sheet->errorOccurred();
}
void StyleSheetInternal::startLoadingDynamicSheet()
{
if (Node* owner = singleOwnerNode())
owner->startLoadingDynamicSheet();
}
StyleSheetInternal* StyleSheetInternal::rootStyleSheet() const
{
const StyleSheetInternal* root = this;
while (root->parentStyleSheet())
root = root->parentStyleSheet();
return const_cast<StyleSheetInternal*>(root);
}
Node* StyleSheetInternal::singleOwnerNode() const
{
StyleSheetInternal* root = rootStyleSheet();
if (root->m_clients.isEmpty())
return 0;
ASSERT(root->m_clients.size() == 1);
return root->m_clients[0]->ownerNode();
}
Document* StyleSheetInternal::singleOwnerDocument() const
{
Node* ownerNode = singleOwnerNode();
return ownerNode ? ownerNode->document() : 0;
}
void StyleSheetInternal::styleSheetChanged()
{
m_hasMutated = true;
Document* ownerDocument = singleOwnerDocument();
if (!ownerDocument)
return;
ownerDocument->styleResolverChanged(DeferRecalcStyle);
}
KURL StyleSheetInternal::completeURL(const String& url) const
{
return CSSParser::completeURL(m_parserContext, url);
}
void StyleSheetInternal::addSubresourceStyleURLs(ListHashSet<KURL>& urls)
{
Deque<StyleSheetInternal*> styleSheetQueue;
styleSheetQueue.append(this);
while (!styleSheetQueue.isEmpty()) {
StyleSheetInternal* styleSheet = styleSheetQueue.takeFirst();
for (unsigned i = 0; i < styleSheet->m_importRules.size(); ++i) {
StyleRuleImport* importRule = styleSheet->m_importRules[i].get();
if (importRule->styleSheet()) {
styleSheetQueue.append(importRule->styleSheet());
addSubresourceURL(urls, importRule->styleSheet()->baseURL());
}
}
for (unsigned i = 0; i < styleSheet->m_childRules.size(); ++i) {
StyleRuleBase* rule = styleSheet->m_childRules[i].get();
if (rule->isStyleRule())
static_cast<StyleRule*>(rule)->properties()->addSubresourceStyleURLs(urls, this);
else if (rule->isFontFaceRule())
static_cast<StyleRuleFontFace*>(rule)->properties()->addSubresourceStyleURLs(urls, this);
}
}
}
StyleSheetInternal* StyleSheetInternal::parentStyleSheet() const
{
return m_ownerRule ? m_ownerRule->parentStyleSheet() : 0;
}
void StyleSheetInternal::registerClient(CSSStyleSheet* sheet)
{
ASSERT(!m_clients.contains(sheet));
m_clients.append(sheet);
}
void StyleSheetInternal::unregisterClient(CSSStyleSheet* sheet)
{
size_t position = m_clients.find(sheet);
ASSERT(position != notFound);
m_clients.remove(position);
}
PassRefPtr<CSSStyleSheet> CSSStyleSheet::createInline(Node* ownerNode, const KURL& baseURL, const String& encoding)
{
CSSParserContext parserContext(ownerNode->document(), baseURL, encoding);
RefPtr<StyleSheetInternal> sheet = StyleSheetInternal::create(baseURL.string(), baseURL, parserContext);
return adoptRef(new CSSStyleSheet(sheet.release(), ownerNode));
}
CSSStyleSheet::CSSStyleSheet(PassRefPtr<StyleSheetInternal> styleSheet, CSSImportRule* ownerRule)
: m_internal(styleSheet)
, m_isDisabled(false)
, m_ownerNode(0)
, m_ownerRule(ownerRule)
{
m_internal->registerClient(this);
}
CSSStyleSheet::CSSStyleSheet(PassRefPtr<StyleSheetInternal> styleSheet, Node* ownerNode)
: m_internal(styleSheet)
, m_isDisabled(false)
, m_ownerNode(ownerNode)
, m_ownerRule(0)
{
ASSERT(isAcceptableCSSStyleSheetParent(ownerNode));
m_internal->registerClient(this);
}
CSSStyleSheet::~CSSStyleSheet()
{
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_internal->unregisterClient(this);
}
void CSSStyleSheet::setDisabled(bool disabled)
{
if (disabled == m_isDisabled)
return;
m_isDisabled = disabled;
Document* owner = ownerDocument();
if (owner)
owner->styleResolverChanged(DeferRecalcStyle);
}
void CSSStyleSheet::setMediaQueries(PassRefPtr<MediaQuerySet> mediaQueries)
{
m_mediaQueries = mediaQueries;
}
unsigned CSSStyleSheet::length() const
{
return m_internal->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>& cssRule = m_childRuleCSSOMWrappers[index];
if (!cssRule)
cssRule = m_internal->createChildRuleCSSOMWrapper(index, this);
return cssRule.get();
}
PassRefPtr<CSSRuleList> CSSStyleSheet::rules()
{
KURL url = m_internal->finalURL();
Document* document = ownerDocument();
if (!url.isEmpty() && document && !document->securityOrigin()->canRequest(url))
return 0;
RefPtr<StaticCSSRuleList> 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_internal->ruleCount());
ec = 0;
if (index > length()) {
ec = INDEX_SIZE_ERR;
return 0;
}
CSSParser p(m_internal->parserContext());
RefPtr<StyleRuleBase> rule = p.parseRule(m_internal.get(), ruleString);
if (!rule) {
ec = SYNTAX_ERR;
return 0;
}
bool success = m_internal->wrapperInsertRule(rule, index);
if (!success) {
ec = HIERARCHY_REQUEST_ERR;
return 0;
}
if (!m_childRuleCSSOMWrappers.isEmpty())
m_childRuleCSSOMWrappers.insert(index, RefPtr<CSSRule>());
return index;
}
void CSSStyleSheet::deleteRule(unsigned index, ExceptionCode& ec)
{
ASSERT(m_childRuleCSSOMWrappers.isEmpty() || m_childRuleCSSOMWrappers.size() == m_internal->ruleCount());
ec = 0;
if (index >= length()) {
ec = INDEX_SIZE_ERR;
return;
}
m_internal->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);
return -1;
}
int CSSStyleSheet::addRule(const String& selector, const String& style, ExceptionCode& ec)
{
return addRule(selector, style, length(), ec);
}
PassRefPtr<CSSRuleList> CSSStyleSheet::cssRules()
{
KURL url = m_internal->finalURL();
Document* document = ownerDocument();
if (!url.isEmpty() && document && !document->securityOrigin()->canRequest(url))
return 0;
if (!m_ruleListCSSOMWrapper)
m_ruleListCSSOMWrapper = adoptPtr(new StyleSheetCSSRuleList(this));
return m_ruleListCSSOMWrapper.get();
}
MediaList* CSSStyleSheet::media() const
{
if (!m_mediaQueries)
return 0;
if (!m_mediaCSSOMWrapper)
m_mediaCSSOMWrapper = MediaList::create(m_mediaQueries.get(), const_cast<CSSStyleSheet*>(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();
}
}