/* 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) 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011 Apple Inc. All rights reserved. Copyright (C) 2009 Torch Mobile Inc. http://www.torchmobile.com/ 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. This class provides all functionality needed for loading images, style sheets and html pages from the web. It has a memory cache for these objects. */ #include "sky/engine/core/fetch/ResourceFetcher.h" #include "gen/sky/core/FetchInitiatorTypeNames.h" #include "gen/sky/platform/RuntimeEnabledFeatures.h" #include "sky/engine/core/dom/Document.h" #include "sky/engine/core/fetch/FetchContext.h" #include "sky/engine/core/fetch/FontResource.h" #include "sky/engine/core/fetch/ImageResource.h" #include "sky/engine/core/fetch/MemoryCache.h" #include "sky/engine/core/fetch/RawResource.h" #include "sky/engine/core/fetch/ResourceLoader.h" #include "sky/engine/core/fetch/ResourceLoaderSet.h" #include "sky/engine/core/frame/LocalDOMWindow.h" #include "sky/engine/core/frame/LocalFrame.h" #include "sky/engine/core/frame/Settings.h" #include "sky/engine/core/html/HTMLElement.h" #include "sky/engine/core/html/imports/HTMLImportsController.h" #include "sky/engine/core/inspector/ConsoleMessage.h" #include "sky/engine/core/loader/FrameLoaderClient.h" #include "sky/engine/core/loader/UniqueIdentifier.h" #include "sky/engine/core/page/Page.h" #include "sky/engine/platform/Logging.h" #include "sky/engine/platform/SharedBuffer.h" #include "sky/engine/platform/TraceEvent.h" #include "sky/engine/platform/weborigin/SecurityPolicy.h" #include "sky/engine/public/platform/Platform.h" #include "sky/engine/public/platform/WebURL.h" #include "sky/engine/public/platform/WebURLRequest.h" #include "sky/engine/wtf/text/CString.h" #include "sky/engine/wtf/text/WTFString.h" using blink::WebURLRequest; namespace blink { static Resource* createResource(Resource::Type type, const ResourceRequest& request, const String& charset) { switch (type) { case Resource::Image: return new ImageResource(request); case Resource::Font: return new FontResource(request); case Resource::MainResource: case Resource::Raw: case Resource::Media: return new RawResource(request, type); case Resource::LinkPrefetch: return new Resource(request, Resource::LinkPrefetch); case Resource::LinkSubresource: return new Resource(request, Resource::LinkSubresource); case Resource::ImportResource: return new RawResource(request, type); } ASSERT_NOT_REACHED(); return 0; } static ResourceLoadPriority loadPriority(Resource::Type type, const FetchRequest& request) { if (request.priority() != ResourceLoadPriorityUnresolved) return request.priority(); switch (type) { case Resource::MainResource: return ResourceLoadPriorityVeryHigh; case Resource::Raw: case Resource::Font: case Resource::ImportResource: return ResourceLoadPriorityMedium; case Resource::Image: // We'll default images to VeryLow, and promote whatever is visible. This improves // speed-index by ~5% on average, ~14% at the 99th percentile. return ResourceLoadPriorityVeryLow; case Resource::Media: return ResourceLoadPriorityLow; case Resource::LinkPrefetch: return ResourceLoadPriorityVeryLow; case Resource::LinkSubresource: return ResourceLoadPriorityLow; } ASSERT_NOT_REACHED(); return ResourceLoadPriorityUnresolved; } static WebURLRequest::RequestContext requestContextFromType(const ResourceFetcher* fetcher, Resource::Type type) { switch (type) { case Resource::MainResource: // FIXME: Change this to a context frame type (once we introduce them): http://fetch.spec.whatwg.org/#concept-request-context-frame-type return WebURLRequest::RequestContextHyperlink; case Resource::Font: return WebURLRequest::RequestContextFont; case Resource::Image: return WebURLRequest::RequestContextImage; case Resource::Raw: return WebURLRequest::RequestContextSubresource; case Resource::ImportResource: return WebURLRequest::RequestContextImport; case Resource::LinkPrefetch: return WebURLRequest::RequestContextPrefetch; case Resource::LinkSubresource: return WebURLRequest::RequestContextSubresource; case Resource::Media: // TODO: Split this. return WebURLRequest::RequestContextVideo; } ASSERT_NOT_REACHED(); return WebURLRequest::RequestContextSubresource; } ResourceFetcher::ResourceFetcher(Document* document) : m_document(document) , m_requestCount(0) , m_garbageCollectDocumentResourcesTimer(this, &ResourceFetcher::garbageCollectDocumentResourcesTimerFired) , m_autoLoadImages(true) , m_imagesEnabled(true) , m_allowStaleResources(false) { } ResourceFetcher::~ResourceFetcher() { m_document = nullptr; // Make sure no requests still point to this ResourceFetcher ASSERT(!m_requestCount); } Resource* ResourceFetcher::cachedResource(const KURL& resourceURL) const { KURL url = MemoryCache::removeFragmentIdentifierIfNeeded(resourceURL); return m_documentResources.get(url).get(); } LocalFrame* ResourceFetcher::frame() const { // FIXME(sky): This used to prefer DocumentLoader::frame // over importsController->master()->frame(), but in our // world we should always just have one frame. if (!m_document) return 0; if (m_document->importsController()) return m_document->importsController()->master()->frame(); return m_document->frame(); } FetchContext& ResourceFetcher::context() const { if (LocalFrame* frame = this->frame()) return frame->fetchContext(); return FetchContext::nullInstance(); } ResourcePtr ResourceFetcher::fetchImage(FetchRequest& request) { request.setDefer(clientDefersImage(request.resourceRequest().url()) ? FetchRequest::DeferredByClient : FetchRequest::NoDefer); ResourcePtr resource = requestResource(Resource::Image, request); return resource && resource->type() == Resource::Image ? toImageResource(resource) : 0; } ResourcePtr ResourceFetcher::fetchFont(FetchRequest& request) { ASSERT(request.resourceRequest().frameType() == WebURLRequest::FrameTypeNone); request.mutableResourceRequest().setRequestContext(WebURLRequest::RequestContextFont); return toFontResource(requestResource(Resource::Font, request)); } bool ResourceFetcher::canRequest(Resource::Type type, const KURL& url, const ResourceLoaderOptions& options, FetchRequest::OriginRestriction originRestriction) const { // FIXME(sky): Remove return true; } bool ResourceFetcher::shouldLoadNewResource(Resource::Type type) const { if (!frame()) return false; return true; } bool ResourceFetcher::resourceNeedsLoad(Resource* resource, const FetchRequest& request, RevalidationPolicy policy) { if (FetchRequest::DeferredByClient == request.defer()) return false; if (policy != Use) return true; return resource->stillNeedsLoad(); } void ResourceFetcher::requestLoadStarted(Resource* resource, const FetchRequest& request, ResourceLoadStartType type) { if (type == ResourceLoadingFromCache) notifyLoadedFromMemoryCache(resource); if (request.resourceRequest().url().protocolIsData()) return; m_validatedURLs.add(request.resourceRequest().url()); } ResourcePtr ResourceFetcher::requestResource(Resource::Type type, FetchRequest& request) { ASSERT(request.options().synchronousPolicy == RequestAsynchronously || type == Resource::Raw); TRACE_EVENT0("blink", "ResourceFetcher::requestResource"); KURL url = request.resourceRequest().url(); WTF_LOG(ResourceLoading, "ResourceFetcher::requestResource '%s', charset '%s', priority=%d, type=%s", url.elidedString().latin1().data(), request.charset().latin1().data(), request.priority(), ResourceTypeName(type)); // If only the fragment identifiers differ, it is the same resource. url = MemoryCache::removeFragmentIdentifierIfNeeded(url); if (!url.isValid()) return 0; if (!canRequest(type, url, request.options(), request.originRestriction())) return 0; if (LocalFrame* f = frame()) f->loaderClient()->dispatchWillRequestResource(&request); // See if we can use an existing resource from the cache. ResourcePtr resource = memoryCache()->resourceForURL(url); const RevalidationPolicy policy = determineRevalidationPolicy(type, request, resource.get()); switch (policy) { case Reload: memoryCache()->remove(resource.get()); // Fall through case Load: resource = createResourceForLoading(type, request, request.charset()); break; case Revalidate: resource = createResourceForRevalidation(request, resource.get()); break; case Use: memoryCache()->updateForAccess(resource.get()); break; } if (!resource) return 0; if (!resource->hasClients()) m_deadStatsRecorder.update(policy); if (policy != Use) resource->setIdentifier(createUniqueIdentifier()); ResourceLoadPriority priority = loadPriority(type, request); if (priority != resource->resourceRequest().priority()) { resource->mutableResourceRequest().setPriority(priority); resource->didChangePriority(priority, 0); } if (resourceNeedsLoad(resource.get(), request, policy)) { if (!shouldLoadNewResource(type)) { if (memoryCache()->contains(resource.get())) memoryCache()->remove(resource.get()); return 0; } resource->load(this, request.options()); // For asynchronous loads that immediately fail, it's sufficient to return a // null Resource, as it indicates that something prevented the load from starting. // If there's a network error, that failure will happen asynchronously. However, if // a sync load receives a network error, it will have already happened by this point. // In that case, the requester should have access to the relevant ResourceError, so // we need to return a non-null Resource. if (resource->errorOccurred()) { if (memoryCache()->contains(resource.get())) memoryCache()->remove(resource.get()); return 0; } } requestLoadStarted(resource.get(), request, policy == Use ? ResourceLoadingFromCache : ResourceLoadingFromNetwork); ASSERT(resource->url() == url.string()); m_documentResources.set(resource->url(), resource); return resource; } void ResourceFetcher::determineRequestContext(ResourceRequest& request, Resource::Type type) { WebURLRequest::RequestContext requestContext = requestContextFromType(this, type); request.setRequestContext(requestContext); } ResourceRequestCachePolicy ResourceFetcher::resourceRequestCachePolicy(const ResourceRequest& request, Resource::Type type) { if (request.isConditional()) return ReloadIgnoringCacheData; return UseProtocolCachePolicy; } void ResourceFetcher::addAdditionalRequestHeaders(ResourceRequest& request, Resource::Type type) { if (!frame()) return; if (request.cachePolicy() == UseProtocolCachePolicy) request.setCachePolicy(resourceRequestCachePolicy(request, type)); if (request.requestContext() == WebURLRequest::RequestContextUnspecified) determineRequestContext(request, type); if (type == Resource::LinkPrefetch || type == Resource::LinkSubresource) request.setHTTPHeaderField("Purpose", "prefetch"); context().addAdditionalRequestHeaders(document(), request, (type == Resource::MainResource) ? FetchMainResource : FetchSubresource); } ResourcePtr ResourceFetcher::createResourceForRevalidation(const FetchRequest& request, Resource* resource) { ASSERT(resource); ASSERT(memoryCache()->contains(resource)); ASSERT(resource->isLoaded()); ASSERT(resource->canUseCacheValidator()); ASSERT(!resource->resourceToRevalidate()); ResourceRequest revalidatingRequest(resource->resourceRequest()); revalidatingRequest.clearHTTPReferrer(); addAdditionalRequestHeaders(revalidatingRequest, resource->type()); const AtomicString& lastModified = resource->response().httpHeaderField("Last-Modified"); const AtomicString& eTag = resource->response().httpHeaderField("ETag"); if (!lastModified.isEmpty() || !eTag.isEmpty()) { ASSERT(context().cachePolicy(document()) != CachePolicyReload); if (context().cachePolicy(document()) == CachePolicyRevalidate) revalidatingRequest.setHTTPHeaderField("Cache-Control", "max-age=0"); } if (!lastModified.isEmpty()) revalidatingRequest.setHTTPHeaderField("If-Modified-Since", lastModified); if (!eTag.isEmpty()) revalidatingRequest.setHTTPHeaderField("If-None-Match", eTag); ResourcePtr newResource = createResource(resource->type(), revalidatingRequest, resource->encoding()); WTF_LOG(ResourceLoading, "Resource %p created to revalidate %p", newResource.get(), resource); newResource->setResourceToRevalidate(resource); memoryCache()->remove(resource); memoryCache()->add(newResource.get()); return newResource; } ResourcePtr ResourceFetcher::createResourceForLoading(Resource::Type type, FetchRequest& request, const String& charset) { ASSERT(!memoryCache()->resourceForURL(request.resourceRequest().url())); WTF_LOG(ResourceLoading, "Loading Resource for '%s'.", request.resourceRequest().url().elidedString().latin1().data()); addAdditionalRequestHeaders(request.mutableResourceRequest(), type); ResourcePtr resource = createResource(type, request.resourceRequest(), charset); memoryCache()->add(resource.get()); return resource; } ResourceFetcher::RevalidationPolicy ResourceFetcher::determineRevalidationPolicy(Resource::Type type, const FetchRequest& fetchRequest, Resource* existingResource) const { const ResourceRequest& request = fetchRequest.resourceRequest(); if (!existingResource) return Load; // If the same URL has been loaded as a different type, we need to reload. if (existingResource->type() != type) { WTF_LOG(ResourceLoading, "ResourceFetcher::determineRevalidationPolicy reloading due to type mismatch."); return Reload; } // Do not load from cache if images are not enabled. The load for this image will be blocked // in ImageResource::load. if (FetchRequest::DeferredByClient == fetchRequest.defer()) return Reload; // Always use data uris. // FIXME: Extend this to non-images. if (type == Resource::Image && request.url().protocolIsData()) return Use; if (!existingResource->canReuse(request)) return Reload; // Never use cache entries for downloadToFile requests. The caller expects the resource in a file. if (request.downloadToFile()) return Reload; // Certain requests (e.g., XHRs) might have manually set headers that require revalidation. // FIXME: In theory, this should be a Revalidate case. In practice, the MemoryCache revalidation path assumes a whole bunch // of things about how revalidation works that manual headers violate, so punt to Reload instead. if (request.isConditional()) return Reload; // Don't reload resources while pasting. if (m_allowStaleResources) return Use; if (!fetchRequest.options().canReuseRequest(existingResource->options())) return Reload; // CachePolicyHistoryBuffer uses the cache no matter what. CachePolicy cachePolicy = context().cachePolicy(document()); if (cachePolicy == CachePolicyHistoryBuffer) return Use; // Don't reuse resources with Cache-control: no-store. if (existingResource->hasCacheControlNoStoreHeader()) { WTF_LOG(ResourceLoading, "ResourceFetcher::determineRevalidationPolicy reloading due to Cache-control: no-store."); return Reload; } // If credentials were sent with the previous request and won't be // with this one, or vice versa, re-fetch the resource. // // This helps with the case where the server sends back // "Access-Control-Allow-Origin: *" all the time, but some of the // client's requests are made without CORS and some with. if (existingResource->resourceRequest().allowStoredCredentials() != request.allowStoredCredentials()) { WTF_LOG(ResourceLoading, "ResourceFetcher::determineRevalidationPolicy reloading due to difference in credentials settings."); return Reload; } // During the initial load, avoid loading the same resource multiple times for a single document, // even if the cache policies would tell us to. // We also group loads of the same resource together. // Raw resources are exempted, as XHRs fall into this category and may have user-set Cache-Control: // headers or other factors that require separate requests. if (type != Resource::Raw) { if (document() && !document()->loadEventFinished() && m_validatedURLs.contains(existingResource->url())) return Use; if (existingResource->isLoading()) return Use; } // CachePolicyReload always reloads if (cachePolicy == CachePolicyReload) { WTF_LOG(ResourceLoading, "ResourceFetcher::determineRevalidationPolicy reloading due to CachePolicyReload."); return Reload; } // We'll try to reload the resource if it failed last time. if (existingResource->errorOccurred()) { WTF_LOG(ResourceLoading, "ResourceFetcher::determineRevalidationPolicye reloading due to resource being in the error state"); return Reload; } // List of available images logic allows images to be re-used without cache validation. We restrict this only to images // from memory cache which are the same as the version in the current document. if (type == Resource::Image && existingResource == cachedResource(request.url())) return Use; // Check if the cache headers requires us to revalidate (cache expiration for example). if (cachePolicy == CachePolicyRevalidate || existingResource->mustRevalidateDueToCacheHeaders() || request.cacheControlContainsNoCache()) { // See if the resource has usable ETag or Last-modified headers. if (existingResource->canUseCacheValidator()) return Revalidate; // No, must reload. WTF_LOG(ResourceLoading, "ResourceFetcher::determineRevalidationPolicy reloading due to missing cache validators."); return Reload; } return Use; } void ResourceFetcher::printAccessDeniedMessage(const KURL& url) const { if (url.isNull()) return; if (!frame()) return; String message; if (!m_document || m_document->url().isNull()) message = "Unsafe attempt to load URL " + url.elidedString() + '.'; else message = "Unsafe attempt to load URL " + url.elidedString() + " from frame with URL " + m_document->url().elidedString() + ". Domains, protocols and ports must match.\n"; frame()->document()->addConsoleMessage(ConsoleMessage::create(SecurityMessageSource, ErrorMessageLevel, message)); } void ResourceFetcher::setAutoLoadImages(bool enable) { if (enable == m_autoLoadImages) return; m_autoLoadImages = enable; if (!m_autoLoadImages) return; reloadImagesIfNotDeferred(); } void ResourceFetcher::setImagesEnabled(bool enable) { if (enable == m_imagesEnabled) return; m_imagesEnabled = enable; if (!m_imagesEnabled) return; reloadImagesIfNotDeferred(); } bool ResourceFetcher::clientDefersImage(const KURL& url) const { // FIXME(sky): remove return false; } bool ResourceFetcher::shouldDeferImageLoad(const KURL& url) const { return clientDefersImage(url) || !m_autoLoadImages; } void ResourceFetcher::reloadImagesIfNotDeferred() { DocumentResourceMap::iterator end = m_documentResources.end(); for (DocumentResourceMap::iterator it = m_documentResources.begin(); it != end; ++it) { Resource* resource = it->value.get(); if (resource->type() == Resource::Image && resource->stillNeedsLoad() && !clientDefersImage(resource->url())) const_cast(resource)->load(this, defaultResourceOptions()); } } void ResourceFetcher::didLoadResource(Resource* resource) { RefPtr protectDocument(m_document); if (m_document) m_document->checkCompleted(); scheduleDocumentResourcesGC(); } void ResourceFetcher::scheduleDocumentResourcesGC() { if (!m_garbageCollectDocumentResourcesTimer.isActive()) m_garbageCollectDocumentResourcesTimer.startOneShot(0, FROM_HERE); } // Garbage collecting m_documentResources is a workaround for the // ResourcePtrs on the RHS being strong references. Ideally this // would be a weak map, however ResourcePtrs perform additional // bookkeeping on Resources, so instead pseudo-GC them -- when the // reference count reaches 1, m_documentResources is the only reference, so // remove it from the map. void ResourceFetcher::garbageCollectDocumentResourcesTimerFired(Timer* timer) { ASSERT_UNUSED(timer, timer == &m_garbageCollectDocumentResourcesTimer); garbageCollectDocumentResources(); } void ResourceFetcher::garbageCollectDocumentResources() { typedef Vector StringVector; StringVector resourcesToDelete; for (DocumentResourceMap::iterator it = m_documentResources.begin(); it != m_documentResources.end(); ++it) { if (it->value->hasOneHandle()) resourcesToDelete.append(it->key); } m_documentResources.removeAll(resourcesToDelete); } void ResourceFetcher::notifyLoadedFromMemoryCache(Resource* resource) { if (!frame() || !frame()->page() || resource->status() != Resource::Cached || m_validatedURLs.contains(resource->url())) return; ResourceRequest request(resource->url()); unsigned long identifier = createUniqueIdentifier(); context().dispatchDidLoadResourceFromMemoryCache(request, resource->response()); // FIXME: If willSendRequest changes the request, we don't respect it. willSendRequest(identifier, request, ResourceResponse(), resource->options().initiatorInfo); context().sendRemainingDelegateMessages(m_document, identifier, resource->response(), resource->encodedSize()); } void ResourceFetcher::incrementRequestCount(const Resource* res) { if (res->ignoreForRequestCount()) return; ++m_requestCount; } void ResourceFetcher::decrementRequestCount(const Resource* res) { if (res->ignoreForRequestCount()) return; --m_requestCount; ASSERT(m_requestCount > -1); } void ResourceFetcher::didFinishLoading(const Resource* resource, double finishTime, int64_t encodedDataLength) { TRACE_EVENT_ASYNC_END0("net", "Resource", resource); context().dispatchDidFinishLoading(m_document, resource->identifier(), finishTime, encodedDataLength); } void ResourceFetcher::didChangeLoadingPriority(const Resource* resource, ResourceLoadPriority loadPriority, int intraPriorityValue) { TRACE_EVENT_ASYNC_STEP_INTO1("net", "Resource", resource, "ChangePriority", "priority", loadPriority); context().dispatchDidChangeResourcePriority(resource->identifier(), loadPriority, intraPriorityValue); } void ResourceFetcher::didFailLoading(const Resource* resource, const ResourceError& error) { TRACE_EVENT_ASYNC_END0("net", "Resource", resource); context().dispatchDidFail(m_document, resource->identifier(), error); } void ResourceFetcher::willSendRequest(unsigned long identifier, ResourceRequest& request, const ResourceResponse& redirectResponse, const FetchInitiatorInfo& initiatorInfo) { context().dispatchWillSendRequest(m_document, identifier, request, redirectResponse, initiatorInfo); } void ResourceFetcher::didReceiveResponse(const Resource* resource, const ResourceResponse& response) { context().dispatchDidReceiveResponse(m_document, resource->identifier(), response, resource->loader()); } void ResourceFetcher::didReceiveData(const Resource* resource, const char* data, int dataLength, int encodedDataLength) { context().dispatchDidReceiveData(m_document, resource->identifier(), data, dataLength, encodedDataLength); } void ResourceFetcher::didDownloadData(const Resource* resource, int dataLength, int encodedDataLength) { context().dispatchDidDownloadData(m_document, resource->identifier(), dataLength, encodedDataLength); } void ResourceFetcher::subresourceLoaderFinishedLoadingOnePart(ResourceLoader* loader) { if (!m_multipartLoaders) m_multipartLoaders = ResourceLoaderSet::create(); m_multipartLoaders->add(loader); m_loaders->remove(loader); } void ResourceFetcher::didInitializeResourceLoader(ResourceLoader* loader) { if (!m_document) return; if (!m_loaders) m_loaders = ResourceLoaderSet::create(); ASSERT(!m_loaders->contains(loader)); m_loaders->add(loader); } void ResourceFetcher::willTerminateResourceLoader(ResourceLoader* loader) { if (m_loaders && m_loaders->contains(loader)) m_loaders->remove(loader); if (m_multipartLoaders && m_multipartLoaders->contains(loader)) m_multipartLoaders->remove(loader); } void ResourceFetcher::willStartLoadingResource(Resource* resource, ResourceRequest& request) { TRACE_EVENT_ASYNC_BEGIN2( "net", "Resource", resource, "url", TRACE_STR_COPY(resource->url().string().ascii().data()), "priority", resource->resourceRequest().priority()); } void ResourceFetcher::stopFetching() { if (m_multipartLoaders) m_multipartLoaders->cancelAll(); if (m_loaders) m_loaders->cancelAll(); } bool ResourceFetcher::isFetching() const { return m_loaders && !m_loaders->isEmpty(); } bool ResourceFetcher::isLoadedBy(ResourceLoaderHost* possibleOwner) const { return this == possibleOwner; } #if !ENABLE(OILPAN) void ResourceFetcher::refResourceLoaderHost() { ref(); } void ResourceFetcher::derefResourceLoaderHost() { deref(); } #endif const ResourceLoaderOptions& ResourceFetcher::defaultResourceOptions() { DEFINE_STATIC_LOCAL(ResourceLoaderOptions, options, (BufferData, AllowStoredCredentials, ClientRequestedCredentials, DocumentContext)); return options; } ResourceFetcher::DeadResourceStatsRecorder::DeadResourceStatsRecorder() : m_useCount(0) , m_revalidateCount(0) , m_loadCount(0) { } ResourceFetcher::DeadResourceStatsRecorder::~DeadResourceStatsRecorder() { blink::Platform::current()->histogramCustomCounts( "WebCore.ResourceFetcher.HitCount", m_useCount, 0, 1000, 50); blink::Platform::current()->histogramCustomCounts( "WebCore.ResourceFetcher.RevalidateCount", m_revalidateCount, 0, 1000, 50); blink::Platform::current()->histogramCustomCounts( "WebCore.ResourceFetcher.LoadCount", m_loadCount, 0, 1000, 50); } void ResourceFetcher::DeadResourceStatsRecorder::update(RevalidationPolicy policy) { switch (policy) { case Reload: case Load: ++m_loadCount; return; case Revalidate: ++m_revalidateCount; return; case Use: ++m_useCount; return; } } }