blob: f5bdcbe66b3c826a883ffce249cf29eace4f06a2 [file] [log] [blame]
/*
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, 2005, 2006, 2007, 2008, 2009, 2010, 2011 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 "platform/loader/fetch/Resource.h"
#include "platform/Histogram.h"
#include "platform/InstanceCounters.h"
#include "platform/RuntimeEnabledFeatures.h"
#include "platform/SharedBuffer.h"
#include "platform/WebTaskRunner.h"
#include "platform/instrumentation/tracing/TraceEvent.h"
#include "platform/loader/fetch/CachedMetadata.h"
#include "platform/loader/fetch/CrossOriginAccessControl.h"
#include "platform/loader/fetch/FetchInitiatorTypeNames.h"
#include "platform/loader/fetch/FetchRequest.h"
#include "platform/loader/fetch/IntegrityMetadata.h"
#include "platform/loader/fetch/MemoryCache.h"
#include "platform/loader/fetch/ResourceClient.h"
#include "platform/loader/fetch/ResourceClientWalker.h"
#include "platform/loader/fetch/ResourceLoader.h"
#include "platform/network/HTTPParsers.h"
#include "platform/weborigin/KURL.h"
#include "public/platform/Platform.h"
#include "public/platform/WebCachePolicy.h"
#include "public/platform/WebScheduler.h"
#include "public/platform/WebSecurityOrigin.h"
#include "wtf/CurrentTime.h"
#include "wtf/MathExtras.h"
#include "wtf/StdLibExtras.h"
#include "wtf/Vector.h"
#include "wtf/text/CString.h"
#include "wtf/text/StringBuilder.h"
#include <algorithm>
#include <cassert>
#include <memory>
#include <stdint.h>
namespace blink {
// 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],
TextCaseASCIIInsensitive))
return false;
}
return true;
}
class Resource::CachedMetadataHandlerImpl : public CachedMetadataHandler {
public:
static Resource::CachedMetadataHandlerImpl* create(Resource* resource) {
return new CachedMetadataHandlerImpl(resource);
}
~CachedMetadataHandlerImpl() override {}
DECLARE_VIRTUAL_TRACE();
void setCachedMetadata(uint32_t, const char*, size_t, CacheType) override;
void clearCachedMetadata(CacheType) override;
PassRefPtr<CachedMetadata> cachedMetadata(uint32_t) const override;
String encoding() const override;
// Sets the serialized metadata retrieved from the platform's cache.
void setSerializedCachedMetadata(const char*, size_t);
protected:
explicit CachedMetadataHandlerImpl(Resource*);
virtual void sendToPlatform();
const ResourceResponse& response() const { return m_resource->response(); }
RefPtr<CachedMetadata> m_cachedMetadata;
private:
Member<Resource> m_resource;
};
Resource::CachedMetadataHandlerImpl::CachedMetadataHandlerImpl(
Resource* resource)
: m_resource(resource) {}
DEFINE_TRACE(Resource::CachedMetadataHandlerImpl) {
visitor->trace(m_resource);
CachedMetadataHandler::trace(visitor);
}
void Resource::CachedMetadataHandlerImpl::setCachedMetadata(
uint32_t dataTypeID,
const char* data,
size_t size,
CachedMetadataHandler::CacheType cacheType) {
// Currently, only one type of cached metadata per resource is supported. If
// the need arises for multiple types of metadata per resource this could be
// enhanced to store types of metadata in a map.
DCHECK(!m_cachedMetadata);
m_cachedMetadata = CachedMetadata::create(dataTypeID, data, size);
if (cacheType == CachedMetadataHandler::SendToPlatform)
sendToPlatform();
}
void Resource::CachedMetadataHandlerImpl::clearCachedMetadata(
CachedMetadataHandler::CacheType cacheType) {
m_cachedMetadata.clear();
if (cacheType == CachedMetadataHandler::SendToPlatform)
sendToPlatform();
}
PassRefPtr<CachedMetadata> Resource::CachedMetadataHandlerImpl::cachedMetadata(
uint32_t dataTypeID) const {
if (!m_cachedMetadata || m_cachedMetadata->dataTypeID() != dataTypeID)
return nullptr;
return m_cachedMetadata.get();
}
String Resource::CachedMetadataHandlerImpl::encoding() const {
return m_resource->encoding();
}
void Resource::CachedMetadataHandlerImpl::setSerializedCachedMetadata(
const char* data,
size_t size) {
// We only expect to receive cached metadata from the platform once. If this
// triggers, it indicates an efficiency problem which is most likely
// unexpected in code designed to improve performance.
DCHECK(!m_cachedMetadata);
m_cachedMetadata = CachedMetadata::createFromSerializedData(data, size);
}
void Resource::CachedMetadataHandlerImpl::sendToPlatform() {
if (m_cachedMetadata) {
const Vector<char>& serializedData = m_cachedMetadata->serializedData();
Platform::current()->cacheMetadata(
response().url(), response().responseTime(), serializedData.data(),
serializedData.size());
} else {
Platform::current()->cacheMetadata(response().url(),
response().responseTime(), nullptr, 0);
}
}
class Resource::ServiceWorkerResponseCachedMetadataHandler
: public Resource::CachedMetadataHandlerImpl {
public:
static Resource::CachedMetadataHandlerImpl* create(
Resource* resource,
SecurityOrigin* securityOrigin) {
return new ServiceWorkerResponseCachedMetadataHandler(resource,
securityOrigin);
}
~ServiceWorkerResponseCachedMetadataHandler() override {}
DECLARE_VIRTUAL_TRACE();
protected:
void sendToPlatform() override;
private:
explicit ServiceWorkerResponseCachedMetadataHandler(Resource*,
SecurityOrigin*);
String m_cacheStorageCacheName;
RefPtr<SecurityOrigin> m_securityOrigin;
};
Resource::ServiceWorkerResponseCachedMetadataHandler::
ServiceWorkerResponseCachedMetadataHandler(Resource* resource,
SecurityOrigin* securityOrigin)
: CachedMetadataHandlerImpl(resource), m_securityOrigin(securityOrigin) {}
DEFINE_TRACE(Resource::ServiceWorkerResponseCachedMetadataHandler) {
CachedMetadataHandlerImpl::trace(visitor);
}
void Resource::ServiceWorkerResponseCachedMetadataHandler::sendToPlatform() {
// We don't support sending the metadata to the platform when the response was
// directly fetched via a ServiceWorker (eg:
// FetchEvent.respondWith(fetch(FetchEvent.request))) to prevent an attacker's
// Service Worker from poisoning the metadata cache of HTTPCache.
if (response().cacheStorageCacheName().isNull())
return;
if (m_cachedMetadata) {
const Vector<char>& serializedData = m_cachedMetadata->serializedData();
Platform::current()->cacheMetadataInCacheStorage(
response().url(), response().responseTime(), serializedData.data(),
serializedData.size(), WebSecurityOrigin(m_securityOrigin),
response().cacheStorageCacheName());
} else {
Platform::current()->cacheMetadataInCacheStorage(
response().url(), response().responseTime(), nullptr, 0,
WebSecurityOrigin(m_securityOrigin),
response().cacheStorageCacheName());
}
}
// This class cannot be on-heap because the first callbackHandler() call
// instantiates the singleton object while we can call it in the
// pre-finalization step.
class Resource::ResourceCallback final {
public:
static ResourceCallback& callbackHandler();
void schedule(Resource*);
void cancel(Resource*);
bool isScheduled(Resource*) const;
private:
ResourceCallback();
void runTask();
TaskHandle m_taskHandle;
HashSet<Persistent<Resource>> m_resourcesWithPendingClients;
};
Resource::ResourceCallback& Resource::ResourceCallback::callbackHandler() {
DEFINE_STATIC_LOCAL(ResourceCallback, callbackHandler, ());
return callbackHandler;
}
Resource::ResourceCallback::ResourceCallback() {}
void Resource::ResourceCallback::schedule(Resource* resource) {
if (!m_taskHandle.isActive()) {
// WTF::unretained(this) is safe because a posted task is canceled when
// |m_taskHandle| is destroyed on the dtor of this ResourceCallback.
m_taskHandle =
Platform::current()
->currentThread()
->scheduler()
->loadingTaskRunner()
->postCancellableTask(
BLINK_FROM_HERE,
WTF::bind(&ResourceCallback::runTask, WTF::unretained(this)));
}
m_resourcesWithPendingClients.insert(resource);
}
void Resource::ResourceCallback::cancel(Resource* resource) {
m_resourcesWithPendingClients.erase(resource);
if (m_taskHandle.isActive() && m_resourcesWithPendingClients.isEmpty())
m_taskHandle.cancel();
}
bool Resource::ResourceCallback::isScheduled(Resource* resource) const {
return m_resourcesWithPendingClients.contains(resource);
}
void Resource::ResourceCallback::runTask() {
HeapVector<Member<Resource>> resources;
for (const Member<Resource>& resource : m_resourcesWithPendingClients)
resources.push_back(resource.get());
m_resourcesWithPendingClients.clear();
for (const auto& resource : resources)
resource->finishPendingClients();
}
Resource::Resource(const ResourceRequest& request,
Type type,
const ResourceLoaderOptions& options)
: m_loadFinishTime(0),
m_identifier(0),
m_encodedSize(0),
m_encodedSizeMemoryUsage(0),
m_decodedSize(0),
m_overheadSize(calculateOverheadSize()),
m_preloadCount(0),
m_preloadDiscoveryTime(0.0),
m_cacheIdentifier(MemoryCache::defaultCacheIdentifier()),
m_preloadResult(PreloadNotReferenced),
m_type(type),
m_status(ResourceStatus::NotStarted),
m_needsSynchronousCacheHit(false),
m_linkPreload(false),
m_isRevalidating(false),
m_isAlive(false),
m_isAddRemoveClientProhibited(false),
m_integrityDisposition(ResourceIntegrityDisposition::NotChecked),
m_options(options),
m_responseTimestamp(currentTime()),
m_cancelTimer(Platform::current()->mainThread()->getWebTaskRunner(),
this,
&Resource::cancelTimerFired),
m_resourceRequest(request) {
InstanceCounters::incrementCounter(InstanceCounters::ResourceCounter);
// Currently we support the metadata caching only for HTTP family.
if (resourceRequest().url().protocolIsInHTTPFamily())
m_cacheHandler = CachedMetadataHandlerImpl::create(this);
MemoryCoordinator::instance().registerClient(this);
}
Resource::~Resource() {
InstanceCounters::decrementCounter(InstanceCounters::ResourceCounter);
}
DEFINE_TRACE(Resource) {
visitor->trace(m_loader);
visitor->trace(m_cacheHandler);
visitor->trace(m_clients);
visitor->trace(m_clientsAwaitingCallback);
visitor->trace(m_finishedClients);
MemoryCoordinatorClient::trace(visitor);
}
void Resource::setLoader(ResourceLoader* loader) {
CHECK(!m_loader);
DCHECK(stillNeedsLoad());
m_loader = loader;
m_status = ResourceStatus::Pending;
}
void Resource::checkNotify() {
if (isLoading())
return;
ResourceClientWalker<ResourceClient> w(m_clients);
while (ResourceClient* c = w.next()) {
markClientFinished(c);
c->notifyFinished(this);
}
}
void Resource::markClientFinished(ResourceClient* client) {
if (m_clients.contains(client)) {
m_finishedClients.insert(client);
m_clients.erase(client);
}
}
void Resource::appendData(const char* data, size_t length) {
TRACE_EVENT0("blink", "Resource::appendData");
DCHECK(!m_isRevalidating);
DCHECK(!errorOccurred());
if (m_options.dataBufferingPolicy == DoNotBufferData)
return;
if (m_data)
m_data->append(data, length);
else
m_data = SharedBuffer::create(data, length);
setEncodedSize(m_data->size());
}
void Resource::setResourceBuffer(PassRefPtr<SharedBuffer> resourceBuffer) {
DCHECK(!m_isRevalidating);
DCHECK(!errorOccurred());
DCHECK_EQ(m_options.dataBufferingPolicy, BufferData);
m_data = std::move(resourceBuffer);
setEncodedSize(m_data->size());
}
void Resource::clearData() {
m_data.clear();
m_encodedSizeMemoryUsage = 0;
}
void Resource::setDataBufferingPolicy(DataBufferingPolicy dataBufferingPolicy) {
m_options.dataBufferingPolicy = dataBufferingPolicy;
clearData();
setEncodedSize(0);
}
void Resource::error(const ResourceError& error) {
DCHECK(!error.isNull());
m_error = error;
m_isRevalidating = false;
if (m_error.isCancellation() || !isPreloaded())
memoryCache()->remove(this);
if (!errorOccurred())
setStatus(ResourceStatus::LoadError);
DCHECK(errorOccurred());
clearData();
m_loader = nullptr;
checkNotify();
}
void Resource::finish(double loadFinishTime) {
DCHECK(!m_isRevalidating);
m_loadFinishTime = loadFinishTime;
if (!errorOccurred())
m_status = ResourceStatus::Cached;
m_loader = nullptr;
checkNotify();
}
AtomicString Resource::httpContentType() const {
return response().httpContentType();
}
bool Resource::passesAccessControlCheck(
const SecurityOrigin* securityOrigin) const {
StoredCredentials storedCredentials =
lastResourceRequest().allowStoredCredentials()
? AllowStoredCredentials
: DoNotAllowStoredCredentials;
CrossOriginAccessControl::AccessStatus status =
CrossOriginAccessControl::checkAccess(response(), storedCredentials,
securityOrigin);
return status == CrossOriginAccessControl::kAccessAllowed;
}
bool Resource::isEligibleForIntegrityCheck(
SecurityOrigin* securityOrigin) const {
return securityOrigin->canRequest(resourceRequest().url()) ||
passesAccessControlCheck(securityOrigin);
}
void Resource::setIntegrityDisposition(
ResourceIntegrityDisposition disposition) {
DCHECK_NE(disposition, ResourceIntegrityDisposition::NotChecked);
DCHECK(m_type == Resource::Script || m_type == Resource::CSSStyleSheet);
m_integrityDisposition = disposition;
}
bool Resource::mustRefetchDueToIntegrityMetadata(
const FetchRequest& request) const {
if (request.integrityMetadata().isEmpty())
return false;
return !IntegrityMetadata::setsEqual(m_integrityMetadata,
request.integrityMetadata());
}
static double currentAge(const ResourceResponse& response,
double responseTimestamp) {
// RFC2616 13.2.3
// No compensation for latency as that is not terribly important in practice
double dateValue = response.date();
double apparentAge = std::isfinite(dateValue)
? std::max(0., responseTimestamp - dateValue)
: 0;
double ageValue = response.age();
double correctedReceivedAge =
std::isfinite(ageValue) ? std::max(apparentAge, ageValue) : apparentAge;
double residentTime = currentTime() - responseTimestamp;
return correctedReceivedAge + residentTime;
}
double Resource::currentAge() const {
return blink::currentAge(response(), m_responseTimestamp);
}
static double freshnessLifetime(const ResourceResponse& response,
double responseTimestamp) {
#if !OS(ANDROID)
// On desktop, local files should be reloaded in case they change.
if (response.url().isLocalFile())
return 0;
#endif
// Cache other non-http / non-filesystem resources liberally.
if (!response.url().protocolIsInHTTPFamily() &&
!response.url().protocolIs("filesystem"))
return std::numeric_limits<double>::max();
// RFC2616 13.2.4
double maxAgeValue = response.cacheControlMaxAge();
if (std::isfinite(maxAgeValue))
return maxAgeValue;
double expiresValue = response.expires();
double dateValue = response.date();
double creationTime =
std::isfinite(dateValue) ? dateValue : responseTimestamp;
if (std::isfinite(expiresValue))
return expiresValue - creationTime;
double lastModifiedValue = 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;
}
double Resource::freshnessLifetime() const {
return blink::freshnessLifetime(response(), m_responseTimestamp);
}
static bool canUseResponse(const ResourceResponse& response,
double responseTimestamp) {
if (response.isNull())
return false;
// FIXME: Why isn't must-revalidate considered a reason we can't use the
// response?
if (response.cacheControlContainsNoCache() ||
response.cacheControlContainsNoStore())
return false;
if (response.httpStatusCode() == 303) {
// Must not be cached.
return false;
}
if (response.httpStatusCode() == 302 || response.httpStatusCode() == 307) {
// Default to not cacheable unless explicitly allowed.
bool hasMaxAge = std::isfinite(response.cacheControlMaxAge());
bool hasExpires = std::isfinite(response.expires());
// TODO: consider catching Cache-Control "private" and "public" here.
if (!hasMaxAge && !hasExpires)
return false;
}
return currentAge(response, responseTimestamp) <=
freshnessLifetime(response, responseTimestamp);
}
const ResourceRequest& Resource::lastResourceRequest() const {
if (!m_redirectChain.size())
return resourceRequest();
return m_redirectChain.back().m_request;
}
void Resource::setRevalidatingRequest(const ResourceRequest& request) {
SECURITY_CHECK(m_redirectChain.isEmpty());
DCHECK(!request.isNull());
CHECK(!m_isRevalidationStartForbidden);
m_isRevalidating = true;
m_resourceRequest = request;
m_status = ResourceStatus::NotStarted;
}
bool Resource::willFollowRedirect(const ResourceRequest& newRequest,
const ResourceResponse& redirectResponse) {
if (m_isRevalidating)
revalidationFailed();
m_redirectChain.push_back(RedirectPair(newRequest, redirectResponse));
return true;
}
void Resource::setResponse(const ResourceResponse& response) {
m_response = response;
if (this->response().wasFetchedViaServiceWorker()) {
m_cacheHandler = ServiceWorkerResponseCachedMetadataHandler::create(
this, m_fetcherSecurityOrigin.get());
}
}
void Resource::responseReceived(const ResourceResponse& response,
std::unique_ptr<WebDataConsumerHandle>) {
m_responseTimestamp = currentTime();
if (m_preloadDiscoveryTime) {
int timeSinceDiscovery = static_cast<int>(
1000 * (monotonicallyIncreasingTime() - m_preloadDiscoveryTime));
DEFINE_STATIC_LOCAL(CustomCountHistogram,
preloadDiscoveryToFirstByteHistogram,
("PreloadScanner.TTFB", 0, 10000, 50));
preloadDiscoveryToFirstByteHistogram.count(timeSinceDiscovery);
}
if (m_isRevalidating) {
if (response.httpStatusCode() == 304) {
revalidationSucceeded(response);
return;
}
revalidationFailed();
}
setResponse(response);
String encoding = response.textEncodingName();
if (!encoding.isNull())
setEncoding(encoding);
}
void Resource::setSerializedCachedMetadata(const char* data, size_t size) {
DCHECK(!m_isRevalidating);
DCHECK(!response().isNull());
if (m_cacheHandler)
m_cacheHandler->setSerializedCachedMetadata(data, size);
}
CachedMetadataHandler* Resource::cacheHandler() {
return m_cacheHandler.get();
}
String Resource::reasonNotDeletable() const {
StringBuilder builder;
if (hasClientsOrObservers()) {
builder.append("hasClients(");
builder.appendNumber(m_clients.size());
if (!m_clientsAwaitingCallback.isEmpty()) {
builder.append(", AwaitingCallback=");
builder.appendNumber(m_clientsAwaitingCallback.size());
}
if (!m_finishedClients.isEmpty()) {
builder.append(", Finished=");
builder.appendNumber(m_finishedClients.size());
}
builder.append(')');
}
if (m_loader) {
if (!builder.isEmpty())
builder.append(' ');
builder.append("m_loader");
}
if (m_preloadCount) {
if (!builder.isEmpty())
builder.append(' ');
builder.append("m_preloadCount(");
builder.appendNumber(m_preloadCount);
builder.append(')');
}
if (memoryCache()->contains(this)) {
if (!builder.isEmpty())
builder.append(' ');
builder.append("in_memory_cache");
}
return builder.toString();
}
void Resource::didAddClient(ResourceClient* c) {
if (isLoaded()) {
c->notifyFinished(this);
if (m_clients.contains(c)) {
m_finishedClients.insert(c);
m_clients.erase(c);
}
}
}
static bool typeNeedsSynchronousCacheHit(Resource::Type type) {
// Some resources types default to return data synchronously. For most of
// these, it's because there are layout tests that expect data to return
// synchronously in case of cache hit. In the case of fonts, there was a
// performance regression.
// FIXME: Get to the point where we don't need to special-case sync/async
// behavior for different resource types.
if (type == Resource::Image)
return true;
if (type == Resource::CSSStyleSheet)
return true;
if (type == Resource::Script)
return true;
if (type == Resource::Font)
return true;
return false;
}
void Resource::willAddClientOrObserver(PreloadReferencePolicy policy) {
if (policy == MarkAsReferenced && m_preloadResult == PreloadNotReferenced) {
if (isLoaded())
m_preloadResult = PreloadReferencedWhileComplete;
else if (isLoading())
m_preloadResult = PreloadReferencedWhileLoading;
else
m_preloadResult = PreloadReferenced;
if (m_preloadDiscoveryTime) {
int timeSinceDiscovery = static_cast<int>(
1000 * (monotonicallyIncreasingTime() - m_preloadDiscoveryTime));
DEFINE_STATIC_LOCAL(CustomCountHistogram, preloadDiscoveryHistogram,
("PreloadScanner.ReferenceTime", 0, 10000, 50));
preloadDiscoveryHistogram.count(timeSinceDiscovery);
}
}
if (!hasClientsOrObservers()) {
m_isAlive = true;
}
}
void Resource::addClient(ResourceClient* client,
PreloadReferencePolicy policy) {
CHECK(!m_isAddRemoveClientProhibited);
willAddClientOrObserver(policy);
if (m_isRevalidating) {
m_clients.insert(client);
return;
}
// If an error has occurred or we have existing data to send to the new client
// and the resource type supprts it, send it asynchronously.
if ((errorOccurred() || !response().isNull()) &&
!typeNeedsSynchronousCacheHit(getType()) && !m_needsSynchronousCacheHit) {
m_clientsAwaitingCallback.insert(client);
ResourceCallback::callbackHandler().schedule(this);
return;
}
m_clients.insert(client);
didAddClient(client);
return;
}
void Resource::removeClient(ResourceClient* client) {
CHECK(!m_isAddRemoveClientProhibited);
// This code may be called in a pre-finalizer, where weak members in the
// HashCountedSet are already swept out.
if (m_finishedClients.contains(client))
m_finishedClients.erase(client);
else if (m_clientsAwaitingCallback.contains(client))
m_clientsAwaitingCallback.erase(client);
else
m_clients.erase(client);
if (m_clientsAwaitingCallback.isEmpty())
ResourceCallback::callbackHandler().cancel(this);
didRemoveClientOrObserver();
}
void Resource::didRemoveClientOrObserver() {
if (!hasClientsOrObservers() && m_isAlive) {
m_isAlive = false;
allClientsAndObserversRemoved();
// 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 (hasCacheControlNoStoreHeader() && url().protocolIs("https"))
memoryCache()->remove(this);
}
}
void Resource::allClientsAndObserversRemoved() {
if (!m_loader)
return;
if (!m_cancelTimer.isActive())
m_cancelTimer.startOneShot(0, BLINK_FROM_HERE);
}
void Resource::cancelTimerFired(TimerBase* timer) {
DCHECK_EQ(timer, &m_cancelTimer);
if (!hasClientsOrObservers() && m_loader)
m_loader->cancel();
}
void Resource::setDecodedSize(size_t decodedSize) {
if (decodedSize == m_decodedSize)
return;
size_t oldSize = size();
m_decodedSize = decodedSize;
memoryCache()->update(this, oldSize, size());
}
void Resource::setEncodedSize(size_t encodedSize) {
if (encodedSize == m_encodedSize && encodedSize == m_encodedSizeMemoryUsage)
return;
size_t oldSize = size();
m_encodedSize = encodedSize;
m_encodedSizeMemoryUsage = encodedSize;
memoryCache()->update(this, oldSize, size());
}
void Resource::finishPendingClients() {
// We're going to notify clients one by one. It is simple if the client does
// nothing. However there are a couple other things that can happen.
//
// 1. Clients can be added during the loop. Make sure they are not processed.
// 2. Clients can be removed during the loop. Make sure they are always
// available to be removed. Also don't call removed clients or add them
// back.
//
// Handle case (1) by saving a list of clients to notify. A separate list also
// ensure a client is either in m_clients or m_clientsAwaitingCallback.
HeapVector<Member<ResourceClient>> clientsToNotify;
copyToVector(m_clientsAwaitingCallback, clientsToNotify);
for (const auto& client : clientsToNotify) {
// Handle case (2) to skip removed clients.
if (!m_clientsAwaitingCallback.erase(client))
continue;
m_clients.insert(client);
// When revalidation starts after waiting clients are scheduled and
// before they are added here. In such cases, we just add the clients
// to |m_clients| without didAddClient(), as in Resource::addClient().
if (!m_isRevalidating)
didAddClient(client);
}
// It is still possible for the above loop to finish a new client
// synchronously. If there's no client waiting we should deschedule.
bool scheduled = ResourceCallback::callbackHandler().isScheduled(this);
if (scheduled && m_clientsAwaitingCallback.isEmpty())
ResourceCallback::callbackHandler().cancel(this);
// Prevent the case when there are clients waiting but no callback scheduled.
DCHECK(m_clientsAwaitingCallback.isEmpty() || scheduled);
}
void Resource::prune() {
destroyDecodedDataIfPossible();
}
void Resource::onPurgeMemory() {
prune();
if (!m_cacheHandler)
return;
m_cacheHandler->clearCachedMetadata(CachedMetadataHandler::CacheLocally);
}
void Resource::onMemoryDump(WebMemoryDumpLevelOfDetail levelOfDetail,
WebProcessMemoryDump* memoryDump) const {
static const size_t kMaxURLReportLength = 128;
static const int kMaxResourceClientToShowInMemoryInfra = 10;
const String dumpName = getMemoryDumpName();
WebMemoryAllocatorDump* dump =
memoryDump->createMemoryAllocatorDump(dumpName);
dump->addScalar("encoded_size", "bytes", m_encodedSizeMemoryUsage);
if (hasClientsOrObservers())
dump->addScalar("live_size", "bytes", m_encodedSizeMemoryUsage);
else
dump->addScalar("dead_size", "bytes", m_encodedSizeMemoryUsage);
if (m_data)
m_data->onMemoryDump(dumpName, memoryDump);
if (levelOfDetail == WebMemoryDumpLevelOfDetail::Detailed) {
String urlToReport = url().getString();
if (urlToReport.length() > kMaxURLReportLength) {
urlToReport.truncate(kMaxURLReportLength);
urlToReport = urlToReport + "...";
}
dump->addString("url", "", urlToReport);
dump->addString("reason_not_deletable", "", reasonNotDeletable());
Vector<String> clientNames;
ResourceClientWalker<ResourceClient> walker(m_clients);
while (ResourceClient* client = walker.next())
clientNames.push_back(client->debugName());
ResourceClientWalker<ResourceClient> walker2(m_clientsAwaitingCallback);
while (ResourceClient* client = walker2.next())
clientNames.push_back("(awaiting) " + client->debugName());
ResourceClientWalker<ResourceClient> walker3(m_finishedClients);
while (ResourceClient* client = walker3.next())
clientNames.push_back("(finished) " + client->debugName());
std::sort(clientNames.begin(), clientNames.end(),
WTF::codePointCompareLessThan);
StringBuilder builder;
for (size_t i = 0;
i < clientNames.size() && i < kMaxResourceClientToShowInMemoryInfra;
++i) {
if (i > 0)
builder.append(" / ");
builder.append(clientNames[i]);
}
if (clientNames.size() > kMaxResourceClientToShowInMemoryInfra) {
builder.append(" / and ");
builder.appendNumber(clientNames.size() -
kMaxResourceClientToShowInMemoryInfra);
builder.append(" more");
}
dump->addString("ResourceClient", "", builder.toString());
}
const String overheadName = dumpName + "/metadata";
WebMemoryAllocatorDump* overheadDump =
memoryDump->createMemoryAllocatorDump(overheadName);
overheadDump->addScalar("size", "bytes", overheadSize());
memoryDump->addSuballocation(
overheadDump->guid(), String(WTF::Partitions::kAllocatedObjectPoolName));
}
String Resource::getMemoryDumpName() const {
return String::format(
"web_cache/%s_resources/%ld",
resourceTypeToString(getType(), options().initiatorInfo.name),
m_identifier);
}
void Resource::setCachePolicyBypassingCache() {
m_resourceRequest.setCachePolicy(WebCachePolicy::BypassingCache);
}
void Resource::setPreviewsStateNoTransform() {
m_resourceRequest.setPreviewsState(WebURLRequest::PreviewsNoTransform);
}
void Resource::clearRangeRequestHeader() {
m_resourceRequest.clearHTTPHeaderField("range");
}
void Resource::revalidationSucceeded(
const ResourceResponse& validatingResponse) {
SECURITY_CHECK(m_redirectChain.isEmpty());
SECURITY_CHECK(equalIgnoringFragmentIdentifier(validatingResponse.url(),
response().url()));
m_response.setResourceLoadTiming(validatingResponse.resourceLoadTiming());
// RFC2616 10.3.5
// Update cached headers from the 304 response
const HTTPHeaderMap& newHeaders = validatingResponse.httpHeaderFields();
for (const auto& header : newHeaders) {
// 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(header.key))
continue;
m_response.setHTTPHeaderField(header.key, header.value);
}
m_isRevalidating = false;
}
void Resource::revalidationFailed() {
SECURITY_CHECK(m_redirectChain.isEmpty());
clearData();
m_cacheHandler.clear();
destroyDecodedDataForFailedRevalidation();
m_isRevalidating = false;
}
bool Resource::canReuseRedirectChain() const {
for (auto& redirect : m_redirectChain) {
if (!canUseResponse(redirect.m_redirectResponse, m_responseTimestamp))
return false;
if (redirect.m_request.cacheControlContainsNoCache() ||
redirect.m_request.cacheControlContainsNoStore())
return false;
}
return true;
}
bool Resource::hasCacheControlNoStoreHeader() const {
return response().cacheControlContainsNoStore() ||
resourceRequest().cacheControlContainsNoStore();
}
bool Resource::mustReloadDueToVaryHeader(
const ResourceRequest& newRequest) const {
const AtomicString& vary = response().httpHeaderField(HTTPNames::Vary);
if (vary.isNull())
return false;
if (vary == "*")
return true;
CommaDelimitedHeaderSet varyHeaders;
parseCommaDelimitedHeader(vary, varyHeaders);
for (const String& header : varyHeaders) {
AtomicString atomicHeader(header);
if (resourceRequest().httpHeaderField(atomicHeader) !=
newRequest.httpHeaderField(atomicHeader)) {
return true;
}
}
return false;
}
bool Resource::mustRevalidateDueToCacheHeaders() const {
return !canUseResponse(response(), m_responseTimestamp) ||
resourceRequest().cacheControlContainsNoCache() ||
resourceRequest().cacheControlContainsNoStore();
}
bool Resource::canUseCacheValidator() const {
if (isLoading() || errorOccurred())
return false;
if (hasCacheControlNoStoreHeader())
return false;
// Do not revalidate Resource with redirects. https://crbug.com/613971
if (!redirectChain().isEmpty())
return false;
return response().hasCacheValidatorFields() ||
resourceRequest().hasCacheValidatorFields();
}
size_t Resource::calculateOverheadSize() const {
static const int kAverageClientsHashMapSize = 384;
return sizeof(Resource) + response().memoryUsage() +
kAverageClientsHashMapSize +
resourceRequest().url().getString().length() * 2;
}
void Resource::didChangePriority(ResourceLoadPriority loadPriority,
int intraPriorityValue) {
m_resourceRequest.setPriority(loadPriority, intraPriorityValue);
if (m_loader)
m_loader->didChangePriority(loadPriority, intraPriorityValue);
}
// TODO(toyoshim): Consider to generate automatically. https://crbug.com/675515.
static const char* initiatorTypeNameToString(
const AtomicString& initiatorTypeName) {
if (initiatorTypeName == FetchInitiatorTypeNames::css)
return "CSS resource";
if (initiatorTypeName == FetchInitiatorTypeNames::document)
return "Document";
if (initiatorTypeName == FetchInitiatorTypeNames::icon)
return "Icon";
if (initiatorTypeName == FetchInitiatorTypeNames::internal)
return "Internal resource";
if (initiatorTypeName == FetchInitiatorTypeNames::link)
return "Link element resource";
if (initiatorTypeName == FetchInitiatorTypeNames::processinginstruction)
return "Processing instruction";
if (initiatorTypeName == FetchInitiatorTypeNames::texttrack)
return "Text track";
if (initiatorTypeName == FetchInitiatorTypeNames::xml)
return "XML resource";
if (initiatorTypeName == FetchInitiatorTypeNames::xmlhttprequest)
return "XMLHttpRequest";
static_assert(
FetchInitiatorTypeNames::FetchInitiatorTypeNamesCount == 12,
"New FetchInitiatorTypeNames should be handled correctly here.");
return "Resource";
}
const char* Resource::resourceTypeToString(
Type type,
const AtomicString& fetchInitiatorName) {
switch (type) {
case Resource::MainResource:
return "Main resource";
case Resource::Image:
return "Image";
case Resource::CSSStyleSheet:
return "CSS stylesheet";
case Resource::Script:
return "Script";
case Resource::Font:
return "Font";
case Resource::Raw:
return initiatorTypeNameToString(fetchInitiatorName);
case Resource::SVGDocument:
return "SVG document";
case Resource::XSLStyleSheet:
return "XSL stylesheet";
case Resource::LinkPrefetch:
return "Link prefetch resource";
case Resource::TextTrack:
return "Text track";
case Resource::ImportResource:
return "Imported resource";
case Resource::Media:
return "Media";
case Resource::Manifest:
return "Manifest";
case Resource::Mock:
return "Mock";
}
NOTREACHED();
return initiatorTypeNameToString(fetchInitiatorName);
}
bool Resource::shouldBlockLoadEvent() const {
return !m_linkPreload && isLoadEventBlockingResourceType();
}
bool Resource::isLoadEventBlockingResourceType() const {
switch (m_type) {
case Resource::MainResource:
case Resource::Image:
case Resource::CSSStyleSheet:
case Resource::Script:
case Resource::Font:
case Resource::SVGDocument:
case Resource::XSLStyleSheet:
case Resource::ImportResource:
return true;
case Resource::Raw:
case Resource::LinkPrefetch:
case Resource::TextTrack:
case Resource::Media:
case Resource::Manifest:
case Resource::Mock:
return false;
}
NOTREACHED();
return false;
}
} // namespace blink