1/*
2 Copyright (C) 1998 Lars Knoll (knoll@mpi-hd.mpg.de)
3 Copyright (C) 2001 Dirk Mueller (mueller@kde.org)
4 Copyright (C) 2002 Waldo Bastian (bastian@kde.org)
5 Copyright (C) 2006 Samuel Weinig (sam.weinig@gmail.com)
6 Copyright (C) 2004-2017 Apple Inc. All rights reserved.
7
8 This library is free software; you can redistribute it and/or
9 modify it under the terms of the GNU Library General Public
10 License as published by the Free Software Foundation; either
11 version 2 of the License, or (at your option) any later version.
12
13 This library is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 Library General Public License for more details.
17
18 You should have received a copy of the GNU Library General Public License
19 along with this library; see the file COPYING.LIB. If not, write to
20 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
21 Boston, MA 02110-1301, USA.
22*/
23
24#include "config.h"
25#include "CachedCSSStyleSheet.h"
26
27#include "CSSStyleSheet.h"
28#include "CachedResourceClientWalker.h"
29#include "CachedResourceRequest.h"
30#include "CachedStyleSheetClient.h"
31#include "HTTPHeaderNames.h"
32#include "HTTPParsers.h"
33#include "MemoryCache.h"
34#include "SharedBuffer.h"
35#include "StyleSheetContents.h"
36#include "TextResourceDecoder.h"
37
38namespace WebCore {
39
40CachedCSSStyleSheet::CachedCSSStyleSheet(CachedResourceRequest&& request, const PAL::SessionID& sessionID, const CookieJar* cookieJar)
41 : CachedResource(WTFMove(request), Type::CSSStyleSheet, sessionID, cookieJar)
42 , m_decoder(TextResourceDecoder::create("text/css", request.charset()))
43{
44}
45
46CachedCSSStyleSheet::~CachedCSSStyleSheet()
47{
48 if (m_parsedStyleSheetCache)
49 m_parsedStyleSheetCache->removedFromMemoryCache();
50}
51
52void CachedCSSStyleSheet::didAddClient(CachedResourceClient& client)
53{
54 ASSERT(client.resourceClientType() == CachedStyleSheetClient::expectedType());
55 // CachedResource::didAddClient() must be before setCSSStyleSheet(),
56 // because setCSSStyleSheet() may cause scripts to be executed, which could destroy 'c' if it is an instance of HTMLLinkElement.
57 // see the comment of HTMLLinkElement::setCSSStyleSheet.
58 CachedResource::didAddClient(client);
59
60 if (!isLoading())
61 static_cast<CachedStyleSheetClient&>(client).setCSSStyleSheet(m_resourceRequest.url(), m_response.url(), m_decoder->encoding().name(), this);
62}
63
64void CachedCSSStyleSheet::setEncoding(const String& chs)
65{
66 m_decoder->setEncoding(chs, TextResourceDecoder::EncodingFromHTTPHeader);
67}
68
69String CachedCSSStyleSheet::encoding() const
70{
71 return m_decoder->encoding().name();
72}
73
74const String CachedCSSStyleSheet::sheetText(MIMETypeCheckHint mimeTypeCheckHint, bool* hasValidMIMEType) const
75{
76 if (!m_data || m_data->isEmpty() || !canUseSheet(mimeTypeCheckHint, hasValidMIMEType))
77 return String();
78
79 if (!m_decodedSheetText.isNull())
80 return m_decodedSheetText;
81
82 // Don't cache the decoded text, regenerating is cheap and it can use quite a bit of memory
83 return m_decoder->decodeAndFlush(m_data->data(), m_data->size());
84}
85
86void CachedCSSStyleSheet::setBodyDataFrom(const CachedResource& resource)
87{
88 ASSERT(resource.type() == type());
89 const CachedCSSStyleSheet& sheet = static_cast<const CachedCSSStyleSheet&>(resource);
90
91 CachedResource::setBodyDataFrom(resource);
92
93 m_decoder = sheet.m_decoder;
94 m_decodedSheetText = sheet.m_decodedSheetText;
95 if (sheet.m_parsedStyleSheetCache)
96 saveParsedStyleSheet(*sheet.m_parsedStyleSheetCache);
97}
98
99void CachedCSSStyleSheet::finishLoading(SharedBuffer* data)
100{
101 m_data = data;
102 setEncodedSize(data ? data->size() : 0);
103 // Decode the data to find out the encoding and keep the sheet text around during checkNotify()
104 if (data)
105 m_decodedSheetText = m_decoder->decodeAndFlush(data->data(), data->size());
106 setLoading(false);
107 checkNotify();
108 // Clear the decoded text as it is unlikely to be needed immediately again and is cheap to regenerate.
109 m_decodedSheetText = String();
110}
111
112void CachedCSSStyleSheet::checkNotify()
113{
114 if (isLoading())
115 return;
116
117 CachedResourceClientWalker<CachedStyleSheetClient> w(m_clients);
118 while (CachedStyleSheetClient* c = w.next())
119 c->setCSSStyleSheet(m_resourceRequest.url(), m_response.url(), m_decoder->encoding().name(), this);
120}
121
122String CachedCSSStyleSheet::responseMIMEType() const
123{
124 return extractMIMETypeFromMediaType(m_response.httpHeaderField(HTTPHeaderName::ContentType));
125}
126
127bool CachedCSSStyleSheet::mimeTypeAllowedByNosniff() const
128{
129 return parseContentTypeOptionsHeader(m_response.httpHeaderField(HTTPHeaderName::XContentTypeOptions)) != ContentTypeOptionsNosniff || equalLettersIgnoringASCIICase(responseMIMEType(), "text/css");
130}
131
132bool CachedCSSStyleSheet::canUseSheet(MIMETypeCheckHint mimeTypeCheckHint, bool* hasValidMIMEType) const
133{
134 if (errorOccurred())
135 return false;
136
137 if (!mimeTypeAllowedByNosniff()) {
138 if (hasValidMIMEType)
139 *hasValidMIMEType = false;
140 return false;
141 }
142
143 if (mimeTypeCheckHint == MIMETypeCheckHint::Lax)
144 return true;
145
146 // This check exactly matches Firefox. Note that we grab the Content-Type
147 // header directly because we want to see what the value is BEFORE content
148 // sniffing. Firefox does this by setting a "type hint" on the channel.
149 // This implementation should be observationally equivalent.
150 //
151 // This code defaults to allowing the stylesheet for non-HTTP protocols so
152 // folks can use standards mode for local HTML documents.
153 String mimeType = responseMIMEType();
154 bool typeOK = mimeType.isEmpty() || equalLettersIgnoringASCIICase(mimeType, "text/css") || equalLettersIgnoringASCIICase(mimeType, "application/x-unknown-content-type");
155 if (hasValidMIMEType)
156 *hasValidMIMEType = typeOK;
157 return typeOK;
158}
159
160void CachedCSSStyleSheet::destroyDecodedData()
161{
162 if (!m_parsedStyleSheetCache)
163 return;
164
165 m_parsedStyleSheetCache->removedFromMemoryCache();
166 m_parsedStyleSheetCache = nullptr;
167
168 setDecodedSize(0);
169}
170
171RefPtr<StyleSheetContents> CachedCSSStyleSheet::restoreParsedStyleSheet(const CSSParserContext& context, CachePolicy cachePolicy, FrameLoader& loader)
172{
173 if (!m_parsedStyleSheetCache)
174 return nullptr;
175 if (!m_parsedStyleSheetCache->subresourcesAllowReuse(cachePolicy, loader)) {
176 m_parsedStyleSheetCache->removedFromMemoryCache();
177 m_parsedStyleSheetCache = nullptr;
178 return nullptr;
179 }
180
181 ASSERT(m_parsedStyleSheetCache->isCacheable());
182 ASSERT(m_parsedStyleSheetCache->isInMemoryCache());
183
184 // Contexts must be identical so we know we would get the same exact result if we parsed again.
185 if (m_parsedStyleSheetCache->parserContext() != context)
186 return nullptr;
187
188 didAccessDecodedData(MonotonicTime::now());
189
190 return m_parsedStyleSheetCache;
191}
192
193void CachedCSSStyleSheet::saveParsedStyleSheet(Ref<StyleSheetContents>&& sheet)
194{
195 ASSERT(sheet->isCacheable());
196
197 if (m_parsedStyleSheetCache)
198 m_parsedStyleSheetCache->removedFromMemoryCache();
199 m_parsedStyleSheetCache = WTFMove(sheet);
200 m_parsedStyleSheetCache->addedToMemoryCache();
201
202 setDecodedSize(m_parsedStyleSheetCache->estimatedSizeInBytes());
203}
204
205}
206