3e49d235832551b7e1fa7137ee20cd9c325c84f1
[WebKit-https.git] / Source / WebCore / css / StyleSheetContents.cpp
1 /*
2  * (C) 1999-2003 Lars Knoll (knoll@kde.org)
3  * Copyright (C) 2004, 2006, 2007, 2012, 2013 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 "StyleSheetContents.h"
23
24 #include "CSSImportRule.h"
25 #include "CSSParser.h"
26 #include "CSSStyleSheet.h"
27 #include "CachedCSSStyleSheet.h"
28 #include "Document.h"
29 #include "MediaList.h"
30 #include "Node.h"
31 #include "Page.h"
32 #include "PageConsoleClient.h"
33 #include "RuleSet.h"
34 #include "SecurityOrigin.h"
35 #include "StyleProperties.h"
36 #include "StyleRule.h"
37 #include "StyleRuleImport.h"
38 #include <wtf/Deque.h>
39 #include <wtf/NeverDestroyed.h>
40 #include <wtf/Ref.h>
41
42 namespace WebCore {
43
44 // Rough size estimate for the memory cache.
45 unsigned StyleSheetContents::estimatedSizeInBytes() const
46 {
47     // Note that this does not take into account size of the strings hanging from various objects. 
48     // The assumption is that nearly all of of them are atomic and would exist anyway.
49     unsigned size = sizeof(*this);
50
51     // FIXME: This ignores the children of media and region rules.
52     // Most rules are StyleRules.
53     size += ruleCount() * StyleRule::averageSizeInBytes();
54
55     for (unsigned i = 0; i < m_importRules.size(); ++i) {
56         if (StyleSheetContents* sheet = m_importRules[i]->styleSheet())
57             size += sheet->estimatedSizeInBytes();
58     }
59     return size;
60 }
61
62 StyleSheetContents::StyleSheetContents(StyleRuleImport* ownerRule, const String& originalURL, const CSSParserContext& context)
63     : m_ownerRule(ownerRule)
64     , m_originalURL(originalURL)
65     , m_defaultNamespace(starAtom)
66     , m_isUserStyleSheet(ownerRule && ownerRule->parentStyleSheet() && ownerRule->parentStyleSheet()->isUserStyleSheet())
67     , m_parserContext(context)
68 {
69 }
70
71 StyleSheetContents::StyleSheetContents(const StyleSheetContents& o)
72     : RefCounted<StyleSheetContents>()
73     , m_ownerRule(0)
74     , m_originalURL(o.m_originalURL)
75     , m_encodingFromCharsetRule(o.m_encodingFromCharsetRule)
76     , m_importRules(o.m_importRules.size())
77     , m_namespaceRules(o.m_namespaceRules.size())
78     , m_childRules(o.m_childRules.size())
79     , m_namespaces(o.m_namespaces)
80     , m_defaultNamespace(o.m_defaultNamespace)
81     , m_isUserStyleSheet(o.m_isUserStyleSheet)
82     , m_loadCompleted(true)
83     , m_hasSyntacticallyValidCSSHeader(o.m_hasSyntacticallyValidCSSHeader)
84     , m_usesStyleBasedEditability(o.m_usesStyleBasedEditability)
85     , m_parserContext(o.m_parserContext)
86 {
87     ASSERT(o.isCacheable());
88
89     // FIXME: Copy import rules.
90     ASSERT(o.m_importRules.isEmpty());
91
92     for (unsigned i = 0; i < m_childRules.size(); ++i)
93         m_childRules[i] = o.m_childRules[i]->copy();
94 }
95
96 StyleSheetContents::~StyleSheetContents()
97 {
98     clearRules();
99 }
100
101 bool StyleSheetContents::isCacheable() const
102 {
103     // FIXME: Support copying import rules.
104     if (!m_importRules.isEmpty())
105         return false;
106     // FIXME: Support cached stylesheets in import rules.
107     if (m_ownerRule)
108         return false;
109     // This would require dealing with multiple clients for load callbacks.
110     if (!m_loadCompleted)
111         return false;
112     if (m_didLoadErrorOccur)
113         return false;
114     // It is not the original sheet anymore.
115     if (m_isMutable)
116         return false;
117     // If the header is valid we are not going to need to check the SecurityOrigin.
118     // FIXME: Valid mime type avoids the check too.
119     if (!m_hasSyntacticallyValidCSSHeader)
120         return false;
121     return true;
122 }
123
124 void StyleSheetContents::parserAppendRule(Ref<StyleRuleBase>&& rule)
125 {
126     ASSERT(!rule->isCharsetRule());
127
128     if (is<StyleRuleImport>(rule)) {
129         // Parser enforces that @import rules come before anything else except @charset.
130         ASSERT(m_childRules.isEmpty());
131         m_importRules.append(downcast<StyleRuleImport>(rule.ptr()));
132         m_importRules.last()->setParentStyleSheet(this);
133         m_importRules.last()->requestStyleSheet();
134         return;
135     }
136
137     if (is<StyleRuleNamespace>(rule)) {
138         // Parser enforces that @namespace rules come before all rules other than
139         // import/charset rules
140         ASSERT(m_childRules.isEmpty());
141         StyleRuleNamespace& namespaceRule = downcast<StyleRuleNamespace>(rule.get());
142         parserAddNamespace(namespaceRule.prefix(), namespaceRule.uri());
143         m_namespaceRules.append(downcast<StyleRuleNamespace>(rule.ptr()));
144         return;
145     }
146
147     if (is<StyleRuleMedia>(rule))
148         reportMediaQueryWarningIfNeeded(singleOwnerDocument(), downcast<StyleRuleMedia>(rule.get()).mediaQueries());
149
150     // NOTE: The selector list has to fit into RuleData. <http://webkit.org/b/118369>
151     // If we're adding a rule with a huge number of selectors, split it up into multiple rules
152     if (is<StyleRule>(rule) && downcast<StyleRule>(rule.get()).selectorList().componentCount() > RuleData::maximumSelectorComponentCount) {
153         m_childRules.appendVector(downcast<StyleRule>(rule.get()).splitIntoMultipleRulesWithMaximumSelectorComponentCount(RuleData::maximumSelectorComponentCount));
154         return;
155     }
156
157     m_childRules.append(WTFMove(rule));
158 }
159
160 StyleRuleBase* StyleSheetContents::ruleAt(unsigned index) const
161 {
162     ASSERT_WITH_SECURITY_IMPLICATION(index < ruleCount());
163     
164     unsigned childVectorIndex = index;
165     if (childVectorIndex < m_importRules.size())
166         return m_importRules[childVectorIndex].get();
167
168     childVectorIndex -= m_importRules.size();
169     
170     if (childVectorIndex < m_namespaceRules.size())
171         return m_namespaceRules[childVectorIndex].get();
172     
173     childVectorIndex -= m_namespaceRules.size();
174     
175     return m_childRules[childVectorIndex].get();
176 }
177
178 unsigned StyleSheetContents::ruleCount() const
179 {
180     unsigned result = 0;
181     result += m_importRules.size();
182     result += m_namespaceRules.size();
183     result += m_childRules.size();
184     return result;
185 }
186
187 void StyleSheetContents::clearCharsetRule()
188 {
189     m_encodingFromCharsetRule = String();
190 }
191
192 void StyleSheetContents::clearRules()
193 {
194     for (unsigned i = 0; i < m_importRules.size(); ++i) {
195         ASSERT(m_importRules.at(i)->parentStyleSheet() == this);
196         m_importRules[i]->clearParentStyleSheet();
197     }
198     m_importRules.clear();
199     m_namespaceRules.clear();
200     m_childRules.clear();
201     clearCharsetRule();
202 }
203
204 void StyleSheetContents::parserSetEncodingFromCharsetRule(const String& encoding)
205 {
206     // Parser enforces that there is ever only one @charset.
207     ASSERT(m_encodingFromCharsetRule.isNull());
208     m_encodingFromCharsetRule = encoding; 
209 }
210
211 bool StyleSheetContents::wrapperInsertRule(Ref<StyleRuleBase>&& rule, unsigned index)
212 {
213     ASSERT(m_isMutable);
214     ASSERT_WITH_SECURITY_IMPLICATION(index <= ruleCount());
215     // Parser::parseRule doesn't currently allow @charset so we don't need to deal with it.
216     ASSERT(!rule->isCharsetRule());
217     
218     unsigned childVectorIndex = index;
219     if (childVectorIndex < m_importRules.size() || (childVectorIndex == m_importRules.size() && rule->isImportRule())) {
220         // Inserting non-import rule before @import is not allowed.
221         if (!is<StyleRuleImport>(rule))
222             return false;
223         m_importRules.insert(childVectorIndex, downcast<StyleRuleImport>(rule.ptr()));
224         m_importRules[childVectorIndex]->setParentStyleSheet(this);
225         m_importRules[childVectorIndex]->requestStyleSheet();
226         // FIXME: Stylesheet doesn't actually change meaningfully before the imported sheets are loaded.
227         return true;
228     }
229     // Inserting @import rule after a non-import rule is not allowed.
230     if (is<StyleRuleImport>(rule))
231         return false;
232     childVectorIndex -= m_importRules.size();
233
234     
235     if (childVectorIndex < m_namespaceRules.size() || (childVectorIndex == m_namespaceRules.size() && rule->isNamespaceRule())) {
236         // Inserting non-namespace rules other than import rule before @namespace is
237         // not allowed.
238         if (!is<StyleRuleNamespace>(rule))
239             return false;
240         // Inserting @namespace rule when rules other than import/namespace/charset
241         // are present is not allowed.
242         if (!m_childRules.isEmpty())
243             return false;
244         
245         StyleRuleNamespace& namespaceRule = downcast<StyleRuleNamespace>(rule.get());
246         m_namespaceRules.insert(index, downcast<StyleRuleNamespace>(rule.ptr()));
247         
248         // For now to be compatible with IE and Firefox if a namespace rule with the same
249         // prefix is added, it overwrites previous ones.
250         // FIXME: The eventual correct behavior would be to ensure that the last value in
251         // the list wins.
252         parserAddNamespace(namespaceRule.prefix(), namespaceRule.uri());
253         return true;
254     }
255     if (is<StyleRuleNamespace>(rule))
256         return false;
257     childVectorIndex -= m_namespaceRules.size();
258
259     // If the number of selectors would overflow RuleData, we drop the operation.
260     if (is<StyleRule>(rule) && downcast<StyleRule>(rule.get()).selectorList().componentCount() > RuleData::maximumSelectorComponentCount)
261         return false;
262
263     m_childRules.insert(childVectorIndex, WTFMove(rule));
264     return true;
265 }
266
267 void StyleSheetContents::wrapperDeleteRule(unsigned index)
268 {
269     ASSERT(m_isMutable);
270     ASSERT_WITH_SECURITY_IMPLICATION(index < ruleCount());
271
272     unsigned childVectorIndex = index;
273     if (childVectorIndex < m_importRules.size()) {
274         m_importRules[childVectorIndex]->clearParentStyleSheet();
275         m_importRules.remove(childVectorIndex);
276         return;
277     }
278     childVectorIndex -= m_importRules.size();
279
280     if (childVectorIndex < m_namespaceRules.size()) {
281         if (!m_childRules.isEmpty())
282             return;
283         m_namespaceRules.remove(childVectorIndex);
284         return;
285     }
286     childVectorIndex -= m_namespaceRules.size();
287
288     m_childRules.remove(childVectorIndex);
289 }
290
291 void StyleSheetContents::parserAddNamespace(const AtomicString& prefix, const AtomicString& uri)
292 {
293     ASSERT(!uri.isNull());
294     if (prefix.isNull()) {
295         m_defaultNamespace = uri;
296         return;
297     }
298     PrefixNamespaceURIMap::AddResult result = m_namespaces.add(prefix, uri);
299     if (result.isNewEntry)
300         return;
301     result.iterator->value = uri;
302 }
303
304 const AtomicString& StyleSheetContents::namespaceURIFromPrefix(const AtomicString& prefix)
305 {
306     PrefixNamespaceURIMap::const_iterator it = m_namespaces.find(prefix);
307     if (it == m_namespaces.end())
308         return nullAtom;
309     return it->value;
310 }
311
312 void StyleSheetContents::parseAuthorStyleSheet(const CachedCSSStyleSheet* cachedStyleSheet, const SecurityOrigin* securityOrigin)
313 {
314     bool isSameOriginRequest = securityOrigin && securityOrigin->canRequest(baseURL());
315     CachedCSSStyleSheet::MIMETypeCheckHint mimeTypeCheckHint = isStrictParserMode(m_parserContext.mode) || !isSameOriginRequest ? CachedCSSStyleSheet::MIMETypeCheckHint::Strict : CachedCSSStyleSheet::MIMETypeCheckHint::Lax;
316     bool hasValidMIMEType = true;
317     String sheetText = cachedStyleSheet->sheetText(mimeTypeCheckHint, &hasValidMIMEType);
318
319     if (!hasValidMIMEType) {
320         ASSERT(sheetText.isNull());
321         if (auto* document = singleOwnerDocument()) {
322             if (auto* page = document->page()) {
323                 if (isStrictParserMode(m_parserContext.mode))
324                     page->console().addMessage(MessageSource::Security, MessageLevel::Error, makeString("Did not parse stylesheet at '", cachedStyleSheet->url().stringCenterEllipsizedToLength(), "' because non CSS MIME types are not allowed in strict mode."));
325 #if ENABLE(NOSNIFF)
326                 else if (!cachedStyleSheet->mimeTypeAllowedByNosniff())
327                     page->console().addMessage(MessageSource::Security, MessageLevel::Error, makeString("Did not parse stylesheet at '", cachedStyleSheet->url().stringCenterEllipsizedToLength(), "' because non CSS MIME types are not allowed when 'X-Content-Type: nosniff' is given."));
328 #endif
329                 else
330                     page->console().addMessage(MessageSource::Security, MessageLevel::Error, makeString("Did not parse stylesheet at '", cachedStyleSheet->url().stringCenterEllipsizedToLength(), "' because non CSS MIME types are not allowed for cross-origin stylesheets."));
331             }
332         }
333         return;
334     }
335
336     CSSParser p(parserContext());
337     p.parseSheet(this, sheetText, CSSParser::RuleParsing::Deferred);
338
339     if (m_parserContext.needsSiteSpecificQuirks && isStrictParserMode(m_parserContext.mode)) {
340         // Work around <https://bugs.webkit.org/show_bug.cgi?id=28350>.
341         static NeverDestroyed<const String> mediaWikiKHTMLFixesStyleSheet(ASCIILiteral("/* KHTML fix stylesheet */\n/* work around the horizontal scrollbars */\n#column-content { margin-left: 0; }\n\n"));
342         // There are two variants of KHTMLFixes.css. One is equal to mediaWikiKHTMLFixesStyleSheet,
343         // while the other lacks the second trailing newline.
344         if (baseURL().string().endsWith("/KHTMLFixes.css") && !sheetText.isNull() && mediaWikiKHTMLFixesStyleSheet.get().startsWith(sheetText)
345             && sheetText.length() >= mediaWikiKHTMLFixesStyleSheet.get().length() - 1)
346             clearRules();
347     }
348 }
349
350 bool StyleSheetContents::parseString(const String& sheetText)
351 {
352     CSSParser p(parserContext());
353     p.parseSheet(this, sheetText, parserContext().mode != UASheetMode ? CSSParser::RuleParsing::Deferred : CSSParser::RuleParsing::Normal);
354     return true;
355 }
356
357 bool StyleSheetContents::isLoading() const
358 {
359     for (unsigned i = 0; i < m_importRules.size(); ++i) {
360         if (m_importRules[i]->isLoading())
361             return true;
362     }
363     return false;
364 }
365
366 void StyleSheetContents::checkLoaded()
367 {
368     if (isLoading())
369         return;
370
371     Ref<StyleSheetContents> protectedThis(*this);
372     StyleSheetContents* parentSheet = parentStyleSheet();
373     if (parentSheet) {
374         parentSheet->checkLoaded();
375         m_loadCompleted = true;
376         return;
377     }
378     RefPtr<Node> ownerNode = singleOwnerNode();
379     if (!ownerNode) {
380         m_loadCompleted = true;
381         return;
382     }
383     m_loadCompleted = ownerNode->sheetLoaded();
384     if (m_loadCompleted)
385         ownerNode->notifyLoadedSheetAndAllCriticalSubresources(m_didLoadErrorOccur);
386 }
387
388 void StyleSheetContents::notifyLoadedSheet(const CachedCSSStyleSheet* sheet)
389 {
390     ASSERT(sheet);
391     m_didLoadErrorOccur |= sheet->errorOccurred();
392 #if ENABLE(NOSNIFF)
393     m_didLoadErrorOccur |= !sheet->mimeTypeAllowedByNosniff();
394 #endif
395 }
396
397 void StyleSheetContents::startLoadingDynamicSheet()
398 {
399     if (Node* owner = singleOwnerNode())
400         owner->startLoadingDynamicSheet();
401 }
402
403 StyleSheetContents* StyleSheetContents::rootStyleSheet() const
404 {
405     const StyleSheetContents* root = this;
406     while (root->parentStyleSheet())
407         root = root->parentStyleSheet();
408     return const_cast<StyleSheetContents*>(root);
409 }
410
411 Node* StyleSheetContents::singleOwnerNode() const
412 {
413     StyleSheetContents* root = rootStyleSheet();
414     if (root->m_clients.isEmpty())
415         return 0;
416     ASSERT(root->m_clients.size() == 1);
417     return root->m_clients[0]->ownerNode();
418 }
419
420 Document* StyleSheetContents::singleOwnerDocument() const
421 {
422     Node* ownerNode = singleOwnerNode();
423     return ownerNode ? &ownerNode->document() : 0;
424 }
425
426 URL StyleSheetContents::completeURL(const String& url) const
427 {
428     return m_parserContext.completeURL(url);
429 }
430
431 static bool traverseSubresourcesInRules(const Vector<RefPtr<StyleRuleBase>>& rules, const std::function<bool (const CachedResource&)>& handler)
432 {
433     for (auto& rule : rules) {
434         switch (rule->type()) {
435         case StyleRuleBase::Style: {
436             auto* properties = downcast<StyleRule>(*rule).propertiesWithoutDeferredParsing();
437             if (properties && properties->traverseSubresources(handler))
438                 return true;
439             break;
440         }
441         case StyleRuleBase::FontFace:
442             if (downcast<StyleRuleFontFace>(*rule).properties().traverseSubresources(handler))
443                 return true;
444             break;
445         case StyleRuleBase::Media: {
446             auto* childRules = downcast<StyleRuleMedia>(*rule).childRulesWithoutDeferredParsing();
447             if (childRules && traverseSubresourcesInRules(*childRules, handler))
448                 return true;
449             break;
450         }
451         case StyleRuleBase::Region:
452             if (traverseSubresourcesInRules(downcast<StyleRuleRegion>(*rule).childRules(), handler))
453                 return true;
454             break;
455         case StyleRuleBase::Import:
456             ASSERT_NOT_REACHED();
457 #if ASSERT_DISABLED
458             FALLTHROUGH;
459 #endif
460         case StyleRuleBase::Page:
461         case StyleRuleBase::Keyframes:
462         case StyleRuleBase::Namespace:
463         case StyleRuleBase::Unknown:
464         case StyleRuleBase::Charset:
465         case StyleRuleBase::Keyframe:
466         case StyleRuleBase::Supports:
467 #if ENABLE(CSS_DEVICE_ADAPTATION)
468         case StyleRuleBase::Viewport:
469 #endif
470             break;
471         }
472     }
473     return false;
474 }
475
476 bool StyleSheetContents::traverseSubresources(const std::function<bool (const CachedResource&)>& handler) const
477 {
478     for (auto& importRule : m_importRules) {
479         if (auto* cachedResource = importRule->cachedCSSStyleSheet()) {
480             if (handler(*cachedResource))
481                 return true;
482         }
483         auto* importedStyleSheet = importRule->styleSheet();
484         if (importedStyleSheet && importedStyleSheet->traverseSubresources(handler))
485             return true;
486     }
487     return traverseSubresourcesInRules(m_childRules, handler);
488 }
489
490 bool StyleSheetContents::subresourcesAllowReuse(CachePolicy cachePolicy) const
491 {
492     bool hasFailedOrExpiredResources = traverseSubresources([cachePolicy](const CachedResource& resource) {
493         if (resource.loadFailedOrCanceled())
494             return true;
495         // We can't revalidate subresources individually so don't use reuse the parsed sheet if they need revalidation.
496         if (resource.makeRevalidationDecision(cachePolicy) != CachedResource::RevalidationDecision::No)
497             return true;
498         return false;
499     });
500     return !hasFailedOrExpiredResources;
501 }
502
503 bool StyleSheetContents::isLoadingSubresources() const
504 {
505     return traverseSubresources([](const CachedResource& resource) {
506         return resource.isLoading();
507     });
508 }
509
510 StyleSheetContents* StyleSheetContents::parentStyleSheet() const
511 {
512     return m_ownerRule ? m_ownerRule->parentStyleSheet() : 0;
513 }
514
515 void StyleSheetContents::registerClient(CSSStyleSheet* sheet)
516 {
517     ASSERT(!m_clients.contains(sheet));
518     m_clients.append(sheet);
519 }
520
521 void StyleSheetContents::unregisterClient(CSSStyleSheet* sheet)
522 {
523     bool removed = m_clients.removeFirst(sheet);
524     ASSERT_UNUSED(removed, removed);
525 }
526
527 void StyleSheetContents::addedToMemoryCache()
528 {
529     ASSERT(isCacheable());
530     ++m_inMemoryCacheCount;
531 }
532
533 void StyleSheetContents::removedFromMemoryCache()
534 {
535     ASSERT(m_inMemoryCacheCount);
536     ASSERT(isCacheable());
537     --m_inMemoryCacheCount;
538 }
539
540 void StyleSheetContents::shrinkToFit()
541 {
542     m_importRules.shrinkToFit();
543     m_childRules.shrinkToFit();
544 }
545
546 }