/* Copyright (C) 1998 Lars Knoll (knoll@mpi-hd.mpg.de) Copyright (C) 2001 Dirk Mueller (mueller@kde.org) Copyright (C) 2002 Waldo Bastian (bastian@kde.org) Copyright (C) 2006 Samuel Weinig (sam.weinig@gmail.com) Copyright (C) 2004-2011, 2014 Apple Inc. All rights reserved. This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "config.h" #include "CachedResource.h" #include "CachedResourceClient.h" #include "CachedResourceClientWalker.h" #include "CachedResourceHandle.h" #include "CachedResourceLoader.h" #include "CrossOriginAccessControl.h" #include "DiagnosticLoggingClient.h" #include "DiagnosticLoggingKeys.h" #include "Document.h" #include "DocumentLoader.h" #include "FrameLoader.h" #include "FrameLoaderClient.h" #include "HTTPHeaderNames.h" #include "InspectorInstrumentation.h" #include "URL.h" #include "LoaderStrategy.h" #include "Logging.h" #include "MainFrame.h" #include "MemoryCache.h" #include "PlatformStrategies.h" #include "ResourceHandle.h" #include "SchemeRegistry.h" #include "SecurityOrigin.h" #include "SubresourceLoader.h" #include #include #include #include #include #include #if USE(QUICK_LOOK) #include "QuickLook.h" #endif using namespace WTF; #define RELEASE_LOG_IF_ALLOWED(fmt, ...) RELEASE_LOG_IF(cachedResourceLoader.isAlwaysOnLoggingAllowed(), Network, "%p - CachedResource::" fmt, this, ##__VA_ARGS__) namespace WebCore { ResourceLoadPriority CachedResource::defaultPriorityForResourceType(Type type) { switch (type) { case CachedResource::MainResource: return ResourceLoadPriority::VeryHigh; case CachedResource::CSSStyleSheet: case CachedResource::Script: return ResourceLoadPriority::High; #if ENABLE(SVG_FONTS) case CachedResource::SVGFontResource: #endif case CachedResource::MediaResource: case CachedResource::FontResource: case CachedResource::RawResource: return ResourceLoadPriority::Medium; case CachedResource::ImageResource: return ResourceLoadPriority::Low; #if ENABLE(XSLT) case CachedResource::XSLStyleSheet: return ResourceLoadPriority::High; #endif case CachedResource::SVGDocumentResource: return ResourceLoadPriority::Low; #if ENABLE(LINK_PREFETCH) case CachedResource::LinkPrefetch: return ResourceLoadPriority::VeryLow; case CachedResource::LinkSubresource: return ResourceLoadPriority::VeryLow; #endif #if ENABLE(VIDEO_TRACK) case CachedResource::TextTrackResource: return ResourceLoadPriority::Low; #endif } ASSERT_NOT_REACHED(); return ResourceLoadPriority::Low; } static std::chrono::milliseconds deadDecodedDataDeletionIntervalForResourceType(CachedResource::Type type) { if (type == CachedResource::Script) return std::chrono::milliseconds { 0 }; return MemoryCache::singleton().deadDecodedDataDeletionInterval(); } DEFINE_DEBUG_ONLY_GLOBAL(RefCountedLeakCounter, cachedResourceLeakCounter, ("CachedResource")); CachedResource::CachedResource(CachedResourceRequest&& request, Type type, SessionID sessionID) : m_resourceRequest(request.releaseResourceRequest()) , m_options(request.options()) , m_decodedDataDeletionTimer(*this, &CachedResource::destroyDecodedData, deadDecodedDataDeletionIntervalForResourceType(type)) , m_sessionID(sessionID) , m_loadPriority(defaultPriorityForResourceType(type)) , m_responseTimestamp(std::chrono::system_clock::now()) , m_fragmentIdentifierForRequest(request.releaseFragmentIdentifier()) , m_origin(request.releaseOrigin()) , m_initiatorName(request.initiatorName()) , m_isLinkPreload(request.isLinkPreload()) , m_type(type) { ASSERT(sessionID.isValid()); setLoadPriority(request.priority()); #ifndef NDEBUG cachedResourceLeakCounter.increment(); #endif // FIXME: We should have a better way of checking for Navigation loads, maybe FetchMode::Options::Navigate. ASSERT(m_origin || m_type == CachedResource::MainResource); if (isRequestCrossOrigin(m_origin.get(), m_resourceRequest.url(), m_options)) setCrossOrigin(); } // FIXME: For this constructor, we should probably mandate that the URL has no fragment identifier. CachedResource::CachedResource(const URL& url, Type type, SessionID sessionID) : m_resourceRequest(url) , m_decodedDataDeletionTimer(*this, &CachedResource::destroyDecodedData, deadDecodedDataDeletionIntervalForResourceType(type)) , m_sessionID(sessionID) , m_responseTimestamp(std::chrono::system_clock::now()) , m_fragmentIdentifierForRequest(CachedResourceRequest::splitFragmentIdentifierFromRequestURL(m_resourceRequest)) , m_type(type) , m_status(Cached) { ASSERT(sessionID.isValid()); #ifndef NDEBUG cachedResourceLeakCounter.increment(); #endif } CachedResource::~CachedResource() { ASSERT(!m_resourceToRevalidate); // Should be true because canDelete() checks this. ASSERT(canDelete()); ASSERT(!inCache()); ASSERT(!m_deleted); ASSERT(url().isNull() || !allowsCaching() || MemoryCache::singleton().resourceForRequest(resourceRequest(), sessionID()) != this); #ifndef NDEBUG m_deleted = true; cachedResourceLeakCounter.decrement(); #endif if (m_owningCachedResourceLoader) m_owningCachedResourceLoader->removeCachedResource(*this); } void CachedResource::failBeforeStarting() { // FIXME: What if resources in other frames were waiting for this revalidation? LOG(ResourceLoading, "Cannot start loading '%s'", url().string().latin1().data()); if (allowsCaching() && m_resourceToRevalidate) MemoryCache::singleton().revalidationFailed(*this); error(CachedResource::LoadError); } void CachedResource::load(CachedResourceLoader& cachedResourceLoader) { if (!cachedResourceLoader.frame()) { RELEASE_LOG_IF_ALLOWED("load: No associated frame"); failBeforeStarting(); return; } Frame& frame = *cachedResourceLoader.frame(); // Prevent new loads if we are in the PageCache or being added to the PageCache. // We query the top document because new frames may be created in pagehide event handlers // and their pageCacheState will not reflect the fact that they are about to enter page // cache. if (auto* topDocument = frame.mainFrame().document()) { if (topDocument->pageCacheState() != Document::NotInPageCache) { RELEASE_LOG_IF_ALLOWED("load: Already in page cache or being added to it (frame = %p)", &frame); failBeforeStarting(); return; } } FrameLoader& frameLoader = frame.loader(); if (m_options.securityCheck == DoSecurityCheck && (frameLoader.state() == FrameStateProvisional || !frameLoader.activeDocumentLoader() || frameLoader.activeDocumentLoader()->isStopping())) { if (frameLoader.state() == FrameStateProvisional) RELEASE_LOG_IF_ALLOWED("load: Failed security check -- state is provisional (frame = %p)", &frame); else if (!frameLoader.activeDocumentLoader()) RELEASE_LOG_IF_ALLOWED("load: Failed security check -- not active document (frame = %p)", &frame); else if (frameLoader.activeDocumentLoader()->isStopping()) RELEASE_LOG_IF_ALLOWED("load: Failed security check -- active loader is stopping (frame = %p)", &frame); failBeforeStarting(); return; } m_loading = true; if (isCacheValidator()) { CachedResource* resourceToRevalidate = m_resourceToRevalidate; ASSERT(resourceToRevalidate->canUseCacheValidator()); ASSERT(resourceToRevalidate->isLoaded()); const String& lastModified = resourceToRevalidate->response().httpHeaderField(HTTPHeaderName::LastModified); const String& eTag = resourceToRevalidate->response().httpHeaderField(HTTPHeaderName::ETag); if (!lastModified.isEmpty() || !eTag.isEmpty()) { ASSERT(cachedResourceLoader.cachePolicy(type()) != CachePolicyReload); if (cachedResourceLoader.cachePolicy(type()) == CachePolicyRevalidate) m_resourceRequest.setHTTPHeaderField(HTTPHeaderName::CacheControl, "max-age=0"); if (!lastModified.isEmpty()) m_resourceRequest.setHTTPHeaderField(HTTPHeaderName::IfModifiedSince, lastModified); if (!eTag.isEmpty()) m_resourceRequest.setHTTPHeaderField(HTTPHeaderName::IfNoneMatch, eTag); } } #if ENABLE(LINK_PREFETCH) if (type() == CachedResource::LinkPrefetch || type() == CachedResource::LinkSubresource) m_resourceRequest.setHTTPHeaderField(HTTPHeaderName::Purpose, "prefetch"); #endif m_resourceRequest.setPriority(loadPriority()); // Navigation algorithm is setting up the request before sending it to CachedResourceLoader?CachedResource. // So no need for extra fields for MainResource. if (type() != CachedResource::MainResource) frameLoader.addExtraFieldsToSubresourceRequest(m_resourceRequest); // FIXME: It's unfortunate that the cache layer and below get to know anything about fragment identifiers. // We should look into removing the expectation of that knowledge from the platform network stacks. ResourceRequest request(m_resourceRequest); if (!m_fragmentIdentifierForRequest.isNull()) { URL url = request.url(); url.setFragmentIdentifier(m_fragmentIdentifierForRequest); request.setURL(url); m_fragmentIdentifierForRequest = String(); } m_loader = platformStrategies()->loaderStrategy()->loadResource(frame, *this, request, m_options); if (!m_loader) { RELEASE_LOG_IF_ALLOWED("load: Unable to create SubresourceLoader (frame = %p)", &frame); failBeforeStarting(); return; } m_status = Pending; } void CachedResource::loadFrom(const CachedResource& resource) { ASSERT(url() == resource.url()); ASSERT(type() == resource.type()); ASSERT(resource.status() == Status::Cached); if (isCrossOrigin() && m_options.mode == FetchOptions::Mode::Cors) { ASSERT(m_origin); String errorMessage; if (!WebCore::passesAccessControlCheck(resource.response(), m_options.allowCredentials, *m_origin, errorMessage)) { setResourceError(ResourceError(String(), 0, url(), errorMessage, ResourceError::Type::AccessControl)); return; } } setBodyDataFrom(resource); setStatus(Status::Cached); setLoading(false); } void CachedResource::setBodyDataFrom(const CachedResource& resource) { m_data = resource.m_data; m_response = resource.m_response; setDecodedSize(resource.decodedSize()); setEncodedSize(resource.encodedSize()); } void CachedResource::checkNotify() { if (isLoading() || stillNeedsLoad()) return; CachedResourceClientWalker walker(m_clients); while (CachedResourceClient* client = walker.next()) client->notifyFinished(*this); } void CachedResource::addDataBuffer(SharedBuffer&) { ASSERT(dataBufferingPolicy() == BufferData); } void CachedResource::addData(const char*, unsigned) { ASSERT(dataBufferingPolicy() == DoNotBufferData); } void CachedResource::finishLoading(SharedBuffer*) { setLoading(false); checkNotify(); } void CachedResource::error(CachedResource::Status status) { setStatus(status); ASSERT(errorOccurred()); m_data = nullptr; setLoading(false); checkNotify(); } void CachedResource::cancelLoad() { if (!isLoading() && !stillNeedsLoad()) return; setStatus(LoadError); setLoading(false); checkNotify(); } void CachedResource::finish() { if (!errorOccurred()) m_status = Cached; } void CachedResource::setCrossOrigin() { ASSERT(m_options.mode != FetchOptions::Mode::SameOrigin); m_responseTainting = (m_options.mode == FetchOptions::Mode::Cors) ? ResourceResponse::Tainting::Cors : ResourceResponse::Tainting::Opaque; } bool CachedResource::isCrossOrigin() const { return m_responseTainting != ResourceResponse::Tainting::Basic; } bool CachedResource::isCORSSameOrigin() const { // Following resource types do not use CORS ASSERT(type() != CachedResource::Type::FontResource); #if ENABLE(SVG_FONTS) ASSERT(type() != CachedResource::Type::SVGFontResource); #endif #if ENABLE(XSLT) ASSERT(type() != CachedResource::XSLStyleSheet); #endif // https://html.spec.whatwg.org/multipage/infrastructure.html#cors-same-origin return !loadFailedOrCanceled() && m_responseTainting != ResourceResponse::Tainting::Opaque; } bool CachedResource::isExpired() const { if (m_response.isNull()) return false; return computeCurrentAge(m_response, m_responseTimestamp) > freshnessLifetime(m_response); } static inline bool shouldCacheSchemeIndefinitely(StringView scheme) { #if PLATFORM(COCOA) if (equalLettersIgnoringASCIICase(scheme, "applewebdata")) return true; #endif #if USE(SOUP) if (equalLettersIgnoringASCIICase(scheme, "resource")) return true; #endif return equalLettersIgnoringASCIICase(scheme, "data"); } std::chrono::microseconds CachedResource::freshnessLifetime(const ResourceResponse& response) const { if (!response.url().protocolIsInHTTPFamily()) { StringView protocol = response.url().protocol(); if (!shouldCacheSchemeIndefinitely(protocol)) { // Don't cache non-HTTP main resources since we can't check for freshness. // FIXME: We should not cache subresources either, but when we tried this // it caused performance and flakiness issues in our test infrastructure. if (m_type == MainResource || SchemeRegistry::shouldAlwaysRevalidateURLScheme(protocol.toStringWithoutCopying())) return 0us; } return std::chrono::microseconds::max(); } return computeFreshnessLifetimeForHTTPFamily(response, m_responseTimestamp); } void CachedResource::redirectReceived(ResourceRequest&, const ResourceResponse& response) { m_requestedFromNetworkingLayer = true; if (response.isNull()) return; updateRedirectChainStatus(m_redirectChainCacheStatus, response); } void CachedResource::setResponse(const ResourceResponse& response) { ASSERT(m_response.type() == ResourceResponse::Type::Default); m_response = response; m_response.setRedirected(m_redirectChainCacheStatus.status != RedirectChainCacheStatus::NoRedirection); m_varyingHeaderValues = collectVaryingRequestHeaders(m_resourceRequest, m_response, m_sessionID); } void CachedResource::responseReceived(const ResourceResponse& response) { setResponse(response); m_responseTimestamp = std::chrono::system_clock::now(); String encoding = response.textEncodingName(); if (!encoding.isNull()) setEncoding(encoding); } void CachedResource::clearLoader() { ASSERT(m_loader); m_identifierForLoadWithoutResourceLoader = m_loader->identifier(); m_loader = nullptr; deleteIfPossible(); } void CachedResource::addClient(CachedResourceClient& client) { if (addClientToSet(client)) didAddClient(client); } void CachedResource::didAddClient(CachedResourceClient& client) { if (m_decodedDataDeletionTimer.isActive()) m_decodedDataDeletionTimer.stop(); if (m_clientsAwaitingCallback.remove(&client)) m_clients.add(&client); if (!isLoading() && !stillNeedsLoad()) client.notifyFinished(*this); } bool CachedResource::addClientToSet(CachedResourceClient& client) { if (m_preloadResult == PreloadNotReferenced) { if (isLoaded()) m_preloadResult = PreloadReferencedWhileComplete; else if (m_requestedFromNetworkingLayer) m_preloadResult = PreloadReferencedWhileLoading; else m_preloadResult = PreloadReferenced; } if (allowsCaching() && !hasClients() && inCache()) MemoryCache::singleton().addToLiveResourcesSize(*this); if ((m_type == RawResource || m_type == MainResource) && !m_response.isNull() && !m_proxyResource) { // Certain resources (especially XHRs and main resources) do crazy things if an asynchronous load returns // synchronously (e.g., scripts may not have set all the state they need to handle the load). // Therefore, rather than immediately sending callbacks on a cache hit like other CachedResources, // we schedule the callbacks and ensure we never finish synchronously. ASSERT(!m_clientsAwaitingCallback.contains(&client)); m_clientsAwaitingCallback.add(&client, std::make_unique(*this, client)); return false; } m_clients.add(&client); return true; } void CachedResource::removeClient(CachedResourceClient& client) { auto callback = m_clientsAwaitingCallback.take(&client); if (callback) { ASSERT(!m_clients.contains(&client)); callback->cancel(); callback = nullptr; } else { ASSERT(m_clients.contains(&client)); m_clients.remove(&client); didRemoveClient(client); } if (deleteIfPossible()) { // `this` object is dead here. return; } if (hasClients()) return; auto& memoryCache = MemoryCache::singleton(); if (allowsCaching() && inCache()) { memoryCache.removeFromLiveResourcesSize(*this); memoryCache.removeFromLiveDecodedResourcesList(*this); } if (!m_switchingClientsToRevalidatedResource) allClientsRemoved(); destroyDecodedDataIfNeeded(); if (!allowsCaching()) return; if (response().cacheControlContainsNoStore() && url().protocolIs("https")) { // RFC2616 14.9.2: // "no-store: ... MUST make a best-effort attempt to remove the information from volatile storage as promptly as possible" // "... History buffers MAY store such responses as part of their normal operation." // We allow non-secure content to be reused in history, but we do not allow secure content to be reused. memoryCache.remove(*this); } memoryCache.pruneSoon(); } void CachedResource::destroyDecodedDataIfNeeded() { if (!m_decodedSize) return; if (!MemoryCache::singleton().deadDecodedDataDeletionInterval().count()) return; m_decodedDataDeletionTimer.restart(); } void CachedResource::decodedDataDeletionTimerFired() { destroyDecodedData(); } bool CachedResource::deleteIfPossible() { if (canDelete()) { if (!inCache()) { InspectorInstrumentation::willDestroyCachedResource(*this); delete this; return true; } if (m_data) m_data->hintMemoryNotNeededSoon(); } return false; } void CachedResource::setDecodedSize(unsigned size) { if (size == m_decodedSize) return; long long delta = static_cast(size) - m_decodedSize; // The object must be moved to a different queue, since its size has been changed. // Remove before updating m_decodedSize, so we find the resource in the correct LRU list. if (allowsCaching() && inCache()) MemoryCache::singleton().removeFromLRUList(*this); m_decodedSize = size; if (allowsCaching() && inCache()) { auto& memoryCache = MemoryCache::singleton(); // Now insert into the new LRU list. memoryCache.insertInLRUList(*this); // Insert into or remove from the live decoded list if necessary. // When inserting into the LiveDecodedResourcesList it is possible // that the m_lastDecodedAccessTime is still zero or smaller than // the m_lastDecodedAccessTime of the current list head. This is a // violation of the invariant that the list is to be kept sorted // by access time. The weakening of the invariant does not pose // a problem. For more details please see: https://bugs.webkit.org/show_bug.cgi?id=30209 bool inLiveDecodedResourcesList = memoryCache.inLiveDecodedResourcesList(*this); if (m_decodedSize && !inLiveDecodedResourcesList && hasClients()) memoryCache.insertInLiveDecodedResourcesList(*this); else if (!m_decodedSize && inLiveDecodedResourcesList) memoryCache.removeFromLiveDecodedResourcesList(*this); // Update the cache's size totals. memoryCache.adjustSize(hasClients(), delta); } } void CachedResource::setEncodedSize(unsigned size) { if (size == m_encodedSize) return; long long delta = static_cast(size) - m_encodedSize; // The object must be moved to a different queue, since its size has been changed. // Remove before updating m_encodedSize, so we find the resource in the correct LRU list. if (allowsCaching() && inCache()) MemoryCache::singleton().removeFromLRUList(*this); m_encodedSize = size; if (allowsCaching() && inCache()) { auto& memoryCache = MemoryCache::singleton(); memoryCache.insertInLRUList(*this); memoryCache.adjustSize(hasClients(), delta); } } void CachedResource::didAccessDecodedData(double timeStamp) { m_lastDecodedAccessTime = timeStamp; if (allowsCaching() && inCache()) { auto& memoryCache = MemoryCache::singleton(); if (memoryCache.inLiveDecodedResourcesList(*this)) { memoryCache.removeFromLiveDecodedResourcesList(*this); memoryCache.insertInLiveDecodedResourcesList(*this); } memoryCache.pruneSoon(); } } void CachedResource::setResourceToRevalidate(CachedResource* resource) { ASSERT(resource); ASSERT(!m_resourceToRevalidate); ASSERT(resource != this); ASSERT(m_handlesToRevalidate.isEmpty()); ASSERT(resource->type() == type()); ASSERT(!resource->m_proxyResource); LOG(ResourceLoading, "CachedResource %p setResourceToRevalidate %p", this, resource); resource->m_proxyResource = this; m_resourceToRevalidate = resource; } void CachedResource::clearResourceToRevalidate() { ASSERT(m_resourceToRevalidate); ASSERT(m_resourceToRevalidate->m_proxyResource == this); if (m_switchingClientsToRevalidatedResource) return; m_resourceToRevalidate->m_proxyResource = nullptr; m_resourceToRevalidate->deleteIfPossible(); m_handlesToRevalidate.clear(); m_resourceToRevalidate = nullptr; deleteIfPossible(); } void CachedResource::switchClientsToRevalidatedResource() { ASSERT(m_resourceToRevalidate); ASSERT(m_resourceToRevalidate->inCache()); ASSERT(!inCache()); LOG(ResourceLoading, "CachedResource %p switchClientsToRevalidatedResource %p", this, m_resourceToRevalidate); m_switchingClientsToRevalidatedResource = true; for (auto& handle : m_handlesToRevalidate) { handle->m_resource = m_resourceToRevalidate; m_resourceToRevalidate->registerHandle(handle); --m_handleCount; } ASSERT(!m_handleCount); m_handlesToRevalidate.clear(); Vector clientsToMove; for (auto& entry : m_clients) { CachedResourceClient* client = entry.key; unsigned count = entry.value; while (count) { clientsToMove.append(client); --count; } } for (auto& client : clientsToMove) removeClient(*client); ASSERT(m_clients.isEmpty()); for (auto& client : clientsToMove) m_resourceToRevalidate->addClientToSet(*client); for (auto& client : clientsToMove) { // Calling didAddClient may do anything, including trying to cancel revalidation. // Assert that it didn't succeed. ASSERT(m_resourceToRevalidate); // Calling didAddClient for a client may end up removing another client. In that case it won't be in the set anymore. if (m_resourceToRevalidate->m_clients.contains(client)) m_resourceToRevalidate->didAddClient(*client); } m_switchingClientsToRevalidatedResource = false; } void CachedResource::updateResponseAfterRevalidation(const ResourceResponse& validatingResponse) { m_responseTimestamp = std::chrono::system_clock::now(); updateResponseHeadersAfterRevalidation(m_response, validatingResponse); } void CachedResource::registerHandle(CachedResourceHandleBase* h) { ++m_handleCount; if (m_resourceToRevalidate) m_handlesToRevalidate.add(h); } void CachedResource::unregisterHandle(CachedResourceHandleBase* h) { ASSERT(m_handleCount > 0); --m_handleCount; if (m_resourceToRevalidate) m_handlesToRevalidate.remove(h); if (!m_handleCount) deleteIfPossible(); } bool CachedResource::canUseCacheValidator() const { if (m_loading || errorOccurred()) return false; if (m_response.cacheControlContainsNoStore()) return false; return m_response.hasCacheValidatorFields(); } CachedResource::RevalidationDecision CachedResource::makeRevalidationDecision(CachePolicy cachePolicy) const { switch (cachePolicy) { case CachePolicyHistoryBuffer: return RevalidationDecision::No; case CachePolicyReload: return RevalidationDecision::YesDueToCachePolicy; case CachePolicyRevalidate: if (m_response.cacheControlContainsImmutable() && m_response.url().protocolIs("https")) { if (isExpired()) return RevalidationDecision::YesDueToExpired; return RevalidationDecision::No; } return RevalidationDecision::YesDueToCachePolicy; case CachePolicyVerify: if (m_response.cacheControlContainsNoCache()) return RevalidationDecision::YesDueToNoCache; // FIXME: Cache-Control:no-store should prevent storing, not reuse. if (m_response.cacheControlContainsNoStore()) return RevalidationDecision::YesDueToNoStore; if (isExpired()) return RevalidationDecision::YesDueToExpired; return RevalidationDecision::No; }; ASSERT_NOT_REACHED(); return RevalidationDecision::No; } bool CachedResource::redirectChainAllowsReuse(ReuseExpiredRedirectionOrNot reuseExpiredRedirection) const { return WebCore::redirectChainAllowsReuse(m_redirectChainCacheStatus, reuseExpiredRedirection); } bool CachedResource::varyHeaderValuesMatch(const ResourceRequest& request) { if (m_varyingHeaderValues.isEmpty()) return true; return verifyVaryingRequestHeaders(m_varyingHeaderValues, request, m_sessionID); } unsigned CachedResource::overheadSize() const { static const int kAverageClientsHashMapSize = 384; return sizeof(CachedResource) + m_response.memoryUsage() + kAverageClientsHashMapSize + m_resourceRequest.url().string().length() * 2; } bool CachedResource::areAllClientsXMLHttpRequests() const { if (type() != RawResource) return false; for (auto& client : m_clients) { if (!client.key->isXMLHttpRequest()) return false; } return true; } void CachedResource::setLoadPriority(const std::optional& loadPriority) { if (loadPriority) m_loadPriority = loadPriority.value(); else m_loadPriority = defaultPriorityForResourceType(type()); } inline CachedResource::Callback::Callback(CachedResource& resource, CachedResourceClient& client) : m_resource(resource) , m_client(client) , m_timer(*this, &Callback::timerFired) { m_timer.startOneShot(0); } inline void CachedResource::Callback::cancel() { if (m_timer.isActive()) m_timer.stop(); } void CachedResource::Callback::timerFired() { m_resource.didAddClient(m_client); } #if USE(FOUNDATION) || USE(SOUP) void CachedResource::tryReplaceEncodedData(SharedBuffer& newBuffer) { if (!m_data) return; if (!mayTryReplaceEncodedData()) return; // We have to do the memcmp because we can't tell if the replacement file backed data is for the // same resource or if we made a second request with the same URL which gave us a different // resource. We have seen this happen for cached POST resources. if (m_data->size() != newBuffer.size() || memcmp(m_data->data(), newBuffer.data(), m_data->size())) return; if (m_data->tryReplaceContentsWithPlatformBuffer(newBuffer)) didReplaceSharedBufferContents(); } #endif }