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