diff options
Diffstat (limited to 'Source/WebCore/loader/cache/CachedResource.cpp')
-rw-r--r-- | Source/WebCore/loader/cache/CachedResource.cpp | 824 |
1 files changed, 385 insertions, 439 deletions
diff --git a/Source/WebCore/loader/cache/CachedResource.cpp b/Source/WebCore/loader/cache/CachedResource.cpp index 5f0bc76fa..8b09d3285 100644 --- a/Source/WebCore/loader/cache/CachedResource.cpp +++ b/Source/WebCore/loader/cache/CachedResource.cpp @@ -3,7 +3,7 @@ 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, 2005, 2006, 2007, 2008, 2009, 2010, 2011 Apple Inc. All rights reserved. + 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 @@ -29,23 +29,23 @@ #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 "PurgeableBuffer.h" -#include "ResourceBuffer.h" #include "ResourceHandle.h" -#include "ResourceLoadScheduler.h" #include "SchemeRegistry.h" #include "SecurityOrigin.h" -#include "SecurityPolicy.h" #include "SubresourceLoader.h" #include <wtf/CurrentTime.h> #include <wtf/MathExtras.h> @@ -60,139 +60,99 @@ using namespace WTF; -namespace WebCore { +#define RELEASE_LOG_IF_ALLOWED(fmt, ...) RELEASE_LOG_IF(cachedResourceLoader.isAlwaysOnLoggingAllowed(), Network, "%p - CachedResource::" fmt, this, ##__VA_ARGS__) -// These response headers are not copied from a revalidated response to the -// cached response headers. For compatibility, this list is based on Chromium's -// net/http/http_response_headers.cc. -const char* const headersToIgnoreAfterRevalidation[] = { - "allow", - "connection", - "etag", - "expires", - "keep-alive", - "last-modified" - "proxy-authenticate", - "proxy-connection", - "trailer", - "transfer-encoding", - "upgrade", - "www-authenticate", - "x-frame-options", - "x-xss-protection", -}; - -// Some header prefixes mean "Don't copy this header from a 304 response.". -// Rather than listing all the relevant headers, we can consolidate them into -// this list, also grabbed from Chromium's net/http/http_response_headers.cc. -const char* const headerPrefixesToIgnoreAfterRevalidation[] = { - "content-", - "x-content-", - "x-webkit-" -}; - -static inline bool shouldUpdateHeaderAfterRevalidation(const AtomicString& header) -{ - for (size_t i = 0; i < WTF_ARRAY_LENGTH(headersToIgnoreAfterRevalidation); i++) { - if (equalIgnoringCase(header, headersToIgnoreAfterRevalidation[i])) - return false; - } - for (size_t i = 0; i < WTF_ARRAY_LENGTH(headerPrefixesToIgnoreAfterRevalidation); i++) { - if (header.startsWith(headerPrefixesToIgnoreAfterRevalidation[i], false)) - return false; - } - return true; -} +namespace WebCore { -static ResourceLoadPriority defaultPriorityForResourceType(CachedResource::Type type) +ResourceLoadPriority CachedResource::defaultPriorityForResourceType(Type type) { switch (type) { case CachedResource::MainResource: - return ResourceLoadPriorityVeryHigh; + return ResourceLoadPriority::VeryHigh; case CachedResource::CSSStyleSheet: - return ResourceLoadPriorityHigh; case CachedResource::Script: + return ResourceLoadPriority::High; +#if ENABLE(SVG_FONTS) + case CachedResource::SVGFontResource: +#endif + case CachedResource::MediaResource: case CachedResource::FontResource: case CachedResource::RawResource: - return ResourceLoadPriorityMedium; + return ResourceLoadPriority::Medium; case CachedResource::ImageResource: - return ResourceLoadPriorityLow; + return ResourceLoadPriority::Low; #if ENABLE(XSLT) case CachedResource::XSLStyleSheet: - return ResourceLoadPriorityHigh; + return ResourceLoadPriority::High; #endif -#if ENABLE(SVG) case CachedResource::SVGDocumentResource: - return ResourceLoadPriorityLow; -#endif + return ResourceLoadPriority::Low; #if ENABLE(LINK_PREFETCH) case CachedResource::LinkPrefetch: - return ResourceLoadPriorityVeryLow; + return ResourceLoadPriority::VeryLow; case CachedResource::LinkSubresource: - return ResourceLoadPriorityVeryLow; + return ResourceLoadPriority::VeryLow; #endif #if ENABLE(VIDEO_TRACK) case CachedResource::TextTrackResource: - return ResourceLoadPriorityLow; + return ResourceLoadPriority::Low; #endif } ASSERT_NOT_REACHED(); - return ResourceLoadPriorityLow; + return ResourceLoadPriority::Low; } -static double deadDecodedDataDeletionIntervalForResourceType(CachedResource::Type type) +static std::chrono::milliseconds deadDecodedDataDeletionIntervalForResourceType(CachedResource::Type type) { if (type == CachedResource::Script) - return 0; - return memoryCache()->deadDecodedDataDeletionInterval(); + return std::chrono::milliseconds { 0 }; + + return MemoryCache::singleton().deadDecodedDataDeletionInterval(); } DEFINE_DEBUG_ONLY_GLOBAL(RefCountedLeakCounter, cachedResourceLeakCounter, ("CachedResource")); -CachedResource::CachedResource(const ResourceRequest& request, Type type) - : m_resourceRequest(request) +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(currentTime()) - , m_decodedDataDeletionTimer(this, &CachedResource::decodedDataDeletionTimerFired, deadDecodedDataDeletionIntervalForResourceType(type)) - , m_lastDecodedAccessTime(0) - , m_loadFinishTime(0) - , m_encodedSize(0) - , m_decodedSize(0) - , m_accessCount(0) - , m_handleCount(0) - , m_preloadCount(0) - , m_preloadResult(PreloadNotReferenced) - , m_inLiveDecodedResourcesList(false) - , m_requestedFromNetworkingLayer(false) - , m_inCache(false) - , m_loading(false) - , m_switchingClientsToRevalidatedResource(false) + , 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) - , m_status(Pending) +{ + ASSERT(sessionID.isValid()); + + setLoadPriority(request.priority()); #ifndef NDEBUG - , m_deleted(false) - , m_lruIndex(0) + cachedResourceLeakCounter.increment(); #endif - , m_nextInAllResourcesList(0) - , m_prevInAllResourcesList(0) - , m_nextInLiveResourcesList(0) - , m_prevInLiveResourcesList(0) - , m_owningCachedResourceLoader(0) - , m_resourceToRevalidate(0) - , m_proxyResource(0) -{ - ASSERT(m_type == unsigned(type)); // m_type is a bitfield, so this tests careless updates of the enum. + + // 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 - - if (!m_resourceRequest.url().hasFragmentIdentifier()) - return; - URL urlForCache = MemoryCache::removeFragmentIdentifierIfNeeded(m_resourceRequest.url()); - if (urlForCache.hasFragmentIdentifier()) - return; - m_fragmentIdentifierForRequest = m_resourceRequest.url().fragmentIdentifier(); - m_resourceRequest.setURL(urlForCache); } CachedResource::~CachedResource() @@ -201,7 +161,7 @@ CachedResource::~CachedResource() ASSERT(canDelete()); ASSERT(!inCache()); ASSERT(!m_deleted); - ASSERT(url().isNull() || memoryCache()->resourceForRequest(resourceRequest()) != this); + ASSERT(url().isNull() || !allowsCaching() || MemoryCache::singleton().resourceForRequest(resourceRequest(), sessionID()) != this); #ifndef NDEBUG m_deleted = true; @@ -209,99 +169,81 @@ CachedResource::~CachedResource() #endif if (m_owningCachedResourceLoader) - m_owningCachedResourceLoader->removeCachedResource(this); + 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 (m_resourceToRevalidate) - memoryCache()->revalidationFailed(this); + if (allowsCaching() && m_resourceToRevalidate) + MemoryCache::singleton().revalidationFailed(*this); error(CachedResource::LoadError); } -void CachedResource::addAdditionalRequestHeaders(CachedResourceLoader* cachedResourceLoader) +void CachedResource::load(CachedResourceLoader& cachedResourceLoader) { - // Note: We skip the Content-Security-Policy check here because we check - // the Content-Security-Policy at the CachedResourceLoader layer so we can - // handle different resource types differently. - - FrameLoader& frameLoader = cachedResourceLoader->frame()->loader(); - String outgoingReferrer; - String outgoingOrigin; - if (m_resourceRequest.httpReferrer().isNull()) { - outgoingReferrer = frameLoader.outgoingReferrer(); - outgoingOrigin = frameLoader.outgoingOrigin(); - } else { - outgoingReferrer = m_resourceRequest.httpReferrer(); - outgoingOrigin = SecurityOrigin::createFromString(outgoingReferrer)->toString(); - } - - outgoingReferrer = SecurityPolicy::generateReferrerHeader(cachedResourceLoader->document()->referrerPolicy(), m_resourceRequest.url(), outgoingReferrer); - if (outgoingReferrer.isEmpty()) - m_resourceRequest.clearHTTPReferrer(); - else if (!m_resourceRequest.httpReferrer()) - m_resourceRequest.setHTTPReferrer(outgoingReferrer); - FrameLoader::addHTTPOriginIfNeeded(m_resourceRequest, outgoingOrigin); - - frameLoader.addExtraFieldsToSubresourceRequest(m_resourceRequest); -} - -void CachedResource::load(CachedResourceLoader* cachedResourceLoader, const ResourceLoaderOptions& options) -{ - if (!cachedResourceLoader->frame()) { + 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 = cachedResourceLoader->frame()->loader(); - if (options.securityCheck == DoSecurityCheck && (frameLoader.state() == FrameStateProvisional || !frameLoader.activeDocumentLoader() || frameLoader.activeDocumentLoader()->isStopping())) { + 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_options = options; m_loading = true; -#if USE(QUICK_LOOK) - if (!m_resourceRequest.isNull() && m_resourceRequest.url().protocolIs(QLPreviewProtocol())) { - // When QuickLook is invoked to convert a document, it returns a unique URL in the - // NSURLReponse for the main document. To make safeQLURLForDocumentURLAndResourceURL() - // work, we need to use the QL URL not the original URL. - const URL& documentURL = cachedResourceLoader->frame() ? cachedResourceLoader->frame()->loader().documentLoader()->response().url() : cachedResourceLoader->document()->url(); - m_resourceRequest.setURL(safeQLURLForDocumentURLAndResourceURL(documentURL, url())); - } -#endif - - if (!accept().isEmpty()) - m_resourceRequest.setHTTPAccept(accept()); - if (isCacheValidator()) { CachedResource* resourceToRevalidate = m_resourceToRevalidate; ASSERT(resourceToRevalidate->canUseCacheValidator()); ASSERT(resourceToRevalidate->isLoaded()); - const String& lastModified = resourceToRevalidate->response().httpHeaderField("Last-Modified"); - const String& eTag = resourceToRevalidate->response().httpHeaderField("ETag"); + 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("Cache-Control", "max-age=0"); + ASSERT(cachedResourceLoader.cachePolicy(type()) != CachePolicyReload); + if (cachedResourceLoader.cachePolicy(type()) == CachePolicyRevalidate) + m_resourceRequest.setHTTPHeaderField(HTTPHeaderName::CacheControl, "max-age=0"); if (!lastModified.isEmpty()) - m_resourceRequest.setHTTPHeaderField("If-Modified-Since", lastModified); + m_resourceRequest.setHTTPHeaderField(HTTPHeaderName::IfModifiedSince, lastModified); if (!eTag.isEmpty()) - m_resourceRequest.setHTTPHeaderField("If-None-Match", eTag); + m_resourceRequest.setHTTPHeaderField(HTTPHeaderName::IfNoneMatch, eTag); } } #if ENABLE(LINK_PREFETCH) if (type() == CachedResource::LinkPrefetch || type() == CachedResource::LinkSubresource) - m_resourceRequest.setHTTPHeaderField("Purpose", "prefetch"); + m_resourceRequest.setHTTPHeaderField(HTTPHeaderName::Purpose, "prefetch"); #endif m_resourceRequest.setPriority(loadPriority()); - if (type() != MainResource) - addAdditionalRequestHeaders(cachedResourceLoader); + // 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. @@ -313,8 +255,9 @@ void CachedResource::load(CachedResourceLoader* cachedResourceLoader, const Reso m_fragmentIdentifierForRequest = String(); } - m_loader = platformStrategies()->loaderStrategy()->resourceLoadScheduler()->scheduleSubresourceLoad(cachedResourceLoader->frame(), this, request, request.priority(), options); + 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; } @@ -322,27 +265,55 @@ void CachedResource::load(CachedResourceLoader* cachedResourceLoader, const Reso 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()) + if (isLoading() || stillNeedsLoad()) return; - CachedResourceClientWalker<CachedResourceClient> w(m_clients); - while (CachedResourceClient* c = w.next()) - c->notifyFinished(this); + CachedResourceClientWalker<CachedResourceClient> walker(m_clients); + while (CachedResourceClient* client = walker.next()) + client->notifyFinished(*this); } -void CachedResource::addDataBuffer(ResourceBuffer*) +void CachedResource::addDataBuffer(SharedBuffer&) { - ASSERT(m_options.dataBufferingPolicy == BufferData); + ASSERT(dataBufferingPolicy() == BufferData); } void CachedResource::addData(const char*, unsigned) { - ASSERT(m_options.dataBufferingPolicy == DoNotBufferData); + ASSERT(dataBufferingPolicy() == DoNotBufferData); } -void CachedResource::finishLoading(ResourceBuffer*) +void CachedResource::finishLoading(SharedBuffer*) { setLoading(false); checkNotify(); @@ -352,7 +323,7 @@ void CachedResource::error(CachedResource::Status status) { setStatus(status); ASSERT(errorOccurred()); - m_data.clear(); + m_data = nullptr; setLoading(false); checkNotify(); @@ -360,7 +331,7 @@ void CachedResource::error(CachedResource::Status status) void CachedResource::cancelLoad() { - if (!isLoading()) + if (!isLoading() && !stillNeedsLoad()) return; setStatus(LoadError); @@ -374,10 +345,30 @@ void CachedResource::finish() m_status = Cached; } -bool CachedResource::passesAccessControlCheck(SecurityOrigin* securityOrigin) +void CachedResource::setCrossOrigin() { - String errorDescription; - return WebCore::passesAccessControlCheck(m_response, resourceRequest().allowCookies() ? AllowStoredCredentials : DoNotAllowStoredCredentials, securityOrigin, errorDescription); + 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 @@ -385,53 +376,62 @@ bool CachedResource::isExpired() const if (m_response.isNull()) return false; - return currentAge() > freshnessLifetime(); + return computeCurrentAge(m_response, m_responseTimestamp) > freshnessLifetime(m_response); } -double CachedResource::currentAge() const +static inline bool shouldCacheSchemeIndefinitely(StringView scheme) { - // RFC2616 13.2.3 - // No compensation for latency as that is not terribly important in practice - double dateValue = m_response.date(); - double apparentAge = std::isfinite(dateValue) ? std::max(0., m_responseTimestamp - dateValue) : 0; - double ageValue = m_response.age(); - double correctedReceivedAge = std::isfinite(ageValue) ? std::max(apparentAge, ageValue) : apparentAge; - double residentTime = currentTime() - m_responseTimestamp; - return correctedReceivedAge + residentTime; +#if PLATFORM(COCOA) + if (equalLettersIgnoringASCIICase(scheme, "applewebdata")) + return true; +#endif +#if USE(SOUP) + if (equalLettersIgnoringASCIICase(scheme, "resource")) + return true; +#endif + return equalLettersIgnoringASCIICase(scheme, "data"); } -double CachedResource::freshnessLifetime() const +std::chrono::microseconds CachedResource::freshnessLifetime(const ResourceResponse& response) const { - if (!m_response.url().protocolIsInHTTPFamily()) { - // 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::shouldCacheResponsesFromURLSchemeIndefinitely(m_response.url().protocol())) - return 0; + 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::numeric_limits<double>::max(); + return std::chrono::microseconds::max(); } - // RFC2616 13.2.4 - double maxAgeValue = m_response.cacheControlMaxAge(); - if (std::isfinite(maxAgeValue)) - return maxAgeValue; - double expiresValue = m_response.expires(); - double dateValue = m_response.date(); - double creationTime = std::isfinite(dateValue) ? dateValue : m_responseTimestamp; - if (std::isfinite(expiresValue)) - return expiresValue - creationTime; - double lastModifiedValue = m_response.lastModified(); - if (std::isfinite(lastModifiedValue)) - return (creationTime - lastModifiedValue) * 0.1; - // If no cache headers are present, the specification leaves the decision to the UA. Other browsers seem to opt for 0. - return 0; + 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 = currentTime(); + m_responseTimestamp = std::chrono::system_clock::now(); String encoding = response.textEncodingName(); if (!encoding.isNull()) setEncoding(encoding); @@ -440,32 +440,30 @@ void CachedResource::responseReceived(const ResourceResponse& response) void CachedResource::clearLoader() { ASSERT(m_loader); - m_loader = 0; + m_identifierForLoadWithoutResourceLoader = m_loader->identifier(); + m_loader = nullptr; + deleteIfPossible(); } -void CachedResource::addClient(CachedResourceClient* client) +void CachedResource::addClient(CachedResourceClient& client) { if (addClientToSet(client)) didAddClient(client); } -void CachedResource::didAddClient(CachedResourceClient* c) +void CachedResource::didAddClient(CachedResourceClient& client) { if (m_decodedDataDeletionTimer.isActive()) m_decodedDataDeletionTimer.stop(); - if (m_clientsAwaitingCallback.contains(c)) { - m_clients.add(c); - m_clientsAwaitingCallback.remove(c); - } + if (m_clientsAwaitingCallback.remove(&client)) + m_clients.add(&client); if (!isLoading() && !stillNeedsLoad()) - c->notifyFinished(this); + client.notifyFinished(*this); } -bool CachedResource::addClientToSet(CachedResourceClient* client) +bool CachedResource::addClientToSet(CachedResourceClient& client) { - ASSERT(!isPurgeable()); - if (m_preloadResult == PreloadNotReferenced) { if (isLoaded()) m_preloadResult = PreloadReferencedWhileComplete; @@ -474,78 +472,90 @@ bool CachedResource::addClientToSet(CachedResourceClient* client) else m_preloadResult = PreloadReferenced; } - if (!hasClients() && inCache()) - memoryCache()->addToLiveResourcesSize(this); + 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, CachedResourceCallback::schedule(this, client)); + ASSERT(!m_clientsAwaitingCallback.contains(&client)); + m_clientsAwaitingCallback.add(&client, std::make_unique<Callback>(*this, client)); return false; } - m_clients.add(client); + m_clients.add(&client); return true; } -void CachedResource::removeClient(CachedResourceClient* client) +void CachedResource::removeClient(CachedResourceClient& client) { - OwnPtr<CachedResourceCallback> callback = m_clientsAwaitingCallback.take(client); + auto callback = m_clientsAwaitingCallback.take(&client); if (callback) { - ASSERT(!m_clients.contains(client)); + ASSERT(!m_clients.contains(&client)); callback->cancel(); - callback.clear(); + callback = nullptr; } else { - ASSERT(m_clients.contains(client)); - m_clients.remove(client); + ASSERT(m_clients.contains(&client)); + m_clients.remove(&client); didRemoveClient(client); } - bool deleted = deleteIfPossible(); - if (!deleted && !hasClients()) { - if (inCache()) { - memoryCache()->removeFromLiveResourcesSize(this); - memoryCache()->removeFromLiveDecodedResourcesList(this); - } - if (!m_switchingClientsToRevalidatedResource) - allClientsRemoved(); - destroyDecodedDataIfNeeded(); - if (response().cacheControlContainsNoStore()) { - // 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. - if (url().protocolIs("https")) - memoryCache()->remove(this); - } else - memoryCache()->prune(); + if (deleteIfPossible()) { + // `this` object is dead here. + return; } - // This object may be dead here. + + 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()->deadDecodedDataDeletionInterval()) + if (!MemoryCache::singleton().deadDecodedDataDeletionInterval().count()) return; m_decodedDataDeletionTimer.restart(); } -void CachedResource::decodedDataDeletionTimerFired(DeferrableOneShotTimer<CachedResource>&) +void CachedResource::decodedDataDeletionTimerFired() { destroyDecodedData(); } bool CachedResource::deleteIfPossible() { - if (canDelete() && !inCache()) { - InspectorInstrumentation::willDestroyCachedResource(this); - delete this; - return true; + if (canDelete()) { + if (!inCache()) { + InspectorInstrumentation::willDestroyCachedResource(*this); + delete this; + return true; + } + if (m_data) + m_data->hintMemoryNotNeededSoon(); } return false; } @@ -555,19 +565,19 @@ void CachedResource::setDecodedSize(unsigned size) if (size == m_decodedSize) return; - int delta = size - m_decodedSize; + long long delta = static_cast<long long>(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); - // The object must now be moved to a different queue, since its size has been changed. - // We have to remove explicitly before updating m_decodedSize, so that we find the correct previous - // queue. - if (inCache()) - memoryCache()->removeFromLRUList(this); - m_decodedSize = size; - if (inCache()) { + if (allowsCaching() && inCache()) { + auto& memoryCache = MemoryCache::singleton(); // Now insert into the new LRU list. - memoryCache()->insertInLRUList(this); + memoryCache.insertInLRUList(*this); // Insert into or remove from the live decoded list if necessary. // When inserting into the LiveDecodedResourcesList it is possible @@ -576,13 +586,14 @@ void CachedResource::setDecodedSize(unsigned size) // 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 - if (m_decodedSize && !m_inLiveDecodedResourcesList && hasClients()) - memoryCache()->insertInLiveDecodedResourcesList(this); - else if (!m_decodedSize && m_inLiveDecodedResourcesList) - memoryCache()->removeFromLiveDecodedResourcesList(this); + 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); + memoryCache.adjustSize(hasClients(), delta); } } @@ -591,25 +602,19 @@ void CachedResource::setEncodedSize(unsigned size) if (size == m_encodedSize) return; - // The size cannot ever shrink (unless it is being nulled out because of an error). If it ever does, assert. - ASSERT(size == 0 || size >= m_encodedSize); - - int delta = size - m_encodedSize; + long long delta = static_cast<long long>(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); - // The object must now be moved to a different queue, since its size has been changed. - // We have to remove explicitly before updating m_encodedSize, so that we find the correct previous - // queue. - if (inCache()) - memoryCache()->removeFromLRUList(this); - m_encodedSize = size; - - if (inCache()) { - // Now insert into the new LRU list. - memoryCache()->insertInLRUList(this); - - // Update the cache's size totals. - memoryCache()->adjustSize(hasClients(), delta); + + if (allowsCaching() && inCache()) { + auto& memoryCache = MemoryCache::singleton(); + memoryCache.insertInLRUList(*this); + memoryCache.adjustSize(hasClients(), delta); } } @@ -617,12 +622,13 @@ void CachedResource::didAccessDecodedData(double timeStamp) { m_lastDecodedAccessTime = timeStamp; - if (inCache()) { - if (m_inLiveDecodedResourcesList) { - memoryCache()->removeFromLiveDecodedResourcesList(this); - memoryCache()->insertInLiveDecodedResourcesList(this); + if (allowsCaching() && inCache()) { + auto& memoryCache = MemoryCache::singleton(); + if (memoryCache.inLiveDecodedResourcesList(*this)) { + memoryCache.removeFromLiveDecodedResourcesList(*this); + memoryCache.insertInLiveDecodedResourcesList(*this); } - memoryCache()->prune(); + memoryCache.pruneSoon(); } } @@ -633,31 +639,27 @@ void CachedResource::setResourceToRevalidate(CachedResource* resource) ASSERT(resource != this); ASSERT(m_handlesToRevalidate.isEmpty()); ASSERT(resource->type() == type()); + ASSERT(!resource->m_proxyResource); LOG(ResourceLoading, "CachedResource %p setResourceToRevalidate %p", this, resource); - // The following assert should be investigated whenever it occurs. Although it should never fire, it currently does in rare circumstances. - // https://bugs.webkit.org/show_bug.cgi?id=28604. - // So the code needs to be robust to this assert failing thus the "if (m_resourceToRevalidate->m_proxyResource == this)" in CachedResource::clearResourceToRevalidate. - ASSERT(!resource->m_proxyResource); - resource->m_proxyResource = this; m_resourceToRevalidate = resource; } void CachedResource::clearResourceToRevalidate() -{ +{ ASSERT(m_resourceToRevalidate); + ASSERT(m_resourceToRevalidate->m_proxyResource == this); + if (m_switchingClientsToRevalidatedResource) return; - // A resource may start revalidation before this method has been called, so check that this resource is still the proxy resource before clearing it out. - if (m_resourceToRevalidate->m_proxyResource == this) { - m_resourceToRevalidate->m_proxyResource = 0; - m_resourceToRevalidate->deleteIfPossible(); - } + m_resourceToRevalidate->m_proxyResource = nullptr; + m_resourceToRevalidate->deleteIfPossible(); + m_handlesToRevalidate.clear(); - m_resourceToRevalidate = 0; + m_resourceToRevalidate = nullptr; deleteIfPossible(); } @@ -670,9 +672,7 @@ void CachedResource::switchClientsToRevalidatedResource() LOG(ResourceLoading, "CachedResource %p switchClientsToRevalidatedResource %p", this, m_resourceToRevalidate); m_switchingClientsToRevalidatedResource = true; - HashSet<CachedResourceHandleBase*>::iterator end = m_handlesToRevalidate.end(); - for (HashSet<CachedResourceHandleBase*>::iterator it = m_handlesToRevalidate.begin(); it != end; ++it) { - CachedResourceHandleBase* handle = *it; + for (auto& handle : m_handlesToRevalidate) { handle->m_resource = m_resourceToRevalidate; m_resourceToRevalidate->registerHandle(handle); --m_handleCount; @@ -681,52 +681,37 @@ void CachedResource::switchClientsToRevalidatedResource() m_handlesToRevalidate.clear(); Vector<CachedResourceClient*> clientsToMove; - HashCountedSet<CachedResourceClient*>::iterator end2 = m_clients.end(); - for (HashCountedSet<CachedResourceClient*>::iterator it = m_clients.begin(); it != end2; ++it) { - CachedResourceClient* client = it->key; - unsigned count = it->value; + for (auto& entry : m_clients) { + CachedResourceClient* client = entry.key; + unsigned count = entry.value; while (count) { clientsToMove.append(client); --count; } } - unsigned moveCount = clientsToMove.size(); - for (unsigned n = 0; n < moveCount; ++n) - removeClient(clientsToMove[n]); + for (auto& client : clientsToMove) + removeClient(*client); ASSERT(m_clients.isEmpty()); - for (unsigned n = 0; n < moveCount; ++n) - m_resourceToRevalidate->addClientToSet(clientsToMove[n]); - for (unsigned n = 0; n < moveCount; ++n) { + 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(clientsToMove[n])) - m_resourceToRevalidate->didAddClient(clientsToMove[n]); + if (m_resourceToRevalidate->m_clients.contains(client)) + m_resourceToRevalidate->didAddClient(*client); } m_switchingClientsToRevalidatedResource = false; } void CachedResource::updateResponseAfterRevalidation(const ResourceResponse& validatingResponse) { - m_responseTimestamp = currentTime(); - - // RFC2616 10.3.5 - // Update cached headers from the 304 response - const HTTPHeaderMap& newHeaders = validatingResponse.httpHeaderFields(); - HTTPHeaderMap::const_iterator end = newHeaders.end(); - for (HTTPHeaderMap::const_iterator it = newHeaders.begin(); it != end; ++it) { - // Entity headers should not be sent by servers when generating a 304 - // response; misconfigured servers send them anyway. We shouldn't allow - // such headers to update the original request. We'll base this on the - // list defined by RFC2616 7.1, with a few additions for extension headers - // we care about. - if (!shouldUpdateHeaderAfterRevalidation(it->key)) - continue; - m_response.setHTTPHeaderField(it->key, it->value); - } + m_responseTimestamp = std::chrono::system_clock::now(); + + updateResponseHeadersAfterRevalidation(m_response, validatingResponse); } void CachedResource::registerHandle(CachedResourceHandleBase* h) @@ -758,95 +743,50 @@ bool CachedResource::canUseCacheValidator() const return m_response.hasCacheValidatorFields(); } -bool CachedResource::mustRevalidateDueToCacheHeaders(CachePolicy cachePolicy) const +CachedResource::RevalidationDecision CachedResource::makeRevalidationDecision(CachePolicy cachePolicy) const { - ASSERT(cachePolicy == CachePolicyRevalidate || cachePolicy == CachePolicyCache || cachePolicy == CachePolicyVerify); - - if (cachePolicy == CachePolicyRevalidate) - return true; - - if (m_response.cacheControlContainsNoCache() || m_response.cacheControlContainsNoStore()) { - LOG(ResourceLoading, "CachedResource %p mustRevalidate because of m_response.cacheControlContainsNoCache() || m_response.cacheControlContainsNoStore()\n", this); - return true; - } - - if (cachePolicy == CachePolicyCache) { - if (m_response.cacheControlContainsMustRevalidate() && isExpired()) { - LOG(ResourceLoading, "CachedResource %p mustRevalidate because of cachePolicy == CachePolicyCache and m_response.cacheControlContainsMustRevalidate() && isExpired()\n", this); - return true; - } - return false; - } - - // CachePolicyVerify - if (isExpired()) { - LOG(ResourceLoading, "CachedResource %p mustRevalidate because of isExpired()\n", this); - return true; - } - - return false; -} - -bool CachedResource::isSafeToMakePurgeable() const -{ -#if ENABLE(DISK_IMAGE_CACHE) - // It does not make sense to have a resource in the disk image cache - // (memory mapped on disk) and purgeable (in memory). So do not allow - // disk image cached resources to be purgeable. - if (isUsingDiskImageCache()) - return false; -#endif - - return !hasClients() && !m_proxyResource && !m_resourceToRevalidate; -} - -bool CachedResource::makePurgeable(bool purgeable) -{ - if (purgeable) { - ASSERT(isSafeToMakePurgeable()); - - if (m_purgeableData) { - ASSERT(!m_data); - return true; + 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; } - if (!m_data) - return false; - - // Should not make buffer purgeable if it has refs other than this since we don't want two copies. - if (!m_data->hasOneRef()) - return false; + return RevalidationDecision::YesDueToCachePolicy; - m_data->createPurgeableBuffer(); - if (!m_data->hasPurgeableBuffer()) - return false; + 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; - m_purgeableData = m_data->releasePurgeableBuffer(); - m_purgeableData->setPurgePriority(purgePriority()); - m_purgeableData->makePurgeable(true); - m_data.clear(); - return true; - } - - if (!m_purgeableData) - return true; - ASSERT(!m_data); - ASSERT(!hasClients()); + if (isExpired()) + return RevalidationDecision::YesDueToExpired; - if (!m_purgeableData->makePurgeable(false)) - return false; - - m_data = ResourceBuffer::adoptSharedBuffer(SharedBuffer::adoptPurgeableBuffer(m_purgeableData.release())); - return true; + return RevalidationDecision::No; + }; + ASSERT_NOT_REACHED(); + return RevalidationDecision::No; } -bool CachedResource::isPurgeable() const +bool CachedResource::redirectChainAllowsReuse(ReuseExpiredRedirectionOrNot reuseExpiredRedirection) const { - return m_purgeableData && m_purgeableData->isPurgeable(); + return WebCore::redirectChainAllowsReuse(m_redirectChainCacheStatus, reuseExpiredRedirection); } -bool CachedResource::wasPurged() const +bool CachedResource::varyHeaderValuesMatch(const ResourceRequest& request) { - return m_purgeableData && m_purgeableData->wasPurged(); + if (m_varyingHeaderValues.isEmpty()) + return true; + + return verifyVaryingRequestHeaders(m_varyingHeaderValues, request, m_sessionID); } unsigned CachedResource::overheadSize() const @@ -855,59 +795,65 @@ unsigned CachedResource::overheadSize() const return sizeof(CachedResource) + m_response.memoryUsage() + kAverageClientsHashMapSize + m_resourceRequest.url().string().length() * 2; } -void CachedResource::setLoadPriority(ResourceLoadPriority loadPriority) +bool CachedResource::areAllClientsXMLHttpRequests() const { - if (loadPriority == ResourceLoadPriorityUnresolved) - loadPriority = defaultPriorityForResourceType(type()); - if (loadPriority == m_loadPriority) - return; - m_loadPriority = loadPriority; - if (m_loader) - m_loader->didChangePriority(loadPriority); + if (type() != RawResource) + return false; + + for (auto& client : m_clients) { + if (!client.key->isXMLHttpRequest()) + return false; + } + return true; } -CachedResource::CachedResourceCallback::CachedResourceCallback(CachedResource* resource, CachedResourceClient* client) - : m_resource(resource) - , m_client(client) - , m_callbackTimer(this, &CachedResourceCallback::timerFired) +void CachedResource::setLoadPriority(const std::optional<ResourceLoadPriority>& loadPriority) { - m_callbackTimer.startOneShot(0); + if (loadPriority) + m_loadPriority = loadPriority.value(); + else + m_loadPriority = defaultPriorityForResourceType(type()); } -void CachedResource::CachedResourceCallback::cancel() +inline CachedResource::Callback::Callback(CachedResource& resource, CachedResourceClient& client) + : m_resource(resource) + , m_client(client) + , m_timer(*this, &Callback::timerFired) { - if (m_callbackTimer.isActive()) - m_callbackTimer.stop(); + m_timer.startOneShot(0); } -void CachedResource::CachedResourceCallback::timerFired(Timer<CachedResourceCallback>&) +inline void CachedResource::Callback::cancel() { - m_resource->didAddClient(m_client); + if (m_timer.isActive()) + m_timer.stop(); } -#if ENABLE(DISK_IMAGE_CACHE) -bool CachedResource::isUsingDiskImageCache() const +void CachedResource::Callback::timerFired() { - return m_data && m_data->isUsingDiskImageCache(); + m_resource.didAddClient(m_client); } -#endif -#if PLATFORM(MAC) -void CachedResource::tryReplaceEncodedData(PassRefPtr<SharedBuffer> newBuffer) +#if USE(FOUNDATION) || USE(SOUP) + +void CachedResource::tryReplaceEncodedData(SharedBuffer& newBuffer) { if (!m_data) return; if (!mayTryReplaceEncodedData()) return; - - // Because the disk cache is asynchronous and racey with regards to the data we might be asked to replace, - // we need to verify that the new buffer has the same contents as our old buffer. - if (m_data->size() != newBuffer->size() || memcmp(m_data->data(), newBuffer->data(), m_data->size())) + + // 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; - m_data->tryReplaceSharedBufferContents(newBuffer.get()); + if (m_data->tryReplaceContentsWithPlatformBuffer(newBuffer)) + didReplaceSharedBufferContents(); } + #endif } |