1/*
2 * Copyright (C) 2006, 2007 Rob Buis
3 * Copyright (C) 2008-2016 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 "InlineStyleSheetOwner.h"
23
24#include "ContentSecurityPolicy.h"
25#include "Element.h"
26#include "Logging.h"
27#include "MediaList.h"
28#include "MediaQueryEvaluator.h"
29#include "MediaQueryParser.h"
30#include "ScriptableDocumentParser.h"
31#include "ShadowRoot.h"
32#include "StyleScope.h"
33#include "StyleSheetContents.h"
34#include "TextNodeTraversal.h"
35#include <wtf/HashMap.h>
36#include <wtf/NeverDestroyed.h>
37
38namespace WebCore {
39
40using InlineStyleSheetCacheKey = std::pair<String, CSSParserContext>;
41using InlineStyleSheetCache = HashMap<InlineStyleSheetCacheKey, RefPtr<StyleSheetContents>>;
42
43static InlineStyleSheetCache& inlineStyleSheetCache()
44{
45 static NeverDestroyed<InlineStyleSheetCache> cache;
46 return cache;
47}
48
49static CSSParserContext parserContextForElement(const Element& element)
50{
51 auto* shadowRoot = element.containingShadowRoot();
52 // User agent shadow trees can't contain document-relative URLs. Use blank URL as base allowing cross-document sharing.
53 auto& baseURL = shadowRoot && shadowRoot->mode() == ShadowRootMode::UserAgent ? WTF::blankURL() : element.document().baseURL();
54
55 CSSParserContext result = CSSParserContext { element.document(), baseURL, element.document().characterSetWithUTF8Fallback() };
56 if (shadowRoot && shadowRoot->mode() == ShadowRootMode::UserAgent)
57 result.mode = UASheetMode;
58 return result;
59}
60
61static Optional<InlineStyleSheetCacheKey> makeInlineStyleSheetCacheKey(const String& text, const Element& element)
62{
63 // Only cache for shadow trees. Main document inline stylesheets are generally unique and can't be shared between documents.
64 // FIXME: This could be relaxed when a stylesheet does not contain document-relative URLs (or #urls).
65 if (!element.isInShadowTree())
66 return { };
67
68 return std::make_pair(text, parserContextForElement(element));
69}
70
71InlineStyleSheetOwner::InlineStyleSheetOwner(Document& document, bool createdByParser)
72 : m_isParsingChildren(createdByParser)
73 , m_loading(false)
74 , m_startTextPosition()
75{
76 if (createdByParser && document.scriptableDocumentParser() && !document.isInDocumentWrite())
77 m_startTextPosition = document.scriptableDocumentParser()->textPosition();
78}
79
80InlineStyleSheetOwner::~InlineStyleSheetOwner()
81{
82 if (m_sheet)
83 clearSheet();
84}
85
86void InlineStyleSheetOwner::insertedIntoDocument(Element& element)
87{
88 m_styleScope = &Style::Scope::forNode(element);
89 m_styleScope->addStyleSheetCandidateNode(element, m_isParsingChildren);
90
91 if (m_isParsingChildren)
92 return;
93 createSheetFromTextContents(element);
94}
95
96void InlineStyleSheetOwner::removedFromDocument(Element& element)
97{
98 if (m_styleScope) {
99 m_styleScope->removeStyleSheetCandidateNode(element);
100 m_styleScope = nullptr;
101 }
102 if (m_sheet)
103 clearSheet();
104}
105
106void InlineStyleSheetOwner::clearDocumentData(Element& element)
107{
108 if (m_sheet)
109 m_sheet->clearOwnerNode();
110
111 if (m_styleScope) {
112 m_styleScope->removeStyleSheetCandidateNode(element);
113 m_styleScope = nullptr;
114 }
115}
116
117void InlineStyleSheetOwner::childrenChanged(Element& element)
118{
119 if (m_isParsingChildren)
120 return;
121 if (!element.isConnected())
122 return;
123 createSheetFromTextContents(element);
124}
125
126void InlineStyleSheetOwner::finishParsingChildren(Element& element)
127{
128 if (element.isConnected())
129 createSheetFromTextContents(element);
130 m_isParsingChildren = false;
131}
132
133void InlineStyleSheetOwner::createSheetFromTextContents(Element& element)
134{
135 createSheet(element, TextNodeTraversal::contentsAsString(element));
136}
137
138void InlineStyleSheetOwner::clearSheet()
139{
140 ASSERT(m_sheet);
141 auto sheet = WTFMove(m_sheet);
142 sheet->clearOwnerNode();
143}
144
145inline bool isValidCSSContentType(Element& element, const AtomString& type)
146{
147 if (type.isEmpty())
148 return true;
149 // FIXME: Should MIME types really be case sensitive in XML documents? Doesn't seem like they should,
150 // even though other things are case sensitive in that context. MIME types should never be case sensitive.
151 // We should verify this and then remove the isHTMLElement check here.
152 static NeverDestroyed<const AtomString> cssContentType("text/css", AtomString::ConstructFromLiteral);
153 return element.isHTMLElement() ? equalLettersIgnoringASCIICase(type, "text/css") : type == cssContentType;
154}
155
156void InlineStyleSheetOwner::createSheet(Element& element, const String& text)
157{
158 ASSERT(element.isConnected());
159 Document& document = element.document();
160 if (m_sheet) {
161 if (m_sheet->isLoading() && m_styleScope)
162 m_styleScope->removePendingSheet(element);
163 clearSheet();
164 }
165
166 if (!isValidCSSContentType(element, m_contentType))
167 return;
168
169 ASSERT(document.contentSecurityPolicy());
170 const ContentSecurityPolicy& contentSecurityPolicy = *document.contentSecurityPolicy();
171 bool hasKnownNonce = contentSecurityPolicy.allowStyleWithNonce(element.attributeWithoutSynchronization(HTMLNames::nonceAttr), element.isInUserAgentShadowTree());
172 if (!contentSecurityPolicy.allowInlineStyle(document.url(), m_startTextPosition.m_line, text, hasKnownNonce))
173 return;
174
175 auto mediaQueries = MediaQuerySet::create(m_media, MediaQueryParserContext(document));
176
177 MediaQueryEvaluator screenEval("screen"_s, true);
178 MediaQueryEvaluator printEval("print"_s, true);
179 LOG(MediaQueries, "InlineStyleSheetOwner::createSheet evaluating queries");
180 if (!screenEval.evaluate(mediaQueries.get()) && !printEval.evaluate(mediaQueries.get()))
181 return;
182
183 if (m_styleScope)
184 m_styleScope->addPendingSheet(element);
185
186 auto cacheKey = makeInlineStyleSheetCacheKey(text, element);
187 if (cacheKey) {
188 if (auto* cachedSheet = inlineStyleSheetCache().get(*cacheKey)) {
189 ASSERT(cachedSheet->isCacheable());
190 m_sheet = CSSStyleSheet::createInline(*cachedSheet, element, m_startTextPosition);
191 m_sheet->setMediaQueries(WTFMove(mediaQueries));
192 if (!element.isInShadowTree())
193 m_sheet->setTitle(element.title());
194
195 sheetLoaded(element);
196 element.notifyLoadedSheetAndAllCriticalSubresources(false);
197 return;
198 }
199 }
200
201 m_loading = true;
202
203 auto contents = StyleSheetContents::create(String(), parserContextForElement(element));
204
205 m_sheet = CSSStyleSheet::createInline(contents.get(), element, m_startTextPosition);
206 m_sheet->setMediaQueries(WTFMove(mediaQueries));
207 if (!element.isInShadowTree())
208 m_sheet->setTitle(element.title());
209
210 contents->parseString(text);
211
212 m_loading = false;
213
214 contents->checkLoaded();
215
216 if (cacheKey && contents->isCacheable()) {
217 m_sheet->contents().addedToMemoryCache();
218 inlineStyleSheetCache().add(*cacheKey, &m_sheet->contents());
219
220 // Prevent pathological growth.
221 const size_t maximumInlineStyleSheetCacheSize = 50;
222 if (inlineStyleSheetCache().size() > maximumInlineStyleSheetCacheSize) {
223 auto toRemove = inlineStyleSheetCache().random();
224 toRemove->value->removedFromMemoryCache();
225 inlineStyleSheetCache().remove(toRemove);
226 }
227 }
228}
229
230bool InlineStyleSheetOwner::isLoading() const
231{
232 if (m_loading)
233 return true;
234 return m_sheet && m_sheet->isLoading();
235}
236
237bool InlineStyleSheetOwner::sheetLoaded(Element& element)
238{
239 if (isLoading())
240 return false;
241
242 if (m_styleScope)
243 m_styleScope->removePendingSheet(element);
244
245 return true;
246}
247
248void InlineStyleSheetOwner::startLoadingDynamicSheet(Element& element)
249{
250 if (m_styleScope)
251 m_styleScope->addPendingSheet(element);
252}
253
254void InlineStyleSheetOwner::clearCache()
255{
256 inlineStyleSheetCache().clear();
257}
258
259}
260