• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * (C) 1999-2003 Lars Knoll (knoll@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.
estimatedSizeInBytes() const41 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 
StyleSheetContents(StyleRuleImport * ownerRule,const String & originalURL,const CSSParserContext & context)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 
StyleSheetContents(const StyleSheetContents & o)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 
~StyleSheetContents()98 StyleSheetContents::~StyleSheetContents()
99 {
100     clearRules();
101 }
102 
isCacheable() const103 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 
parserAppendRule(PassRefPtr<StyleRuleBase> rule)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 
setHasMediaQueries()157 void StyleSheetContents::setHasMediaQueries()
158 {
159     m_hasMediaQueries = true;
160     if (parentStyleSheet())
161         parentStyleSheet()->setHasMediaQueries();
162 }
163 
ruleAt(unsigned index) const164 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 
ruleCount() const181 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 
clearCharsetRule()190 void StyleSheetContents::clearCharsetRule()
191 {
192     m_encodingFromCharsetRule = String();
193 }
194 
clearRules()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 
parserSetEncodingFromCharsetRule(const String & encoding)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 
wrapperInsertRule(PassRefPtr<StyleRuleBase> rule,unsigned index)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 
wrapperDeleteRule(unsigned index)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 
parserAddNamespace(const AtomicString & prefix,const AtomicString & uri)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 
determineNamespace(const AtomicString & prefix)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 
parseAuthorStyleSheet(const CSSStyleSheetResource * cachedStyleSheet,const SecurityOrigin * securityOrigin)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 
parseString(const String & sheetText)325 bool StyleSheetContents::parseString(const String& sheetText)
326 {
327     return parseStringAtPosition(sheetText, TextPosition::minimumPosition(), false);
328 }
329 
parseStringAtPosition(const String & sheetText,const TextPosition & startPosition,bool createdByParser)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 
isLoading() const338 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 
checkLoaded()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 
notifyLoadedSheet(const CSSStyleSheetResource * sheet)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 
startLoadingDynamicSheet()383 void StyleSheetContents::startLoadingDynamicSheet()
384 {
385     if (Node* owner = singleOwnerNode())
386         owner->startLoadingDynamicSheet();
387 }
388 
rootStyleSheet() const389 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 
hasSingleOwnerNode() const397 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 
singleOwnerNode() const405 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 
singleOwnerDocument() const414 Document* StyleSheetContents::singleOwnerDocument() const
415 {
416     Node* ownerNode = singleOwnerNode();
417     return ownerNode ? &ownerNode->document() : 0;
418 }
419 
completeURL(const String & url) const420 KURL StyleSheetContents::completeURL(const String& url) const
421 {
422     return CSSParser::completeURL(m_parserContext, url);
423 }
424 
addSubresourceStyleURLs(ListHashSet<KURL> & urls)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 
childRulesHaveFailedOrCanceledSubresources(const Vector<RefPtr<StyleRuleBase>> & rules)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 
hasFailedOrCanceledSubresources() const487 bool StyleSheetContents::hasFailedOrCanceledSubresources() const
488 {
489     ASSERT(isCacheable());
490     return childRulesHaveFailedOrCanceledSubresources(m_childRules);
491 }
492 
parentStyleSheet() const493 StyleSheetContents* StyleSheetContents::parentStyleSheet() const
494 {
495     return m_ownerRule ? m_ownerRule->parentStyleSheet() : 0;
496 }
497 
registerClient(CSSStyleSheet * sheet)498 void StyleSheetContents::registerClient(CSSStyleSheet* sheet)
499 {
500     ASSERT(!m_clients.contains(sheet));
501     m_clients.append(sheet);
502 }
503 
unregisterClient(CSSStyleSheet * sheet)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 
addedToMemoryCache()511 void StyleSheetContents::addedToMemoryCache()
512 {
513     ASSERT(!m_isInMemoryCache);
514     ASSERT(isCacheable());
515     m_isInMemoryCache = true;
516 }
517 
removedFromMemoryCache()518 void StyleSheetContents::removedFromMemoryCache()
519 {
520     ASSERT(m_isInMemoryCache);
521     ASSERT(isCacheable());
522     m_isInMemoryCache = false;
523 }
524 
shrinkToFit()525 void StyleSheetContents::shrinkToFit()
526 {
527     m_importRules.shrinkToFit();
528     m_childRules.shrinkToFit();
529 }
530 
ensureRuleSet(const MediaQueryEvaluator & medium,AddRuleFlags addRuleFlags)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 
clearRuleSet()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