Home | History | Annotate | Download | only in css
      1 /*
      2  * (C) 1999-2003 Lars Knoll (knoll (at) kde.org)
      3  * Copyright (C) 2004, 2006, 2007, 2012 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 "core/css/StyleSheetContents.h"
     23 
     24 #include "core/css/CSSParser.h"
     25 #include "core/css/CSSStyleSheet.h"
     26 #include "core/css/MediaList.h"
     27 #include "core/css/StylePropertySet.h"
     28 #include "core/css/StyleRule.h"
     29 #include "core/css/StyleRuleImport.h"
     30 #include "core/dom/Node.h"
     31 #include "core/loader/cache/CSSStyleSheetResource.h"
     32 #include "weborigin/SecurityOrigin.h"
     33 #include "wtf/Deque.h"
     34 
     35 namespace WebCore {
     36 
     37 // Rough size estimate for the memory cache.
     38 unsigned StyleSheetContents::estimatedSizeInBytes() const
     39 {
     40     // Note that this does not take into account size of the strings hanging from various objects.
     41     // The assumption is that nearly all of of them are atomic and would exist anyway.
     42     unsigned size = sizeof(*this);
     43 
     44     // FIXME: This ignores the children of media and region rules.
     45     // Most rules are StyleRules.
     46     size += ruleCount() * StyleRule::averageSizeInBytes();
     47 
     48     for (unsigned i = 0; i < m_importRules.size(); ++i) {
     49         if (StyleSheetContents* sheet = m_importRules[i]->styleSheet())
     50             size += sheet->estimatedSizeInBytes();
     51     }
     52     return size;
     53 }
     54 
     55 StyleSheetContents::StyleSheetContents(StyleRuleImport* ownerRule, const String& originalURL, const CSSParserContext& context)
     56     : m_ownerRule(ownerRule)
     57     , m_originalURL(originalURL)
     58     , m_loadCompleted(false)
     59     , m_isUserStyleSheet(ownerRule && ownerRule->parentStyleSheet() && ownerRule->parentStyleSheet()->isUserStyleSheet())
     60     , m_hasSyntacticallyValidCSSHeader(true)
     61     , m_didLoadErrorOccur(false)
     62     , m_usesRemUnits(false)
     63     , m_isMutable(false)
     64     , m_isInMemoryCache(false)
     65     , m_parserContext(context)
     66 {
     67 }
     68 
     69 StyleSheetContents::StyleSheetContents(const StyleSheetContents& o)
     70     : RefCounted<StyleSheetContents>()
     71     , m_ownerRule(0)
     72     , m_originalURL(o.m_originalURL)
     73     , m_encodingFromCharsetRule(o.m_encodingFromCharsetRule)
     74     , m_importRules(o.m_importRules.size())
     75     , m_childRules(o.m_childRules.size())
     76     , m_namespaces(o.m_namespaces)
     77     , m_loadCompleted(true)
     78     , m_isUserStyleSheet(o.m_isUserStyleSheet)
     79     , m_hasSyntacticallyValidCSSHeader(o.m_hasSyntacticallyValidCSSHeader)
     80     , m_didLoadErrorOccur(false)
     81     , m_usesRemUnits(o.m_usesRemUnits)
     82     , m_isMutable(false)
     83     , m_isInMemoryCache(false)
     84     , m_parserContext(o.m_parserContext)
     85 {
     86     ASSERT(o.isCacheable());
     87 
     88     // FIXME: Copy import rules.
     89     ASSERT(o.m_importRules.isEmpty());
     90 
     91     for (unsigned i = 0; i < m_childRules.size(); ++i)
     92         m_childRules[i] = o.m_childRules[i]->copy();
     93 }
     94 
     95 StyleSheetContents::~StyleSheetContents()
     96 {
     97     clearRules();
     98 }
     99 
    100 bool StyleSheetContents::isCacheable() const
    101 {
    102     // FIXME: Support copying import rules.
    103     if (!m_importRules.isEmpty())
    104         return false;
    105     // FIXME: Support cached stylesheets in import rules.
    106     if (m_ownerRule)
    107         return false;
    108     // This would require dealing with multiple clients for load callbacks.
    109     if (!m_loadCompleted)
    110         return false;
    111     if (m_didLoadErrorOccur)
    112         return false;
    113     // It is not the original sheet anymore.
    114     if (m_isMutable)
    115         return false;
    116     // If the header is valid we are not going to need to check the SecurityOrigin.
    117     // FIXME: Valid mime type avoids the check too.
    118     if (!m_hasSyntacticallyValidCSSHeader)
    119         return false;
    120     return true;
    121 }
    122 
    123 void StyleSheetContents::parserAppendRule(PassRefPtr<StyleRuleBase> rule)
    124 {
    125     ASSERT(!rule->isCharsetRule());
    126     if (rule->isImportRule()) {
    127         // Parser enforces that @import rules come before anything else except @charset.
    128         ASSERT(m_childRules.isEmpty());
    129         m_importRules.append(static_cast<StyleRuleImport*>(rule.get()));
    130         m_importRules.last()->setParentStyleSheet(this);
    131         m_importRules.last()->requestStyleSheet();
    132         return;
    133     }
    134 
    135     // Add warning message to inspector if dpi/dpcm values are used for screen media.
    136     if (rule->isMediaRule())
    137         reportMediaQueryWarningIfNeeded(singleOwnerDocument(), static_cast<StyleRuleMedia*>(rule.get())->mediaQueries());
    138 
    139     m_childRules.append(rule);
    140 }
    141 
    142 StyleRuleBase* StyleSheetContents::ruleAt(unsigned index) const
    143 {
    144     ASSERT_WITH_SECURITY_IMPLICATION(index < ruleCount());
    145 
    146     unsigned childVectorIndex = index;
    147     if (hasCharsetRule()) {
    148         if (index == 0)
    149             return 0;
    150         --childVectorIndex;
    151     }
    152     if (childVectorIndex < m_importRules.size())
    153         return m_importRules[childVectorIndex].get();
    154 
    155     childVectorIndex -= m_importRules.size();
    156     return m_childRules[childVectorIndex].get();
    157 }
    158 
    159 unsigned StyleSheetContents::ruleCount() const
    160 {
    161     unsigned result = 0;
    162     result += hasCharsetRule() ? 1 : 0;
    163     result += m_importRules.size();
    164     result += m_childRules.size();
    165     return result;
    166 }
    167 
    168 void StyleSheetContents::clearCharsetRule()
    169 {
    170     m_encodingFromCharsetRule = String();
    171 }
    172 
    173 void StyleSheetContents::clearRules()
    174 {
    175     for (unsigned i = 0; i < m_importRules.size(); ++i) {
    176         ASSERT(m_importRules.at(i)->parentStyleSheet() == this);
    177         m_importRules[i]->clearParentStyleSheet();
    178     }
    179     m_importRules.clear();
    180     m_childRules.clear();
    181     clearCharsetRule();
    182 }
    183 
    184 void StyleSheetContents::parserSetEncodingFromCharsetRule(const String& encoding)
    185 {
    186     // Parser enforces that there is ever only one @charset.
    187     ASSERT(m_encodingFromCharsetRule.isNull());
    188     m_encodingFromCharsetRule = encoding;
    189 }
    190 
    191 bool StyleSheetContents::wrapperInsertRule(PassRefPtr<StyleRuleBase> rule, unsigned index)
    192 {
    193     ASSERT(m_isMutable);
    194     ASSERT_WITH_SECURITY_IMPLICATION(index <= ruleCount());
    195     // Parser::parseRule doesn't currently allow @charset so we don't need to deal with it.
    196     ASSERT(!rule->isCharsetRule());
    197 
    198     unsigned childVectorIndex = index;
    199     // m_childRules does not contain @charset which is always in index 0 if it exists.
    200     if (hasCharsetRule()) {
    201         if (childVectorIndex == 0) {
    202             // Nothing can be inserted before @charset.
    203             return false;
    204         }
    205         --childVectorIndex;
    206     }
    207 
    208     if (childVectorIndex < m_importRules.size() || (childVectorIndex == m_importRules.size() && rule->isImportRule())) {
    209         // Inserting non-import rule before @import is not allowed.
    210         if (!rule->isImportRule())
    211             return false;
    212         m_importRules.insert(childVectorIndex, static_cast<StyleRuleImport*>(rule.get()));
    213         m_importRules[childVectorIndex]->setParentStyleSheet(this);
    214         m_importRules[childVectorIndex]->requestStyleSheet();
    215         // FIXME: Stylesheet doesn't actually change meaningfully before the imported sheets are loaded.
    216         return true;
    217     }
    218     // Inserting @import rule after a non-import rule is not allowed.
    219     if (rule->isImportRule())
    220         return false;
    221     childVectorIndex -= m_importRules.size();
    222 
    223     m_childRules.insert(childVectorIndex, rule);
    224     return true;
    225 }
    226 
    227 void StyleSheetContents::wrapperDeleteRule(unsigned index)
    228 {
    229     ASSERT(m_isMutable);
    230     ASSERT_WITH_SECURITY_IMPLICATION(index < ruleCount());
    231 
    232     unsigned childVectorIndex = index;
    233     if (hasCharsetRule()) {
    234         if (childVectorIndex == 0) {
    235             clearCharsetRule();
    236             return;
    237         }
    238         --childVectorIndex;
    239     }
    240     if (childVectorIndex < m_importRules.size()) {
    241         m_importRules[childVectorIndex]->clearParentStyleSheet();
    242         m_importRules.remove(childVectorIndex);
    243         return;
    244     }
    245     childVectorIndex -= m_importRules.size();
    246 
    247     m_childRules.remove(childVectorIndex);
    248 }
    249 
    250 void StyleSheetContents::parserAddNamespace(const AtomicString& prefix, const AtomicString& uri)
    251 {
    252     if (uri.isNull() || prefix.isNull())
    253         return;
    254     PrefixNamespaceURIMap::AddResult result = m_namespaces.add(prefix, uri);
    255     if (result.isNewEntry)
    256         return;
    257     result.iterator->value = uri;
    258 }
    259 
    260 const AtomicString& StyleSheetContents::determineNamespace(const AtomicString& prefix)
    261 {
    262     if (prefix.isNull())
    263         return nullAtom; // No namespace. If an element/attribute has a namespace, we won't match it.
    264     if (prefix == starAtom)
    265         return starAtom; // We'll match any namespace.
    266     PrefixNamespaceURIMap::const_iterator it = m_namespaces.find(prefix);
    267     if (it == m_namespaces.end())
    268         return nullAtom;
    269     return it->value;
    270 }
    271 
    272 void StyleSheetContents::parseAuthorStyleSheet(const CSSStyleSheetResource* cachedStyleSheet, const SecurityOrigin* securityOrigin)
    273 {
    274     bool enforceMIMEType = isStrictParserMode(m_parserContext.mode);
    275     bool hasValidMIMEType = false;
    276     String sheetText = cachedStyleSheet->sheetText(enforceMIMEType, &hasValidMIMEType);
    277 
    278     CSSParser p(parserContext(), UseCounter::getFrom(this));
    279     p.parseSheet(this, sheetText, TextPosition::minimumPosition(), 0, true);
    280 
    281     // If we're loading a stylesheet cross-origin, and the MIME type is not standard, require the CSS
    282     // to at least start with a syntactically valid CSS rule.
    283     // This prevents an attacker playing games by injecting CSS strings into HTML, XML, JSON, etc. etc.
    284     if (!hasValidMIMEType && !hasSyntacticallyValidCSSHeader()) {
    285         bool isCrossOriginCSS = !securityOrigin || !securityOrigin->canRequest(baseURL());
    286         if (isCrossOriginCSS) {
    287             clearRules();
    288             return;
    289         }
    290     }
    291     if (m_parserContext.needsSiteSpecificQuirks && isStrictParserMode(m_parserContext.mode)) {
    292         // Work around <https://bugs.webkit.org/show_bug.cgi?id=28350>.
    293         DEFINE_STATIC_LOCAL(const String, mediaWikiKHTMLFixesStyleSheet, ("/* KHTML fix stylesheet */\n/* work around the horizontal scrollbars */\n#column-content { margin-left: 0; }\n\n"));
    294         // There are two variants of KHTMLFixes.css. One is equal to mediaWikiKHTMLFixesStyleSheet,
    295         // while the other lacks the second trailing newline.
    296         if (baseURL().string().endsWith("/KHTMLFixes.css") && !sheetText.isNull() && mediaWikiKHTMLFixesStyleSheet.startsWith(sheetText)
    297             && sheetText.length() >= mediaWikiKHTMLFixesStyleSheet.length() - 1)
    298             clearRules();
    299     }
    300 }
    301 
    302 bool StyleSheetContents::parseString(const String& sheetText)
    303 {
    304     return parseStringAtPosition(sheetText, TextPosition::minimumPosition(), false);
    305 }
    306 
    307 bool StyleSheetContents::parseStringAtPosition(const String& sheetText, const TextPosition& startPosition, bool createdByParser)
    308 {
    309     CSSParser p(parserContext(), UseCounter::getFrom(this));
    310     p.parseSheet(this, sheetText, startPosition, 0, createdByParser);
    311 
    312     return true;
    313 }
    314 
    315 bool StyleSheetContents::isLoading() const
    316 {
    317     for (unsigned i = 0; i < m_importRules.size(); ++i) {
    318         if (m_importRules[i]->isLoading())
    319             return true;
    320     }
    321     return false;
    322 }
    323 
    324 void StyleSheetContents::checkLoaded()
    325 {
    326     if (isLoading())
    327         return;
    328 
    329     RefPtr<StyleSheetContents> protect(this);
    330 
    331     // Avoid |this| being deleted by scripts that run via
    332     // ScriptableDocumentParser::executeScriptsWaitingForResources().
    333     // See <rdar://problem/6622300>.
    334     RefPtr<StyleSheetContents> protector(this);
    335     StyleSheetContents* parentSheet = parentStyleSheet();
    336     if (parentSheet) {
    337         parentSheet->checkLoaded();
    338         m_loadCompleted = true;
    339         return;
    340     }
    341     RefPtr<Node> ownerNode = singleOwnerNode();
    342     if (!ownerNode) {
    343         m_loadCompleted = true;
    344         return;
    345     }
    346     m_loadCompleted = ownerNode->sheetLoaded();
    347     if (m_loadCompleted)
    348         ownerNode->notifyLoadedSheetAndAllCriticalSubresources(m_didLoadErrorOccur);
    349 }
    350 
    351 void StyleSheetContents::notifyLoadedSheet(const CSSStyleSheetResource* sheet)
    352 {
    353     ASSERT(sheet);
    354     m_didLoadErrorOccur |= sheet->errorOccurred();
    355 }
    356 
    357 void StyleSheetContents::startLoadingDynamicSheet()
    358 {
    359     if (Node* owner = singleOwnerNode())
    360         owner->startLoadingDynamicSheet();
    361 }
    362 
    363 StyleSheetContents* StyleSheetContents::rootStyleSheet() const
    364 {
    365     const StyleSheetContents* root = this;
    366     while (root->parentStyleSheet())
    367         root = root->parentStyleSheet();
    368     return const_cast<StyleSheetContents*>(root);
    369 }
    370 
    371 bool StyleSheetContents::hasSingleOwnerNode() const
    372 {
    373     StyleSheetContents* root = rootStyleSheet();
    374     if (root->m_clients.isEmpty())
    375         return false;
    376     return root->m_clients.size() == 1;
    377 }
    378 
    379 Node* StyleSheetContents::singleOwnerNode() const
    380 {
    381     StyleSheetContents* root = rootStyleSheet();
    382     if (root->m_clients.isEmpty())
    383         return 0;
    384     ASSERT(root->m_clients.size() == 1);
    385     return root->m_clients[0]->ownerNode();
    386 }
    387 
    388 Document* StyleSheetContents::singleOwnerDocument() const
    389 {
    390     Node* ownerNode = singleOwnerNode();
    391     return ownerNode ? ownerNode->document() : 0;
    392 }
    393 
    394 KURL StyleSheetContents::completeURL(const String& url) const
    395 {
    396     return CSSParser::completeURL(m_parserContext, url);
    397 }
    398 
    399 void StyleSheetContents::addSubresourceStyleURLs(ListHashSet<KURL>& urls)
    400 {
    401     Deque<StyleSheetContents*> styleSheetQueue;
    402     styleSheetQueue.append(this);
    403 
    404     while (!styleSheetQueue.isEmpty()) {
    405         StyleSheetContents* styleSheet = styleSheetQueue.takeFirst();
    406 
    407         for (unsigned i = 0; i < styleSheet->m_importRules.size(); ++i) {
    408             StyleRuleImport* importRule = styleSheet->m_importRules[i].get();
    409             if (importRule->styleSheet()) {
    410                 styleSheetQueue.append(importRule->styleSheet());
    411                 addSubresourceURL(urls, importRule->styleSheet()->baseURL());
    412             }
    413         }
    414         for (unsigned i = 0; i < styleSheet->m_childRules.size(); ++i) {
    415             StyleRuleBase* rule = styleSheet->m_childRules[i].get();
    416             if (rule->isStyleRule())
    417                 static_cast<StyleRule*>(rule)->properties()->addSubresourceStyleURLs(urls, this);
    418             else if (rule->isFontFaceRule())
    419                 static_cast<StyleRuleFontFace*>(rule)->properties()->addSubresourceStyleURLs(urls, this);
    420         }
    421     }
    422 }
    423 
    424 static bool childRulesHaveFailedOrCanceledSubresources(const Vector<RefPtr<StyleRuleBase> >& rules)
    425 {
    426     for (unsigned i = 0; i < rules.size(); ++i) {
    427         const StyleRuleBase* rule = rules[i].get();
    428         switch (rule->type()) {
    429         case StyleRuleBase::Style:
    430             if (static_cast<const StyleRule*>(rule)->properties()->hasFailedOrCanceledSubresources())
    431                 return true;
    432             break;
    433         case StyleRuleBase::FontFace:
    434             if (static_cast<const StyleRuleFontFace*>(rule)->properties()->hasFailedOrCanceledSubresources())
    435                 return true;
    436             break;
    437         case StyleRuleBase::Media:
    438             if (childRulesHaveFailedOrCanceledSubresources(static_cast<const StyleRuleMedia*>(rule)->childRules()))
    439                 return true;
    440             break;
    441         case StyleRuleBase::Region:
    442             if (childRulesHaveFailedOrCanceledSubresources(static_cast<const StyleRuleRegion*>(rule)->childRules()))
    443                 return true;
    444             break;
    445         case StyleRuleBase::HostInternal:
    446             if (childRulesHaveFailedOrCanceledSubresources(static_cast<const StyleRuleHost*>(rule)->childRules()))
    447                 return true;
    448             break;
    449         case StyleRuleBase::Import:
    450             ASSERT_NOT_REACHED();
    451         case StyleRuleBase::Page:
    452         case StyleRuleBase::Keyframes:
    453         case StyleRuleBase::Unknown:
    454         case StyleRuleBase::Charset:
    455         case StyleRuleBase::Keyframe:
    456         case StyleRuleBase::Supports:
    457         case StyleRuleBase::Viewport:
    458         case StyleRuleBase::Filter:
    459             break;
    460         }
    461     }
    462     return false;
    463 }
    464 
    465 bool StyleSheetContents::hasFailedOrCanceledSubresources() const
    466 {
    467     ASSERT(isCacheable());
    468     return childRulesHaveFailedOrCanceledSubresources(m_childRules);
    469 }
    470 
    471 StyleSheetContents* StyleSheetContents::parentStyleSheet() const
    472 {
    473     return m_ownerRule ? m_ownerRule->parentStyleSheet() : 0;
    474 }
    475 
    476 void StyleSheetContents::registerClient(CSSStyleSheet* sheet)
    477 {
    478     ASSERT(!m_clients.contains(sheet));
    479     m_clients.append(sheet);
    480 }
    481 
    482 void StyleSheetContents::unregisterClient(CSSStyleSheet* sheet)
    483 {
    484     size_t position = m_clients.find(sheet);
    485     ASSERT(position != notFound);
    486     m_clients.remove(position);
    487 }
    488 
    489 void StyleSheetContents::addedToMemoryCache()
    490 {
    491     ASSERT(!m_isInMemoryCache);
    492     ASSERT(isCacheable());
    493     m_isInMemoryCache = true;
    494 }
    495 
    496 void StyleSheetContents::removedFromMemoryCache()
    497 {
    498     ASSERT(m_isInMemoryCache);
    499     ASSERT(isCacheable());
    500     m_isInMemoryCache = false;
    501 }
    502 
    503 void StyleSheetContents::shrinkToFit()
    504 {
    505     m_importRules.shrinkToFit();
    506     m_childRules.shrinkToFit();
    507 }
    508 
    509 }
    510