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