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