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