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 Apple Inc. All rights reserved.
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.
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.
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.
25 #include "CachedResource.h"
27 #include "CachedResourceClient.h"
28 #include "CachedResourceClientWalker.h"
29 #include "CachedResourceHandle.h"
30 #include "CachedResourceLoader.h"
31 #include "CrossOriginAccessControl.h"
32 #include "DiagnosticLoggingClient.h"
33 #include "DiagnosticLoggingKeys.h"
35 #include "DocumentLoader.h"
36 #include "FrameLoader.h"
37 #include "FrameLoaderClient.h"
38 #include "HTTPHeaderNames.h"
39 #include "InspectorInstrumentation.h"
40 #include "LoaderStrategy.h"
42 #include "MainFrame.h"
43 #include "MemoryCache.h"
44 #include "PlatformStrategies.h"
45 #include "ProgressTracker.h"
46 #include "ResourceHandle.h"
47 #include "SchemeRegistry.h"
48 #include "SecurityOrigin.h"
49 #include "SubresourceLoader.h"
51 #include <wtf/CurrentTime.h>
52 #include <wtf/MathExtras.h>
53 #include <wtf/RefCountedLeakCounter.h>
54 #include <wtf/StdLibExtras.h>
55 #include <wtf/Vector.h>
56 #include <wtf/text/CString.h>
59 #include "QuickLook.h"
64 #define RELEASE_LOG_IF_ALLOWED(fmt, ...) RELEASE_LOG_IF(cachedResourceLoader.isAlwaysOnLoggingAllowed(), Network, "%p - CachedResource::" fmt, this, ##__VA_ARGS__)
68 ResourceLoadPriority CachedResource::defaultPriorityForResourceType(Type type)
71 case CachedResource::MainResource:
72 return ResourceLoadPriority::VeryHigh;
73 case CachedResource::CSSStyleSheet:
74 case CachedResource::Script:
75 return ResourceLoadPriority::High;
77 case CachedResource::SVGFontResource:
79 case CachedResource::MediaResource:
80 case CachedResource::FontResource:
81 case CachedResource::RawResource:
82 case CachedResource::Icon:
83 return ResourceLoadPriority::Medium;
84 case CachedResource::ImageResource:
85 return ResourceLoadPriority::Low;
87 case CachedResource::XSLStyleSheet:
88 return ResourceLoadPriority::High;
90 case CachedResource::SVGDocumentResource:
91 return ResourceLoadPriority::Low;
92 case CachedResource::Beacon:
93 return ResourceLoadPriority::VeryLow;
94 #if ENABLE(LINK_PREFETCH)
95 case CachedResource::LinkPrefetch:
96 return ResourceLoadPriority::VeryLow;
97 case CachedResource::LinkSubresource:
98 return ResourceLoadPriority::VeryLow;
100 #if ENABLE(VIDEO_TRACK)
101 case CachedResource::TextTrackResource:
102 return ResourceLoadPriority::Low;
105 ASSERT_NOT_REACHED();
106 return ResourceLoadPriority::Low;
109 static Seconds deadDecodedDataDeletionIntervalForResourceType(CachedResource::Type type)
111 if (type == CachedResource::Script)
114 return MemoryCache::singleton().deadDecodedDataDeletionInterval();
117 DEFINE_DEBUG_ONLY_GLOBAL(RefCountedLeakCounter, cachedResourceLeakCounter, ("CachedResource"));
119 CachedResource::CachedResource(CachedResourceRequest&& request, Type type, PAL::SessionID sessionID)
120 : m_resourceRequest(request.releaseResourceRequest())
121 , m_options(request.options())
122 , m_decodedDataDeletionTimer(*this, &CachedResource::destroyDecodedData, deadDecodedDataDeletionIntervalForResourceType(type))
123 , m_sessionID(sessionID)
124 , m_loadPriority(defaultPriorityForResourceType(type))
125 , m_responseTimestamp(std::chrono::system_clock::now())
126 , m_fragmentIdentifierForRequest(request.releaseFragmentIdentifier())
127 , m_origin(request.releaseOrigin())
128 , m_initiatorName(request.initiatorName())
129 , m_isLinkPreload(request.isLinkPreload())
130 , m_hasUnknownEncoding(request.isLinkPreload())
132 , m_ignoreForRequestCount(request.ignoreForRequestCount())
134 ASSERT(sessionID.isValid());
136 setLoadPriority(request.priority());
138 cachedResourceLeakCounter.increment();
141 // FIXME: We should have a better way of checking for Navigation loads, maybe FetchMode::Options::Navigate.
142 ASSERT(m_origin || m_type == CachedResource::MainResource);
144 if (isRequestCrossOrigin(m_origin.get(), m_resourceRequest.url(), m_options))
148 // FIXME: For this constructor, we should probably mandate that the URL has no fragment identifier.
149 CachedResource::CachedResource(const URL& url, Type type, PAL::SessionID sessionID)
150 : m_resourceRequest(url)
151 , m_decodedDataDeletionTimer(*this, &CachedResource::destroyDecodedData, deadDecodedDataDeletionIntervalForResourceType(type))
152 , m_sessionID(sessionID)
153 , m_responseTimestamp(std::chrono::system_clock::now())
154 , m_fragmentIdentifierForRequest(CachedResourceRequest::splitFragmentIdentifierFromRequestURL(m_resourceRequest))
158 ASSERT(sessionID.isValid());
160 cachedResourceLeakCounter.increment();
164 CachedResource::~CachedResource()
166 ASSERT(!m_resourceToRevalidate); // Should be true because canDelete() checks this.
170 ASSERT(url().isNull() || !allowsCaching() || MemoryCache::singleton().resourceForRequest(resourceRequest(), sessionID()) != this);
174 cachedResourceLeakCounter.decrement();
177 if (m_owningCachedResourceLoader)
178 m_owningCachedResourceLoader->removeCachedResource(*this);
181 void CachedResource::failBeforeStarting()
183 // FIXME: What if resources in other frames were waiting for this revalidation?
184 LOG(ResourceLoading, "Cannot start loading '%s'", url().string().latin1().data());
185 if (allowsCaching() && m_resourceToRevalidate)
186 MemoryCache::singleton().revalidationFailed(*this);
187 error(CachedResource::LoadError);
190 void CachedResource::load(CachedResourceLoader& cachedResourceLoader)
192 if (!cachedResourceLoader.frame()) {
193 RELEASE_LOG_IF_ALLOWED("load: No associated frame");
194 failBeforeStarting();
197 Frame& frame = *cachedResourceLoader.frame();
199 // Prevent new loads if we are in the PageCache or being added to the PageCache.
200 // We query the top document because new frames may be created in pagehide event handlers
201 // and their pageCacheState will not reflect the fact that they are about to enter page
203 if (auto* topDocument = frame.mainFrame().document()) {
204 if (topDocument->pageCacheState() != Document::NotInPageCache) {
205 RELEASE_LOG_IF_ALLOWED("load: Already in page cache or being added to it (frame = %p)", &frame);
206 failBeforeStarting();
211 FrameLoader& frameLoader = frame.loader();
212 if (m_options.securityCheck == DoSecurityCheck && (frameLoader.state() == FrameStateProvisional || !frameLoader.activeDocumentLoader() || frameLoader.activeDocumentLoader()->isStopping())) {
213 if (frameLoader.state() == FrameStateProvisional)
214 RELEASE_LOG_IF_ALLOWED("load: Failed security check -- state is provisional (frame = %p)", &frame);
215 else if (!frameLoader.activeDocumentLoader())
216 RELEASE_LOG_IF_ALLOWED("load: Failed security check -- not active document (frame = %p)", &frame);
217 else if (frameLoader.activeDocumentLoader()->isStopping())
218 RELEASE_LOG_IF_ALLOWED("load: Failed security check -- active loader is stopping (frame = %p)", &frame);
219 failBeforeStarting();
225 if (isCacheValidator()) {
226 CachedResource* resourceToRevalidate = m_resourceToRevalidate;
227 ASSERT(resourceToRevalidate->canUseCacheValidator());
228 ASSERT(resourceToRevalidate->isLoaded());
229 const String& lastModified = resourceToRevalidate->response().httpHeaderField(HTTPHeaderName::LastModified);
230 const String& eTag = resourceToRevalidate->response().httpHeaderField(HTTPHeaderName::ETag);
231 if (!lastModified.isEmpty() || !eTag.isEmpty()) {
232 ASSERT(cachedResourceLoader.cachePolicy(type(), url()) != CachePolicyReload);
233 if (cachedResourceLoader.cachePolicy(type(), url()) == CachePolicyRevalidate)
234 m_resourceRequest.setHTTPHeaderField(HTTPHeaderName::CacheControl, "max-age=0");
235 if (!lastModified.isEmpty())
236 m_resourceRequest.setHTTPHeaderField(HTTPHeaderName::IfModifiedSince, lastModified);
238 m_resourceRequest.setHTTPHeaderField(HTTPHeaderName::IfNoneMatch, eTag);
242 #if ENABLE(LINK_PREFETCH)
243 if (type() == CachedResource::LinkPrefetch || type() == CachedResource::LinkSubresource)
244 m_resourceRequest.setHTTPHeaderField(HTTPHeaderName::Purpose, "prefetch");
246 m_resourceRequest.setPriority(loadPriority());
248 // Navigation algorithm is setting up the request before sending it to CachedResourceLoader?CachedResource.
249 // So no need for extra fields for MainResource.
250 if (type() != CachedResource::MainResource)
251 frameLoader.addExtraFieldsToSubresourceRequest(m_resourceRequest);
254 // FIXME: It's unfortunate that the cache layer and below get to know anything about fragment identifiers.
255 // We should look into removing the expectation of that knowledge from the platform network stacks.
256 ResourceRequest request(m_resourceRequest);
257 if (!m_fragmentIdentifierForRequest.isNull()) {
258 URL url = request.url();
259 url.setFragmentIdentifier(m_fragmentIdentifierForRequest);
261 m_fragmentIdentifierForRequest = String();
264 if (m_options.keepAlive) {
265 if (!cachedResourceLoader.keepaliveRequestTracker().tryRegisterRequest(*this)) {
266 setResourceError({ errorDomainWebKitInternal, 0, request.url(), ASCIILiteral("Reached maximum amount of queued data of 64Kb for keepalive requests") });
267 failBeforeStarting();
270 // FIXME: We should not special-case Beacon here.
271 if (shouldUsePingLoad(type())) {
272 ASSERT(m_originalRequestHeaders);
273 CachedResourceHandle<CachedResource> protectedThis(this);
275 // FIXME: Move beacon loads to normal subresource loading to get normal inspector request instrumentation hooks.
276 unsigned long identifier = frame.page()->progress().createUniqueIdentifier();
277 InspectorInstrumentation::willSendRequestOfType(&frame, identifier, frameLoader.activeDocumentLoader(), request, InspectorInstrumentation::LoadType::Beacon);
279 platformStrategies()->loaderStrategy()->startPingLoad(frame, request, *m_originalRequestHeaders, m_options, [this, protectedThis = WTFMove(protectedThis), protectedFrame = makeRef(frame), identifier] (const ResourceError& error, const ResourceResponse& response) {
280 if (!response.isNull())
281 InspectorInstrumentation::didReceiveResourceResponse(protectedFrame, identifier, protectedFrame->loader().activeDocumentLoader(), response, nullptr);
282 if (error.isNull()) {
283 finishLoading(nullptr);
284 NetworkLoadMetrics emptyMetrics;
285 InspectorInstrumentation::didFinishLoading(protectedFrame.ptr(), protectedFrame->loader().activeDocumentLoader(), identifier, emptyMetrics, nullptr);
287 setResourceError(error);
288 this->error(LoadError);
289 InspectorInstrumentation::didFailLoading(protectedFrame.ptr(), protectedFrame->loader().activeDocumentLoader(), identifier, error);
296 m_loader = platformStrategies()->loaderStrategy()->loadResource(frame, *this, request, m_options);
298 RELEASE_LOG_IF_ALLOWED("load: Unable to create SubresourceLoader (frame = %p)", &frame);
299 failBeforeStarting();
306 void CachedResource::loadFrom(const CachedResource& resource)
308 ASSERT(url() == resource.url());
309 ASSERT(type() == resource.type());
310 ASSERT(resource.status() == Status::Cached);
312 if (isCrossOrigin() && m_options.mode == FetchOptions::Mode::Cors) {
315 if (!WebCore::passesAccessControlCheck(resource.response(), m_options.storedCredentialsPolicy, *m_origin, errorMessage)) {
316 setResourceError(ResourceError(String(), 0, url(), errorMessage, ResourceError::Type::AccessControl));
321 setBodyDataFrom(resource);
322 setStatus(Status::Cached);
326 void CachedResource::setBodyDataFrom(const CachedResource& resource)
328 m_data = resource.m_data;
329 m_response = resource.m_response;
330 m_response.setTainting(m_responseTainting);
331 setDecodedSize(resource.decodedSize());
332 setEncodedSize(resource.encodedSize());
335 void CachedResource::checkNotify()
337 if (isLoading() || stillNeedsLoad())
340 CachedResourceClientWalker<CachedResourceClient> walker(m_clients);
341 while (CachedResourceClient* client = walker.next())
342 client->notifyFinished(*this);
345 void CachedResource::addDataBuffer(SharedBuffer&)
347 ASSERT(dataBufferingPolicy() == BufferData);
350 void CachedResource::addData(const char*, unsigned)
352 ASSERT(dataBufferingPolicy() == DoNotBufferData);
355 void CachedResource::finishLoading(SharedBuffer*)
361 void CachedResource::error(CachedResource::Status status)
364 ASSERT(errorOccurred());
371 void CachedResource::cancelLoad()
373 if (!isLoading() && !stillNeedsLoad())
376 setStatus(LoadError);
381 void CachedResource::finish()
383 if (!errorOccurred())
387 void CachedResource::setCrossOrigin()
389 ASSERT(m_options.mode != FetchOptions::Mode::SameOrigin);
390 m_responseTainting = (m_options.mode == FetchOptions::Mode::Cors) ? ResourceResponse::Tainting::Cors : ResourceResponse::Tainting::Opaque;
393 bool CachedResource::isCrossOrigin() const
395 return m_responseTainting != ResourceResponse::Tainting::Basic;
398 bool CachedResource::isCORSSameOrigin() const
400 // Following resource types do not use CORS
401 ASSERT(type() != CachedResource::Type::FontResource);
402 #if ENABLE(SVG_FONTS)
403 ASSERT(type() != CachedResource::Type::SVGFontResource);
406 ASSERT(type() != CachedResource::XSLStyleSheet);
409 // https://html.spec.whatwg.org/multipage/infrastructure.html#cors-same-origin
410 return !loadFailedOrCanceled() && m_responseTainting != ResourceResponse::Tainting::Opaque;
413 bool CachedResource::isExpired() const
415 if (m_response.isNull())
418 return computeCurrentAge(m_response, m_responseTimestamp) > freshnessLifetime(m_response);
421 static inline bool shouldCacheSchemeIndefinitely(StringView scheme)
424 if (equalLettersIgnoringASCIICase(scheme, "applewebdata"))
428 if (equalLettersIgnoringASCIICase(scheme, "resource"))
431 return equalLettersIgnoringASCIICase(scheme, "data");
434 std::chrono::microseconds CachedResource::freshnessLifetime(const ResourceResponse& response) const
436 using namespace std::literals::chrono_literals;
438 if (!response.url().protocolIsInHTTPFamily()) {
439 StringView protocol = response.url().protocol();
440 if (!shouldCacheSchemeIndefinitely(protocol)) {
441 // Don't cache non-HTTP main resources since we can't check for freshness.
442 // FIXME: We should not cache subresources either, but when we tried this
443 // it caused performance and flakiness issues in our test infrastructure.
444 if (m_type == MainResource || SchemeRegistry::shouldAlwaysRevalidateURLScheme(protocol.toStringWithoutCopying()))
448 return std::chrono::microseconds::max();
451 return computeFreshnessLifetimeForHTTPFamily(response, m_responseTimestamp);
454 void CachedResource::redirectReceived(ResourceRequest&, const ResourceResponse& response)
456 m_requestedFromNetworkingLayer = true;
457 if (response.isNull())
460 updateRedirectChainStatus(m_redirectChainCacheStatus, response);
463 void CachedResource::setResponse(const ResourceResponse& response)
465 ASSERT(m_response.type() == ResourceResponse::Type::Default);
466 m_response = response;
467 m_response.setRedirected(m_redirectChainCacheStatus.status != RedirectChainCacheStatus::NoRedirection);
468 if (m_response.tainting() == ResourceResponse::Tainting::Basic || m_response.tainting() == ResourceResponse::Tainting::Cors)
469 m_response.setTainting(m_responseTainting);
471 m_varyingHeaderValues = collectVaryingRequestHeaders(m_resourceRequest, m_response, m_sessionID);
474 void CachedResource::responseReceived(const ResourceResponse& response)
476 setResponse(response);
477 m_responseTimestamp = std::chrono::system_clock::now();
478 String encoding = response.textEncodingName();
479 if (!encoding.isNull())
480 setEncoding(encoding);
483 void CachedResource::clearLoader()
486 m_identifierForLoadWithoutResourceLoader = m_loader->identifier();
491 void CachedResource::addClient(CachedResourceClient& client)
493 if (addClientToSet(client))
494 didAddClient(client);
497 void CachedResource::didAddClient(CachedResourceClient& client)
499 if (m_decodedDataDeletionTimer.isActive())
500 m_decodedDataDeletionTimer.stop();
502 if (m_clientsAwaitingCallback.remove(&client))
503 m_clients.add(&client);
504 if (!isLoading() && !stillNeedsLoad())
505 client.notifyFinished(*this);
508 bool CachedResource::addClientToSet(CachedResourceClient& client)
510 if (m_preloadResult == PreloadNotReferenced && client.shouldMarkAsReferenced()) {
512 m_preloadResult = PreloadReferencedWhileComplete;
513 else if (m_requestedFromNetworkingLayer)
514 m_preloadResult = PreloadReferencedWhileLoading;
516 m_preloadResult = PreloadReferenced;
518 if (allowsCaching() && !hasClients() && inCache())
519 MemoryCache::singleton().addToLiveResourcesSize(*this);
521 if ((m_type == RawResource || m_type == MainResource) && !m_response.isNull() && !m_proxyResource) {
522 // Certain resources (especially XHRs and main resources) do crazy things if an asynchronous load returns
523 // synchronously (e.g., scripts may not have set all the state they need to handle the load).
524 // Therefore, rather than immediately sending callbacks on a cache hit like other CachedResources,
525 // we schedule the callbacks and ensure we never finish synchronously.
526 ASSERT(!m_clientsAwaitingCallback.contains(&client));
527 m_clientsAwaitingCallback.add(&client, std::make_unique<Callback>(*this, client));
531 m_clients.add(&client);
535 void CachedResource::removeClient(CachedResourceClient& client)
537 auto callback = m_clientsAwaitingCallback.take(&client);
539 ASSERT(!m_clients.contains(&client));
543 ASSERT(m_clients.contains(&client));
544 m_clients.remove(&client);
545 didRemoveClient(client);
548 if (deleteIfPossible()) {
549 // `this` object is dead here.
556 auto& memoryCache = MemoryCache::singleton();
557 if (allowsCaching() && inCache()) {
558 memoryCache.removeFromLiveResourcesSize(*this);
559 memoryCache.removeFromLiveDecodedResourcesList(*this);
561 if (!m_switchingClientsToRevalidatedResource)
563 destroyDecodedDataIfNeeded();
565 if (!allowsCaching())
568 if (response().cacheControlContainsNoStore() && url().protocolIs("https")) {
570 // "no-store: ... MUST make a best-effort attempt to remove the information from volatile storage as promptly as possible"
571 // "... History buffers MAY store such responses as part of their normal operation."
572 // We allow non-secure content to be reused in history, but we do not allow secure content to be reused.
573 memoryCache.remove(*this);
575 memoryCache.pruneSoon();
578 void CachedResource::destroyDecodedDataIfNeeded()
582 if (!MemoryCache::singleton().deadDecodedDataDeletionInterval())
584 m_decodedDataDeletionTimer.restart();
587 void CachedResource::decodedDataDeletionTimerFired()
589 destroyDecodedData();
592 bool CachedResource::deleteIfPossible()
596 InspectorInstrumentation::willDestroyCachedResource(*this);
601 m_data->hintMemoryNotNeededSoon();
606 void CachedResource::setDecodedSize(unsigned size)
608 if (size == m_decodedSize)
611 long long delta = static_cast<long long>(size) - m_decodedSize;
613 // The object must be moved to a different queue, since its size has been changed.
614 // Remove before updating m_decodedSize, so we find the resource in the correct LRU list.
615 if (allowsCaching() && inCache())
616 MemoryCache::singleton().removeFromLRUList(*this);
618 m_decodedSize = size;
620 if (allowsCaching() && inCache()) {
621 auto& memoryCache = MemoryCache::singleton();
622 // Now insert into the new LRU list.
623 memoryCache.insertInLRUList(*this);
625 // Insert into or remove from the live decoded list if necessary.
626 // When inserting into the LiveDecodedResourcesList it is possible
627 // that the m_lastDecodedAccessTime is still zero or smaller than
628 // the m_lastDecodedAccessTime of the current list head. This is a
629 // violation of the invariant that the list is to be kept sorted
630 // by access time. The weakening of the invariant does not pose
631 // a problem. For more details please see: https://bugs.webkit.org/show_bug.cgi?id=30209
632 bool inLiveDecodedResourcesList = memoryCache.inLiveDecodedResourcesList(*this);
633 if (m_decodedSize && !inLiveDecodedResourcesList && hasClients())
634 memoryCache.insertInLiveDecodedResourcesList(*this);
635 else if (!m_decodedSize && inLiveDecodedResourcesList)
636 memoryCache.removeFromLiveDecodedResourcesList(*this);
638 // Update the cache's size totals.
639 memoryCache.adjustSize(hasClients(), delta);
643 void CachedResource::setEncodedSize(unsigned size)
645 if (size == m_encodedSize)
648 long long delta = static_cast<long long>(size) - m_encodedSize;
650 // The object must be moved to a different queue, since its size has been changed.
651 // Remove before updating m_encodedSize, so we find the resource in the correct LRU list.
652 if (allowsCaching() && inCache())
653 MemoryCache::singleton().removeFromLRUList(*this);
655 m_encodedSize = size;
657 if (allowsCaching() && inCache()) {
658 auto& memoryCache = MemoryCache::singleton();
659 memoryCache.insertInLRUList(*this);
660 memoryCache.adjustSize(hasClients(), delta);
664 void CachedResource::didAccessDecodedData(double timeStamp)
666 m_lastDecodedAccessTime = timeStamp;
668 if (allowsCaching() && inCache()) {
669 auto& memoryCache = MemoryCache::singleton();
670 if (memoryCache.inLiveDecodedResourcesList(*this)) {
671 memoryCache.removeFromLiveDecodedResourcesList(*this);
672 memoryCache.insertInLiveDecodedResourcesList(*this);
674 memoryCache.pruneSoon();
678 void CachedResource::setResourceToRevalidate(CachedResource* resource)
681 ASSERT(!m_resourceToRevalidate);
682 ASSERT(resource != this);
683 ASSERT(m_handlesToRevalidate.isEmpty());
684 ASSERT(resource->type() == type());
685 ASSERT(!resource->m_proxyResource);
687 LOG(ResourceLoading, "CachedResource %p setResourceToRevalidate %p", this, resource);
689 resource->m_proxyResource = this;
690 m_resourceToRevalidate = resource;
693 void CachedResource::clearResourceToRevalidate()
695 ASSERT(m_resourceToRevalidate);
696 ASSERT(m_resourceToRevalidate->m_proxyResource == this);
698 if (m_switchingClientsToRevalidatedResource)
701 m_resourceToRevalidate->m_proxyResource = nullptr;
702 m_resourceToRevalidate->deleteIfPossible();
704 m_handlesToRevalidate.clear();
705 m_resourceToRevalidate = nullptr;
709 void CachedResource::switchClientsToRevalidatedResource()
711 ASSERT(m_resourceToRevalidate);
712 ASSERT(m_resourceToRevalidate->inCache());
715 LOG(ResourceLoading, "CachedResource %p switchClientsToRevalidatedResource %p", this, m_resourceToRevalidate);
717 m_switchingClientsToRevalidatedResource = true;
718 for (auto& handle : m_handlesToRevalidate) {
719 handle->m_resource = m_resourceToRevalidate;
720 m_resourceToRevalidate->registerHandle(handle);
723 ASSERT(!m_handleCount);
724 m_handlesToRevalidate.clear();
726 Vector<CachedResourceClient*> clientsToMove;
727 for (auto& entry : m_clients) {
728 CachedResourceClient* client = entry.key;
729 unsigned count = entry.value;
731 clientsToMove.append(client);
736 for (auto& client : clientsToMove)
737 removeClient(*client);
738 ASSERT(m_clients.isEmpty());
740 for (auto& client : clientsToMove)
741 m_resourceToRevalidate->addClientToSet(*client);
742 for (auto& client : clientsToMove) {
743 // Calling didAddClient may do anything, including trying to cancel revalidation.
744 // Assert that it didn't succeed.
745 ASSERT(m_resourceToRevalidate);
746 // Calling didAddClient for a client may end up removing another client. In that case it won't be in the set anymore.
747 if (m_resourceToRevalidate->m_clients.contains(client))
748 m_resourceToRevalidate->didAddClient(*client);
750 m_switchingClientsToRevalidatedResource = false;
753 void CachedResource::updateResponseAfterRevalidation(const ResourceResponse& validatingResponse)
755 m_responseTimestamp = std::chrono::system_clock::now();
757 updateResponseHeadersAfterRevalidation(m_response, validatingResponse);
760 void CachedResource::registerHandle(CachedResourceHandleBase* h)
763 if (m_resourceToRevalidate)
764 m_handlesToRevalidate.add(h);
767 void CachedResource::unregisterHandle(CachedResourceHandleBase* h)
769 ASSERT(m_handleCount > 0);
772 if (m_resourceToRevalidate)
773 m_handlesToRevalidate.remove(h);
779 bool CachedResource::canUseCacheValidator() const
781 if (m_loading || errorOccurred())
784 if (m_response.cacheControlContainsNoStore())
786 return m_response.hasCacheValidatorFields();
789 CachedResource::RevalidationDecision CachedResource::makeRevalidationDecision(CachePolicy cachePolicy) const
791 switch (cachePolicy) {
792 case CachePolicyHistoryBuffer:
793 return RevalidationDecision::No;
795 case CachePolicyReload:
796 return RevalidationDecision::YesDueToCachePolicy;
798 case CachePolicyRevalidate:
799 if (m_response.cacheControlContainsImmutable() && m_response.url().protocolIs("https")) {
801 return RevalidationDecision::YesDueToExpired;
802 return RevalidationDecision::No;
804 return RevalidationDecision::YesDueToCachePolicy;
806 case CachePolicyVerify:
807 if (m_response.cacheControlContainsNoCache())
808 return RevalidationDecision::YesDueToNoCache;
809 // FIXME: Cache-Control:no-store should prevent storing, not reuse.
810 if (m_response.cacheControlContainsNoStore())
811 return RevalidationDecision::YesDueToNoStore;
814 return RevalidationDecision::YesDueToExpired;
816 return RevalidationDecision::No;
818 ASSERT_NOT_REACHED();
819 return RevalidationDecision::No;
822 bool CachedResource::redirectChainAllowsReuse(ReuseExpiredRedirectionOrNot reuseExpiredRedirection) const
824 return WebCore::redirectChainAllowsReuse(m_redirectChainCacheStatus, reuseExpiredRedirection);
827 bool CachedResource::varyHeaderValuesMatch(const ResourceRequest& request)
829 if (m_varyingHeaderValues.isEmpty())
832 return verifyVaryingRequestHeaders(m_varyingHeaderValues, request, m_sessionID);
835 unsigned CachedResource::overheadSize() const
837 static const int kAverageClientsHashMapSize = 384;
838 return sizeof(CachedResource) + m_response.memoryUsage() + kAverageClientsHashMapSize + m_resourceRequest.url().string().length() * 2;
841 bool CachedResource::areAllClientsXMLHttpRequests() const
843 if (type() != RawResource)
846 for (auto& client : m_clients) {
847 if (!client.key->isXMLHttpRequest())
853 void CachedResource::setLoadPriority(const std::optional<ResourceLoadPriority>& loadPriority)
856 m_loadPriority = loadPriority.value();
858 m_loadPriority = defaultPriorityForResourceType(type());
861 inline CachedResource::Callback::Callback(CachedResource& resource, CachedResourceClient& client)
862 : m_resource(resource)
864 , m_timer(*this, &Callback::timerFired)
866 m_timer.startOneShot(0_s);
869 inline void CachedResource::Callback::cancel()
871 if (m_timer.isActive())
875 void CachedResource::Callback::timerFired()
877 m_resource.didAddClient(m_client);
880 #if USE(FOUNDATION) || USE(SOUP)
882 void CachedResource::tryReplaceEncodedData(SharedBuffer& newBuffer)
887 if (!mayTryReplaceEncodedData())
890 // We have to do the memcmp because we can't tell if the replacement file backed data is for the
891 // same resource or if we made a second request with the same URL which gave us a different
892 // resource. We have seen this happen for cached POST resources.
893 if (m_data->size() != newBuffer.size() || memcmp(m_data->data(), newBuffer.data(), m_data->size()))
897 m_data->append(newBuffer);
898 didReplaceSharedBufferContents();