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-2011, 2014, 2018 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 "CachedResource.h" |
26 | |
27 | #include "CachedResourceClient.h" |
28 | #include "CachedResourceClientWalker.h" |
29 | #include "CachedResourceHandle.h" |
30 | #include "CachedResourceLoader.h" |
31 | #include "CookieJar.h" |
32 | #include "CrossOriginAccessControl.h" |
33 | #include "DiagnosticLoggingClient.h" |
34 | #include "DiagnosticLoggingKeys.h" |
35 | #include "Document.h" |
36 | #include "DocumentLoader.h" |
37 | #include "Frame.h" |
38 | #include "FrameLoader.h" |
39 | #include "FrameLoaderClient.h" |
40 | #include "HTTPHeaderNames.h" |
41 | #include "InspectorInstrumentation.h" |
42 | #include "LoaderStrategy.h" |
43 | #include "Logging.h" |
44 | #include "MemoryCache.h" |
45 | #include "PlatformStrategies.h" |
46 | #include "ProgressTracker.h" |
47 | #include "ResourceHandle.h" |
48 | #include "SchemeRegistry.h" |
49 | #include "SecurityOrigin.h" |
50 | #include "SubresourceLoader.h" |
51 | #include <wtf/CompletionHandler.h> |
52 | #include <wtf/MathExtras.h> |
53 | #include <wtf/RefCountedLeakCounter.h> |
54 | #include <wtf/StdLibExtras.h> |
55 | #include <wtf/URL.h> |
56 | #include <wtf/Vector.h> |
57 | #include <wtf/text/CString.h> |
58 | |
59 | #if USE(QUICK_LOOK) |
60 | #include "QuickLook.h" |
61 | #endif |
62 | |
63 | #define RELEASE_LOG_IF_ALLOWED(fmt, ...) RELEASE_LOG_IF(cachedResourceLoader.isAlwaysOnLoggingAllowed(), Network, "%p - CachedResource::" fmt, this, ##__VA_ARGS__) |
64 | |
65 | namespace WebCore { |
66 | |
67 | ResourceLoadPriority CachedResource::defaultPriorityForResourceType(Type type) |
68 | { |
69 | switch (type) { |
70 | case Type::MainResource: |
71 | return ResourceLoadPriority::VeryHigh; |
72 | case Type::CSSStyleSheet: |
73 | case Type::Script: |
74 | return ResourceLoadPriority::High; |
75 | #if ENABLE(SVG_FONTS) |
76 | case Type::SVGFontResource: |
77 | #endif |
78 | case Type::MediaResource: |
79 | case Type::FontResource: |
80 | case Type::RawResource: |
81 | case Type::Icon: |
82 | return ResourceLoadPriority::Medium; |
83 | case Type::ImageResource: |
84 | return ResourceLoadPriority::Low; |
85 | #if ENABLE(XSLT) |
86 | case Type::XSLStyleSheet: |
87 | return ResourceLoadPriority::High; |
88 | #endif |
89 | case Type::SVGDocumentResource: |
90 | return ResourceLoadPriority::Low; |
91 | case Type::Beacon: |
92 | case Type::Ping: |
93 | return ResourceLoadPriority::VeryLow; |
94 | case Type::LinkPrefetch: |
95 | return ResourceLoadPriority::VeryLow; |
96 | #if ENABLE(VIDEO_TRACK) |
97 | case Type::TextTrackResource: |
98 | return ResourceLoadPriority::Low; |
99 | #endif |
100 | #if ENABLE(APPLICATION_MANIFEST) |
101 | case Type::ApplicationManifest: |
102 | return ResourceLoadPriority::Low; |
103 | #endif |
104 | } |
105 | ASSERT_NOT_REACHED(); |
106 | return ResourceLoadPriority::Low; |
107 | } |
108 | |
109 | static Seconds deadDecodedDataDeletionIntervalForResourceType(CachedResource::Type type) |
110 | { |
111 | if (type == CachedResource::Type::Script) |
112 | return 0_s; |
113 | |
114 | return MemoryCache::singleton().deadDecodedDataDeletionInterval(); |
115 | } |
116 | |
117 | DEFINE_DEBUG_ONLY_GLOBAL(WTF::RefCountedLeakCounter, cachedResourceLeakCounter, ("CachedResource" )); |
118 | |
119 | CachedResource::CachedResource(CachedResourceRequest&& request, Type type, const PAL::SessionID& sessionID, const CookieJar* cookieJar) |
120 | : m_options(request.options()) |
121 | , m_resourceRequest(request.releaseResourceRequest()) |
122 | , m_decodedDataDeletionTimer(*this, &CachedResource::destroyDecodedData, deadDecodedDataDeletionIntervalForResourceType(type)) |
123 | , m_sessionID(sessionID) |
124 | , m_cookieJar(cookieJar) |
125 | , m_responseTimestamp(WallTime::now()) |
126 | , m_fragmentIdentifierForRequest(request.releaseFragmentIdentifier()) |
127 | , m_origin(request.releaseOrigin()) |
128 | , m_initiatorName(request.initiatorName()) |
129 | , m_loadPriority(defaultPriorityForResourceType(type)) |
130 | , m_type(type) |
131 | , m_isLinkPreload(request.isLinkPreload()) |
132 | , m_hasUnknownEncoding(request.isLinkPreload()) |
133 | , m_ignoreForRequestCount(request.ignoreForRequestCount()) |
134 | { |
135 | ASSERT(m_sessionID.isValid()); |
136 | |
137 | setLoadPriority(request.priority()); |
138 | #ifndef NDEBUG |
139 | cachedResourceLeakCounter.increment(); |
140 | #endif |
141 | |
142 | // FIXME: We should have a better way of checking for Navigation loads, maybe FetchMode::Options::Navigate. |
143 | ASSERT(m_origin || m_type == Type::MainResource); |
144 | |
145 | if (isRequestCrossOrigin(m_origin.get(), m_resourceRequest.url(), m_options)) |
146 | setCrossOrigin(); |
147 | } |
148 | |
149 | // FIXME: For this constructor, we should probably mandate that the URL has no fragment identifier. |
150 | CachedResource::CachedResource(const URL& url, Type type, const PAL::SessionID& sessionID, const CookieJar* cookieJar) |
151 | : m_resourceRequest(url) |
152 | , m_decodedDataDeletionTimer(*this, &CachedResource::destroyDecodedData, deadDecodedDataDeletionIntervalForResourceType(type)) |
153 | , m_sessionID(sessionID) |
154 | , m_cookieJar(cookieJar) |
155 | , m_responseTimestamp(WallTime::now()) |
156 | , m_fragmentIdentifierForRequest(CachedResourceRequest::splitFragmentIdentifierFromRequestURL(m_resourceRequest)) |
157 | , m_status(Cached) |
158 | , m_type(type) |
159 | { |
160 | ASSERT(m_sessionID.isValid()); |
161 | #ifndef NDEBUG |
162 | cachedResourceLeakCounter.increment(); |
163 | #endif |
164 | } |
165 | |
166 | CachedResource::~CachedResource() |
167 | { |
168 | ASSERT(!m_resourceToRevalidate); // Should be true because canDelete() checks this. |
169 | ASSERT(canDelete()); |
170 | ASSERT(!inCache()); |
171 | ASSERT(!m_deleted); |
172 | ASSERT(url().isNull() || !allowsCaching() || MemoryCache::singleton().resourceForRequest(resourceRequest(), sessionID()) != this); |
173 | |
174 | #ifndef NDEBUG |
175 | m_deleted = true; |
176 | cachedResourceLeakCounter.decrement(); |
177 | #endif |
178 | } |
179 | |
180 | void CachedResource::failBeforeStarting() |
181 | { |
182 | // FIXME: What if resources in other frames were waiting for this revalidation? |
183 | LOG(ResourceLoading, "Cannot start loading '%s'" , url().string().latin1().data()); |
184 | if (allowsCaching() && m_resourceToRevalidate) |
185 | MemoryCache::singleton().revalidationFailed(*this); |
186 | error(CachedResource::LoadError); |
187 | } |
188 | |
189 | void CachedResource::load(CachedResourceLoader& cachedResourceLoader) |
190 | { |
191 | if (!cachedResourceLoader.frame()) { |
192 | RELEASE_LOG_IF_ALLOWED("load: No associated frame" ); |
193 | failBeforeStarting(); |
194 | return; |
195 | } |
196 | Frame& frame = *cachedResourceLoader.frame(); |
197 | |
198 | // Prevent new loads if we are in the PageCache or being added to the PageCache. |
199 | // We query the top document because new frames may be created in pagehide event handlers |
200 | // and their pageCacheState will not reflect the fact that they are about to enter page |
201 | // cache. |
202 | if (auto* topDocument = frame.mainFrame().document()) { |
203 | switch (topDocument->pageCacheState()) { |
204 | case Document::NotInPageCache: |
205 | break; |
206 | case Document::AboutToEnterPageCache: |
207 | // Beacons are allowed to go through in 'pagehide' event handlers. |
208 | if (shouldUsePingLoad(type())) |
209 | break; |
210 | RELEASE_LOG_IF_ALLOWED("load: About to enter page cache (frame = %p)" , &frame); |
211 | failBeforeStarting(); |
212 | return; |
213 | case Document::InPageCache: |
214 | RELEASE_LOG_IF_ALLOWED("load: Already in page cache (frame = %p)" , &frame); |
215 | failBeforeStarting(); |
216 | return; |
217 | } |
218 | } |
219 | |
220 | FrameLoader& frameLoader = frame.loader(); |
221 | if (m_options.securityCheck == SecurityCheckPolicy::DoSecurityCheck && !shouldUsePingLoad(type())) { |
222 | while (true) { |
223 | if (frameLoader.state() == FrameStateProvisional) |
224 | RELEASE_LOG_IF_ALLOWED("load: Failed security check -- state is provisional (frame = %p)" , &frame); |
225 | else if (!frameLoader.activeDocumentLoader()) |
226 | RELEASE_LOG_IF_ALLOWED("load: Failed security check -- not active document (frame = %p)" , &frame); |
227 | else if (frameLoader.activeDocumentLoader()->isStopping()) |
228 | RELEASE_LOG_IF_ALLOWED("load: Failed security check -- active loader is stopping (frame = %p)" , &frame); |
229 | else |
230 | break; |
231 | failBeforeStarting(); |
232 | return; |
233 | } |
234 | } |
235 | |
236 | m_loading = true; |
237 | |
238 | if (isCacheValidator()) { |
239 | CachedResource* resourceToRevalidate = m_resourceToRevalidate; |
240 | ASSERT(resourceToRevalidate->canUseCacheValidator()); |
241 | ASSERT(resourceToRevalidate->isLoaded()); |
242 | const String& lastModified = resourceToRevalidate->response().httpHeaderField(HTTPHeaderName::LastModified); |
243 | const String& eTag = resourceToRevalidate->response().httpHeaderField(HTTPHeaderName::ETag); |
244 | if (!lastModified.isEmpty() || !eTag.isEmpty()) { |
245 | ASSERT(cachedResourceLoader.cachePolicy(type(), url()) != CachePolicyReload); |
246 | if (cachedResourceLoader.cachePolicy(type(), url()) == CachePolicyRevalidate) |
247 | m_resourceRequest.setHTTPHeaderField(HTTPHeaderName::CacheControl, "max-age=0" ); |
248 | if (!lastModified.isEmpty()) |
249 | m_resourceRequest.setHTTPHeaderField(HTTPHeaderName::IfModifiedSince, lastModified); |
250 | if (!eTag.isEmpty()) |
251 | m_resourceRequest.setHTTPHeaderField(HTTPHeaderName::IfNoneMatch, eTag); |
252 | } |
253 | } |
254 | |
255 | if (type() == Type::LinkPrefetch) |
256 | m_resourceRequest.setHTTPHeaderField(HTTPHeaderName::Purpose, "prefetch" ); |
257 | m_resourceRequest.setPriority(loadPriority()); |
258 | |
259 | // Navigation algorithm is setting up the request before sending it to CachedResourceLoader?CachedResource. |
260 | // So no need for extra fields for MainResource. |
261 | if (type() != Type::MainResource) |
262 | frameLoader.addExtraFieldsToSubresourceRequest(m_resourceRequest); |
263 | |
264 | |
265 | // FIXME: It's unfortunate that the cache layer and below get to know anything about fragment identifiers. |
266 | // We should look into removing the expectation of that knowledge from the platform network stacks. |
267 | ResourceRequest request(m_resourceRequest); |
268 | if (!m_fragmentIdentifierForRequest.isNull()) { |
269 | URL url = request.url(); |
270 | url.setFragmentIdentifier(m_fragmentIdentifierForRequest); |
271 | request.setURL(url); |
272 | m_fragmentIdentifierForRequest = String(); |
273 | } |
274 | |
275 | if (m_options.keepAlive && type() != Type::Ping && !cachedResourceLoader.keepaliveRequestTracker().tryRegisterRequest(*this)) { |
276 | setResourceError({ errorDomainWebKitInternal, 0, request.url(), "Reached maximum amount of queued data of 64Kb for keepalive requests"_s , ResourceError::Type::AccessControl }); |
277 | failBeforeStarting(); |
278 | return; |
279 | } |
280 | |
281 | // FIXME: Deprecate that code path. |
282 | if (m_options.keepAlive && shouldUsePingLoad(type()) && platformStrategies()->loaderStrategy()->usePingLoad()) { |
283 | ASSERT(m_originalRequest); |
284 | CachedResourceHandle<CachedResource> protectedThis(this); |
285 | |
286 | unsigned long identifier = frame.page()->progress().createUniqueIdentifier(); |
287 | InspectorInstrumentation::willSendRequestOfType(&frame, identifier, frameLoader.activeDocumentLoader(), request, InspectorInstrumentation::LoadType::Beacon); |
288 | |
289 | platformStrategies()->loaderStrategy()->startPingLoad(frame, request, m_originalRequest->httpHeaderFields(), m_options, m_options.contentSecurityPolicyImposition, [this, protectedThis = WTFMove(protectedThis), protectedFrame = makeRef(frame), identifier] (const ResourceError& error, const ResourceResponse& response) { |
290 | if (!response.isNull()) |
291 | InspectorInstrumentation::didReceiveResourceResponse(protectedFrame, identifier, protectedFrame->loader().activeDocumentLoader(), response, nullptr); |
292 | if (!error.isNull()) { |
293 | setResourceError(error); |
294 | this->error(LoadError); |
295 | InspectorInstrumentation::didFailLoading(protectedFrame.ptr(), protectedFrame->loader().activeDocumentLoader(), identifier, error); |
296 | return; |
297 | } |
298 | finishLoading(nullptr); |
299 | NetworkLoadMetrics emptyMetrics; |
300 | InspectorInstrumentation::didFinishLoading(protectedFrame.ptr(), protectedFrame->loader().activeDocumentLoader(), identifier, emptyMetrics, nullptr); |
301 | }); |
302 | return; |
303 | } |
304 | |
305 | platformStrategies()->loaderStrategy()->loadResource(frame, *this, WTFMove(request), m_options, [this, protectedThis = CachedResourceHandle<CachedResource>(this), frame = makeRef(frame), loggingAllowed = cachedResourceLoader.isAlwaysOnLoggingAllowed()] (RefPtr<SubresourceLoader>&& loader) { |
306 | m_loader = WTFMove(loader); |
307 | if (!m_loader) { |
308 | RELEASE_LOG_IF(loggingAllowed, Network, "%p - CachedResource::load: Unable to create SubresourceLoader (frame = %p)" , this, frame.ptr()); |
309 | failBeforeStarting(); |
310 | return; |
311 | } |
312 | m_status = Pending; |
313 | }); |
314 | } |
315 | |
316 | void CachedResource::loadFrom(const CachedResource& resource) |
317 | { |
318 | ASSERT(url() == resource.url()); |
319 | ASSERT(type() == resource.type()); |
320 | ASSERT(resource.status() == Status::Cached); |
321 | |
322 | if (isCrossOrigin() && m_options.mode == FetchOptions::Mode::Cors) { |
323 | ASSERT(m_origin); |
324 | String errorMessage; |
325 | if (!WebCore::passesAccessControlCheck(resource.response(), m_options.storedCredentialsPolicy, *m_origin, errorMessage)) { |
326 | setResourceError(ResourceError(String(), 0, url(), errorMessage, ResourceError::Type::AccessControl)); |
327 | return; |
328 | } |
329 | } |
330 | |
331 | setBodyDataFrom(resource); |
332 | setStatus(Status::Cached); |
333 | setLoading(false); |
334 | } |
335 | |
336 | void CachedResource::setBodyDataFrom(const CachedResource& resource) |
337 | { |
338 | m_data = resource.m_data; |
339 | m_response = resource.m_response; |
340 | m_response.setTainting(m_responseTainting); |
341 | setDecodedSize(resource.decodedSize()); |
342 | setEncodedSize(resource.encodedSize()); |
343 | } |
344 | |
345 | void CachedResource::checkNotify() |
346 | { |
347 | if (isLoading() || stillNeedsLoad()) |
348 | return; |
349 | |
350 | CachedResourceClientWalker<CachedResourceClient> walker(m_clients); |
351 | while (CachedResourceClient* client = walker.next()) |
352 | client->notifyFinished(*this); |
353 | } |
354 | |
355 | void CachedResource::updateBuffer(SharedBuffer&) |
356 | { |
357 | ASSERT(dataBufferingPolicy() == DataBufferingPolicy::BufferData); |
358 | } |
359 | |
360 | void CachedResource::updateData(const char*, unsigned) |
361 | { |
362 | ASSERT(dataBufferingPolicy() == DataBufferingPolicy::DoNotBufferData); |
363 | } |
364 | |
365 | void CachedResource::finishLoading(SharedBuffer*) |
366 | { |
367 | setLoading(false); |
368 | checkNotify(); |
369 | } |
370 | |
371 | void CachedResource::error(CachedResource::Status status) |
372 | { |
373 | setStatus(status); |
374 | ASSERT(errorOccurred()); |
375 | m_data = nullptr; |
376 | |
377 | setLoading(false); |
378 | checkNotify(); |
379 | } |
380 | |
381 | void CachedResource::cancelLoad() |
382 | { |
383 | if (!isLoading() && !stillNeedsLoad()) |
384 | return; |
385 | |
386 | auto* documentLoader = (m_loader && m_loader->frame()) ? m_loader->frame()->loader().activeDocumentLoader() : nullptr; |
387 | if (m_options.keepAlive && (!documentLoader || documentLoader->isStopping())) |
388 | m_error = { }; |
389 | else |
390 | setStatus(LoadError); |
391 | |
392 | setLoading(false); |
393 | checkNotify(); |
394 | } |
395 | |
396 | void CachedResource::finish() |
397 | { |
398 | if (!errorOccurred()) |
399 | m_status = Cached; |
400 | } |
401 | |
402 | void CachedResource::setCrossOrigin() |
403 | { |
404 | ASSERT(m_options.mode != FetchOptions::Mode::SameOrigin); |
405 | m_responseTainting = (m_options.mode == FetchOptions::Mode::Cors) ? ResourceResponse::Tainting::Cors : ResourceResponse::Tainting::Opaque; |
406 | } |
407 | |
408 | bool CachedResource::isCrossOrigin() const |
409 | { |
410 | return m_responseTainting != ResourceResponse::Tainting::Basic; |
411 | } |
412 | |
413 | bool CachedResource::() const |
414 | { |
415 | // Following resource types do not use CORS |
416 | ASSERT(type() != Type::FontResource); |
417 | #if ENABLE(SVG_FONTS) |
418 | ASSERT(type() != Type::SVGFontResource); |
419 | #endif |
420 | #if ENABLE(XSLT) |
421 | ASSERT(type() != Type::XSLStyleSheet); |
422 | #endif |
423 | |
424 | // https://html.spec.whatwg.org/multipage/infrastructure.html#cors-same-origin |
425 | return !loadFailedOrCanceled() && m_responseTainting != ResourceResponse::Tainting::Opaque; |
426 | } |
427 | |
428 | bool CachedResource::isExpired() const |
429 | { |
430 | if (m_response.isNull()) |
431 | return false; |
432 | |
433 | return computeCurrentAge(m_response, m_responseTimestamp) > freshnessLifetime(m_response); |
434 | } |
435 | |
436 | static inline bool shouldCacheSchemeIndefinitely(StringView scheme) |
437 | { |
438 | #if PLATFORM(COCOA) |
439 | if (equalLettersIgnoringASCIICase(scheme, "applewebdata" )) |
440 | return true; |
441 | #endif |
442 | #if USE(SOUP) |
443 | if (equalLettersIgnoringASCIICase(scheme, "resource" )) |
444 | return true; |
445 | #endif |
446 | return equalLettersIgnoringASCIICase(scheme, "data" ); |
447 | } |
448 | |
449 | Seconds CachedResource::freshnessLifetime(const ResourceResponse& response) const |
450 | { |
451 | if (!response.url().protocolIsInHTTPFamily()) { |
452 | StringView protocol = response.url().protocol(); |
453 | if (!shouldCacheSchemeIndefinitely(protocol)) { |
454 | // Don't cache non-HTTP main resources since we can't check for freshness. |
455 | // FIXME: We should not cache subresources either, but when we tried this |
456 | // it caused performance and flakiness issues in our test infrastructure. |
457 | if (m_type == Type::MainResource || SchemeRegistry::shouldAlwaysRevalidateURLScheme(protocol.toStringWithoutCopying())) |
458 | return 0_us; |
459 | } |
460 | |
461 | return Seconds::infinity(); |
462 | } |
463 | |
464 | return computeFreshnessLifetimeForHTTPFamily(response, m_responseTimestamp); |
465 | } |
466 | |
467 | void CachedResource::redirectReceived(ResourceRequest&& request, const ResourceResponse& response, CompletionHandler<void(ResourceRequest&&)>&& completionHandler) |
468 | { |
469 | m_requestedFromNetworkingLayer = true; |
470 | if (response.isNull()) |
471 | return completionHandler(WTFMove(request)); |
472 | |
473 | updateRedirectChainStatus(m_redirectChainCacheStatus, response); |
474 | completionHandler(WTFMove(request)); |
475 | } |
476 | |
477 | void CachedResource::setResponse(const ResourceResponse& response) |
478 | { |
479 | ASSERT(m_response.type() == ResourceResponse::Type::Default); |
480 | m_response = response; |
481 | m_varyingHeaderValues = collectVaryingRequestHeaders(cookieJar(), m_resourceRequest, m_response, sessionID()); |
482 | |
483 | #if ENABLE(SERVICE_WORKER) |
484 | if (m_response.source() == ResourceResponse::Source::ServiceWorker) { |
485 | m_responseTainting = m_response.tainting(); |
486 | return; |
487 | } |
488 | #endif |
489 | m_response.setRedirected(m_redirectChainCacheStatus.status != RedirectChainCacheStatus::Status::NoRedirection); |
490 | if (m_response.tainting() == ResourceResponse::Tainting::Basic || m_response.tainting() == ResourceResponse::Tainting::Cors) |
491 | m_response.setTainting(m_responseTainting); |
492 | } |
493 | |
494 | void CachedResource::responseReceived(const ResourceResponse& response) |
495 | { |
496 | setResponse(response); |
497 | m_responseTimestamp = WallTime::now(); |
498 | String encoding = response.textEncodingName(); |
499 | if (!encoding.isNull()) |
500 | setEncoding(encoding); |
501 | } |
502 | |
503 | void CachedResource::clearLoader() |
504 | { |
505 | ASSERT(m_loader); |
506 | m_identifierForLoadWithoutResourceLoader = m_loader->identifier(); |
507 | m_loader = nullptr; |
508 | deleteIfPossible(); |
509 | } |
510 | |
511 | void CachedResource::addClient(CachedResourceClient& client) |
512 | { |
513 | if (addClientToSet(client)) |
514 | didAddClient(client); |
515 | } |
516 | |
517 | void CachedResource::didAddClient(CachedResourceClient& client) |
518 | { |
519 | if (m_decodedDataDeletionTimer.isActive()) |
520 | m_decodedDataDeletionTimer.stop(); |
521 | |
522 | if (m_clientsAwaitingCallback.remove(&client)) |
523 | m_clients.add(&client); |
524 | |
525 | // FIXME: Make calls to notifyFinished async |
526 | if (!isLoading() && !stillNeedsLoad()) |
527 | client.notifyFinished(*this); |
528 | } |
529 | |
530 | bool CachedResource::addClientToSet(CachedResourceClient& client) |
531 | { |
532 | if (m_preloadResult == PreloadResult::PreloadNotReferenced && client.shouldMarkAsReferenced()) { |
533 | if (isLoaded()) |
534 | m_preloadResult = PreloadResult::PreloadReferencedWhileComplete; |
535 | else if (m_requestedFromNetworkingLayer) |
536 | m_preloadResult = PreloadResult::PreloadReferencedWhileLoading; |
537 | else |
538 | m_preloadResult = PreloadResult::PreloadReferenced; |
539 | } |
540 | if (allowsCaching() && !hasClients() && inCache()) |
541 | MemoryCache::singleton().addToLiveResourcesSize(*this); |
542 | |
543 | if ((m_type == Type::RawResource || m_type == Type::MainResource) && !m_response.isNull() && !m_proxyResource) { |
544 | // Certain resources (especially XHRs and main resources) do crazy things if an asynchronous load returns |
545 | // synchronously (e.g., scripts may not have set all the state they need to handle the load). |
546 | // Therefore, rather than immediately sending callbacks on a cache hit like other CachedResources, |
547 | // we schedule the callbacks and ensure we never finish synchronously. |
548 | ASSERT(!m_clientsAwaitingCallback.contains(&client)); |
549 | m_clientsAwaitingCallback.add(&client, std::make_unique<Callback>(*this, client)); |
550 | return false; |
551 | } |
552 | |
553 | m_clients.add(&client); |
554 | return true; |
555 | } |
556 | |
557 | void CachedResource::removeClient(CachedResourceClient& client) |
558 | { |
559 | auto callback = m_clientsAwaitingCallback.take(&client); |
560 | if (callback) { |
561 | ASSERT(!m_clients.contains(&client)); |
562 | callback->cancel(); |
563 | callback = nullptr; |
564 | } else { |
565 | ASSERT(m_clients.contains(&client)); |
566 | m_clients.remove(&client); |
567 | didRemoveClient(client); |
568 | } |
569 | |
570 | if (deleteIfPossible()) { |
571 | // `this` object is dead here. |
572 | return; |
573 | } |
574 | |
575 | if (hasClients()) |
576 | return; |
577 | |
578 | auto& memoryCache = MemoryCache::singleton(); |
579 | if (allowsCaching() && inCache()) { |
580 | memoryCache.removeFromLiveResourcesSize(*this); |
581 | memoryCache.removeFromLiveDecodedResourcesList(*this); |
582 | } |
583 | if (!m_switchingClientsToRevalidatedResource) |
584 | allClientsRemoved(); |
585 | destroyDecodedDataIfNeeded(); |
586 | |
587 | if (!allowsCaching()) |
588 | return; |
589 | |
590 | if (response().cacheControlContainsNoStore() && url().protocolIs("https" )) { |
591 | // RFC2616 14.9.2: |
592 | // "no-store: ... MUST make a best-effort attempt to remove the information from volatile storage as promptly as possible" |
593 | // "... History buffers MAY store such responses as part of their normal operation." |
594 | // We allow non-secure content to be reused in history, but we do not allow secure content to be reused. |
595 | memoryCache.remove(*this); |
596 | } |
597 | memoryCache.pruneSoon(); |
598 | } |
599 | |
600 | void CachedResource::allClientsRemoved() |
601 | { |
602 | if (isLinkPreload() && m_loader) |
603 | m_loader->cancelIfNotFinishing(); |
604 | } |
605 | |
606 | void CachedResource::destroyDecodedDataIfNeeded() |
607 | { |
608 | if (!m_decodedSize) |
609 | return; |
610 | if (!MemoryCache::singleton().deadDecodedDataDeletionInterval()) |
611 | return; |
612 | m_decodedDataDeletionTimer.restart(); |
613 | } |
614 | |
615 | void CachedResource::decodedDataDeletionTimerFired() |
616 | { |
617 | destroyDecodedData(); |
618 | } |
619 | |
620 | bool CachedResource::deleteIfPossible() |
621 | { |
622 | if (canDelete()) { |
623 | LOG(ResourceLoading, "CachedResource %p deleteIfPossible - can delete, in cache %d" , this, inCache()); |
624 | if (!inCache()) { |
625 | InspectorInstrumentation::willDestroyCachedResource(*this); |
626 | delete this; |
627 | return true; |
628 | } |
629 | if (m_data) |
630 | m_data->hintMemoryNotNeededSoon(); |
631 | } |
632 | |
633 | LOG(ResourceLoading, "CachedResource %p deleteIfPossible - can't delete (hasClients %d loader %p preloadCount %u handleCount %u resourceToRevalidate %p proxyResource %p)" , this, hasClients(), m_loader.get(), m_preloadCount, m_handleCount, m_resourceToRevalidate, m_proxyResource); |
634 | return false; |
635 | } |
636 | |
637 | void CachedResource::setDecodedSize(unsigned size) |
638 | { |
639 | if (size == m_decodedSize) |
640 | return; |
641 | |
642 | long long delta = static_cast<long long>(size) - m_decodedSize; |
643 | |
644 | // The object must be moved to a different queue, since its size has been changed. |
645 | // Remove before updating m_decodedSize, so we find the resource in the correct LRU list. |
646 | if (allowsCaching() && inCache()) |
647 | MemoryCache::singleton().removeFromLRUList(*this); |
648 | |
649 | m_decodedSize = size; |
650 | |
651 | if (allowsCaching() && inCache()) { |
652 | auto& memoryCache = MemoryCache::singleton(); |
653 | // Now insert into the new LRU list. |
654 | memoryCache.insertInLRUList(*this); |
655 | |
656 | // Insert into or remove from the live decoded list if necessary. |
657 | // When inserting into the LiveDecodedResourcesList it is possible |
658 | // that the m_lastDecodedAccessTime is still zero or smaller than |
659 | // the m_lastDecodedAccessTime of the current list head. This is a |
660 | // violation of the invariant that the list is to be kept sorted |
661 | // by access time. The weakening of the invariant does not pose |
662 | // a problem. For more details please see: https://bugs.webkit.org/show_bug.cgi?id=30209 |
663 | bool inLiveDecodedResourcesList = memoryCache.inLiveDecodedResourcesList(*this); |
664 | if (m_decodedSize && !inLiveDecodedResourcesList && hasClients()) |
665 | memoryCache.insertInLiveDecodedResourcesList(*this); |
666 | else if (!m_decodedSize && inLiveDecodedResourcesList) |
667 | memoryCache.removeFromLiveDecodedResourcesList(*this); |
668 | |
669 | // Update the cache's size totals. |
670 | memoryCache.adjustSize(hasClients(), delta); |
671 | } |
672 | } |
673 | |
674 | void CachedResource::setEncodedSize(unsigned size) |
675 | { |
676 | if (size == m_encodedSize) |
677 | return; |
678 | |
679 | long long delta = static_cast<long long>(size) - m_encodedSize; |
680 | |
681 | // The object must be moved to a different queue, since its size has been changed. |
682 | // Remove before updating m_encodedSize, so we find the resource in the correct LRU list. |
683 | if (allowsCaching() && inCache()) |
684 | MemoryCache::singleton().removeFromLRUList(*this); |
685 | |
686 | m_encodedSize = size; |
687 | |
688 | if (allowsCaching() && inCache()) { |
689 | auto& memoryCache = MemoryCache::singleton(); |
690 | memoryCache.insertInLRUList(*this); |
691 | memoryCache.adjustSize(hasClients(), delta); |
692 | } |
693 | } |
694 | |
695 | void CachedResource::didAccessDecodedData(MonotonicTime timeStamp) |
696 | { |
697 | m_lastDecodedAccessTime = timeStamp; |
698 | |
699 | if (allowsCaching() && inCache()) { |
700 | auto& memoryCache = MemoryCache::singleton(); |
701 | if (memoryCache.inLiveDecodedResourcesList(*this)) { |
702 | memoryCache.removeFromLiveDecodedResourcesList(*this); |
703 | memoryCache.insertInLiveDecodedResourcesList(*this); |
704 | } |
705 | memoryCache.pruneSoon(); |
706 | } |
707 | } |
708 | |
709 | void CachedResource::setResourceToRevalidate(CachedResource* resource) |
710 | { |
711 | ASSERT(resource); |
712 | ASSERT(!m_resourceToRevalidate); |
713 | ASSERT(resource != this); |
714 | ASSERT(m_handlesToRevalidate.isEmpty()); |
715 | ASSERT(resource->type() == type()); |
716 | ASSERT(!resource->m_proxyResource); |
717 | |
718 | LOG(ResourceLoading, "CachedResource %p setResourceToRevalidate %p" , this, resource); |
719 | |
720 | resource->m_proxyResource = this; |
721 | m_resourceToRevalidate = resource; |
722 | } |
723 | |
724 | void CachedResource::clearResourceToRevalidate() |
725 | { |
726 | ASSERT(m_resourceToRevalidate); |
727 | ASSERT(m_resourceToRevalidate->m_proxyResource == this); |
728 | |
729 | if (m_switchingClientsToRevalidatedResource) |
730 | return; |
731 | |
732 | m_resourceToRevalidate->m_proxyResource = nullptr; |
733 | m_resourceToRevalidate->deleteIfPossible(); |
734 | |
735 | m_handlesToRevalidate.clear(); |
736 | m_resourceToRevalidate = nullptr; |
737 | deleteIfPossible(); |
738 | } |
739 | |
740 | void CachedResource::switchClientsToRevalidatedResource() |
741 | { |
742 | ASSERT(m_resourceToRevalidate); |
743 | ASSERT(m_resourceToRevalidate->inCache()); |
744 | ASSERT(!inCache()); |
745 | |
746 | LOG(ResourceLoading, "CachedResource %p switchClientsToRevalidatedResource %p" , this, m_resourceToRevalidate); |
747 | |
748 | m_switchingClientsToRevalidatedResource = true; |
749 | for (auto& handle : m_handlesToRevalidate) { |
750 | handle->m_resource = m_resourceToRevalidate; |
751 | m_resourceToRevalidate->registerHandle(handle); |
752 | --m_handleCount; |
753 | } |
754 | ASSERT(!m_handleCount); |
755 | m_handlesToRevalidate.clear(); |
756 | |
757 | Vector<CachedResourceClient*> clientsToMove; |
758 | for (auto& entry : m_clients) { |
759 | CachedResourceClient* client = entry.key; |
760 | unsigned count = entry.value; |
761 | while (count) { |
762 | clientsToMove.append(client); |
763 | --count; |
764 | } |
765 | } |
766 | |
767 | for (auto& client : clientsToMove) |
768 | removeClient(*client); |
769 | ASSERT(m_clients.isEmpty()); |
770 | |
771 | for (auto& client : clientsToMove) |
772 | m_resourceToRevalidate->addClientToSet(*client); |
773 | for (auto& client : clientsToMove) { |
774 | // Calling didAddClient may do anything, including trying to cancel revalidation. |
775 | // Assert that it didn't succeed. |
776 | ASSERT(m_resourceToRevalidate); |
777 | // Calling didAddClient for a client may end up removing another client. In that case it won't be in the set anymore. |
778 | if (m_resourceToRevalidate->m_clients.contains(client)) |
779 | m_resourceToRevalidate->didAddClient(*client); |
780 | } |
781 | m_switchingClientsToRevalidatedResource = false; |
782 | } |
783 | |
784 | void CachedResource::updateResponseAfterRevalidation(const ResourceResponse& validatingResponse) |
785 | { |
786 | m_responseTimestamp = WallTime::now(); |
787 | |
788 | updateResponseHeadersAfterRevalidation(m_response, validatingResponse); |
789 | } |
790 | |
791 | void CachedResource::registerHandle(CachedResourceHandleBase* h) |
792 | { |
793 | ++m_handleCount; |
794 | if (m_resourceToRevalidate) |
795 | m_handlesToRevalidate.add(h); |
796 | } |
797 | |
798 | void CachedResource::unregisterHandle(CachedResourceHandleBase* h) |
799 | { |
800 | ASSERT(m_handleCount > 0); |
801 | --m_handleCount; |
802 | |
803 | if (m_resourceToRevalidate) |
804 | m_handlesToRevalidate.remove(h); |
805 | |
806 | if (!m_handleCount) |
807 | deleteIfPossible(); |
808 | } |
809 | |
810 | bool CachedResource::canUseCacheValidator() const |
811 | { |
812 | if (m_loading || errorOccurred()) |
813 | return false; |
814 | |
815 | if (m_response.cacheControlContainsNoStore()) |
816 | return false; |
817 | return m_response.hasCacheValidatorFields(); |
818 | } |
819 | |
820 | CachedResource::RevalidationDecision CachedResource::makeRevalidationDecision(CachePolicy cachePolicy) const |
821 | { |
822 | switch (cachePolicy) { |
823 | case CachePolicyHistoryBuffer: |
824 | return RevalidationDecision::No; |
825 | |
826 | case CachePolicyReload: |
827 | return RevalidationDecision::YesDueToCachePolicy; |
828 | |
829 | case CachePolicyRevalidate: |
830 | if (m_response.cacheControlContainsImmutable() && m_response.url().protocolIs("https" )) { |
831 | if (isExpired()) |
832 | return RevalidationDecision::YesDueToExpired; |
833 | return RevalidationDecision::No; |
834 | } |
835 | return RevalidationDecision::YesDueToCachePolicy; |
836 | |
837 | case CachePolicyVerify: |
838 | if (m_response.cacheControlContainsNoCache()) |
839 | return RevalidationDecision::YesDueToNoCache; |
840 | // FIXME: Cache-Control:no-store should prevent storing, not reuse. |
841 | if (m_response.cacheControlContainsNoStore()) |
842 | return RevalidationDecision::YesDueToNoStore; |
843 | |
844 | if (isExpired()) |
845 | return RevalidationDecision::YesDueToExpired; |
846 | |
847 | return RevalidationDecision::No; |
848 | }; |
849 | ASSERT_NOT_REACHED(); |
850 | return RevalidationDecision::No; |
851 | } |
852 | |
853 | bool CachedResource::redirectChainAllowsReuse(ReuseExpiredRedirectionOrNot reuseExpiredRedirection) const |
854 | { |
855 | return WebCore::redirectChainAllowsReuse(m_redirectChainCacheStatus, reuseExpiredRedirection); |
856 | } |
857 | |
858 | bool CachedResource::(const ResourceRequest& request) |
859 | { |
860 | if (m_varyingHeaderValues.isEmpty()) |
861 | return true; |
862 | |
863 | return verifyVaryingRequestHeaders(cookieJar(), m_varyingHeaderValues, request, sessionID()); |
864 | } |
865 | |
866 | unsigned CachedResource::overheadSize() const |
867 | { |
868 | static const int kAverageClientsHashMapSize = 384; |
869 | return sizeof(CachedResource) + m_response.memoryUsage() + kAverageClientsHashMapSize + m_resourceRequest.url().string().length() * 2; |
870 | } |
871 | |
872 | bool CachedResource::areAllClientsXMLHttpRequests() const |
873 | { |
874 | if (type() != Type::RawResource) |
875 | return false; |
876 | |
877 | for (auto& client : m_clients) { |
878 | if (!client.key->isXMLHttpRequest()) |
879 | return false; |
880 | } |
881 | return true; |
882 | } |
883 | |
884 | void CachedResource::setLoadPriority(const Optional<ResourceLoadPriority>& loadPriority) |
885 | { |
886 | if (loadPriority) |
887 | m_loadPriority = loadPriority.value(); |
888 | else |
889 | m_loadPriority = defaultPriorityForResourceType(type()); |
890 | } |
891 | |
892 | inline CachedResource::Callback::Callback(CachedResource& resource, CachedResourceClient& client) |
893 | : m_resource(resource) |
894 | , m_client(client) |
895 | , m_timer(*this, &Callback::timerFired) |
896 | { |
897 | m_timer.startOneShot(0_s); |
898 | } |
899 | |
900 | inline void CachedResource::Callback::cancel() |
901 | { |
902 | if (m_timer.isActive()) |
903 | m_timer.stop(); |
904 | } |
905 | |
906 | void CachedResource::Callback::timerFired() |
907 | { |
908 | m_resource.didAddClient(m_client); |
909 | } |
910 | |
911 | #if USE(FOUNDATION) || USE(SOUP) |
912 | |
913 | void CachedResource::tryReplaceEncodedData(SharedBuffer& newBuffer) |
914 | { |
915 | if (!m_data) |
916 | return; |
917 | |
918 | if (!mayTryReplaceEncodedData()) |
919 | return; |
920 | |
921 | // We have to do the memcmp because we can't tell if the replacement file backed data is for the |
922 | // same resource or if we made a second request with the same URL which gave us a different |
923 | // resource. We have seen this happen for cached POST resources. |
924 | if (m_data->size() != newBuffer.size() || memcmp(m_data->data(), newBuffer.data(), m_data->size())) |
925 | return; |
926 | |
927 | m_data->clear(); |
928 | m_data->append(newBuffer); |
929 | didReplaceSharedBufferContents(); |
930 | } |
931 | |
932 | #endif |
933 | |
934 | } |
935 | |