This source file includes following definitions.
- createResource
- loadPriority
- resourceFromDataURIRequest
- populateResourceTiming
- reportResourceTiming
- requestTargetType
- m_allowStaleResources
- cachedResource
- frame
- context
- fetchSynchronously
- fetchImage
- preCacheDataURIImage
- fetchFont
- fetchShader
- fetchImport
- fetchCSSStyleSheet
- fetchUserCSSStyleSheet
- fetchScript
- fetchXSLStyleSheet
- fetchSVGDocument
- fetchLinkResource
- fetchRawResource
- fetchMainResource
- preCacheSubstituteDataForMainResource
- checkInsecureContent
- canRequest
- canAccessResource
- shouldLoadNewResource
- resourceNeedsLoad
- requestResource
- resourceTimingReportTimerFired
- determineTargetType
- resourceRequestCachePolicy
- addAdditionalRequestHeaders
- revalidateResource
- loadResource
- storeResourceTimingInitiatorInformation
- determineRevalidationPolicy
- printAccessDeniedMessage
- setAutoLoadImages
- setImagesEnabled
- clientDefersImage
- shouldDeferImageLoad
- reloadImagesIfNotDeferred
- redirectReceived
- didLoadResource
- scheduleDocumentResourcesGC
- garbageCollectDocumentResourcesTimerFired
- garbageCollectDocumentResources
- notifyLoadedFromMemoryCache
- incrementRequestCount
- decrementRequestCount
- preload
- requestPreload
- isPreloaded
- clearPreloads
- didFinishLoading
- didChangeLoadingPriority
- didFailLoading
- willSendRequest
- didReceiveResponse
- didReceiveData
- didDownloadData
- subresourceLoaderFinishedLoadingOnePart
- didInitializeResourceLoader
- willTerminateResourceLoader
- willStartLoadingResource
- stopFetching
- isFetching
- setDefersLoading
- defersLoading
- isLoadedBy
- canAccessRedirect
- refResourceLoaderHost
- derefResourceLoaderHost
- printPreloadStats
- defaultResourceOptions
- m_loadCount
- update
#include "config.h"
#include "core/fetch/ResourceFetcher.h"
#include "RuntimeEnabledFeatures.h"
#include "bindings/v8/ScriptController.h"
#include "core/dom/Document.h"
#include "core/fetch/CSSStyleSheetResource.h"
#include "core/fetch/CrossOriginAccessControl.h"
#include "core/fetch/DocumentResource.h"
#include "core/fetch/FetchContext.h"
#include "core/fetch/FontResource.h"
#include "core/fetch/ImageResource.h"
#include "core/fetch/MemoryCache.h"
#include "core/fetch/RawResource.h"
#include "core/fetch/ResourceLoader.h"
#include "core/fetch/ResourceLoaderSet.h"
#include "core/fetch/ScriptResource.h"
#include "core/fetch/ShaderResource.h"
#include "core/fetch/XSLStyleSheetResource.h"
#include "core/html/HTMLElement.h"
#include "core/html/HTMLFrameOwnerElement.h"
#include "core/html/imports/HTMLImport.h"
#include "core/inspector/InspectorInstrumentation.h"
#include "core/loader/DocumentLoader.h"
#include "core/loader/FrameLoader.h"
#include "core/loader/FrameLoaderClient.h"
#include "core/loader/PingLoader.h"
#include "core/loader/SubstituteData.h"
#include "core/loader/UniqueIdentifier.h"
#include "core/loader/appcache/ApplicationCacheHost.h"
#include "core/frame/DOMWindow.h"
#include "core/frame/LocalFrame.h"
#include "core/frame/csp/ContentSecurityPolicy.h"
#include "core/timing/Performance.h"
#include "core/timing/ResourceTimingInfo.h"
#include "core/frame/Settings.h"
#include "platform/Logging.h"
#include "platform/TraceEvent.h"
#include "platform/weborigin/SecurityOrigin.h"
#include "platform/weborigin/SecurityPolicy.h"
#include "public/platform/Platform.h"
#include "public/platform/WebURL.h"
#include "wtf/text/CString.h"
#include "wtf/text/WTFString.h"
#define PRELOAD_DEBUG 0
namespace WebCore {
static Resource* createResource(Resource::Type type, const ResourceRequest& request, const String& charset)
{
switch (type) {
case Resource::Image:
return new ImageResource(request);
case Resource::CSSStyleSheet:
return new CSSStyleSheetResource(request, charset);
case Resource::Script:
return new ScriptResource(request, charset);
case Resource::SVGDocument:
return new DocumentResource(request, Resource::SVGDocument);
case Resource::Font:
return new FontResource(request);
case Resource::MainResource:
case Resource::Raw:
case Resource::TextTrack:
return new RawResource(request, type);
case Resource::XSLStyleSheet:
return new XSLStyleSheetResource(request);
case Resource::LinkPrefetch:
return new Resource(request, Resource::LinkPrefetch);
case Resource::LinkSubresource:
return new Resource(request, Resource::LinkSubresource);
case Resource::Shader:
return new ShaderResource(request);
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::CSSStyleSheet:
return ResourceLoadPriorityHigh;
case Resource::Raw:
return request.options().synchronousPolicy == RequestSynchronously ? ResourceLoadPriorityVeryHigh : ResourceLoadPriorityMedium;
case Resource::Script:
case Resource::Font:
case Resource::ImportResource:
return ResourceLoadPriorityMedium;
case Resource::Image:
return ResourceLoadPriorityVeryLow;
case Resource::XSLStyleSheet:
ASSERT(RuntimeEnabledFeatures::xsltEnabled());
return ResourceLoadPriorityHigh;
case Resource::SVGDocument:
return ResourceLoadPriorityLow;
case Resource::LinkPrefetch:
return ResourceLoadPriorityVeryLow;
case Resource::LinkSubresource:
return ResourceLoadPriorityLow;
case Resource::TextTrack:
return ResourceLoadPriorityLow;
case Resource::Shader:
return ResourceLoadPriorityMedium;
}
ASSERT_NOT_REACHED();
return ResourceLoadPriorityUnresolved;
}
static Resource* resourceFromDataURIRequest(const ResourceRequest& request, const ResourceLoaderOptions& resourceOptions)
{
const KURL& url = request.url();
ASSERT(url.protocolIsData());
blink::WebString mimetype;
blink::WebString charset;
RefPtr<SharedBuffer> data = PassRefPtr<SharedBuffer>(blink::Platform::current()->parseDataURL(url, mimetype, charset));
if (!data)
return 0;
ResourceResponse response(url, mimetype, data->size(), charset, String());
Resource* resource = createResource(Resource::Image, request, charset);
resource->setOptions(resourceOptions);
resource->responseReceived(response);
if (data->size())
resource->setResourceBuffer(data);
resource->finish();
return resource;
}
static void populateResourceTiming(ResourceTimingInfo* info, Resource* resource, bool clearLoadTimings)
{
info->setInitialRequest(resource->resourceRequest());
info->setFinalResponse(resource->response());
if (clearLoadTimings)
info->clearLoadTimings();
info->setLoadFinishTime(resource->loadFinishTime());
}
static void reportResourceTiming(ResourceTimingInfo* info, Document* initiatorDocument, bool isMainResource)
{
if (initiatorDocument && isMainResource)
initiatorDocument = initiatorDocument->parentDocument();
if (!initiatorDocument || !initiatorDocument->loader())
return;
if (DOMWindow* initiatorWindow = initiatorDocument->domWindow())
initiatorWindow->performance().addResourceTiming(*info, initiatorDocument);
}
static ResourceRequest::TargetType requestTargetType(const ResourceFetcher* fetcher, const ResourceRequest& request, Resource::Type type)
{
switch (type) {
case Resource::MainResource:
if (fetcher->frame()->tree().parent())
return ResourceRequest::TargetIsSubframe;
return ResourceRequest::TargetIsMainFrame;
case Resource::XSLStyleSheet:
ASSERT(RuntimeEnabledFeatures::xsltEnabled());
case Resource::CSSStyleSheet:
return ResourceRequest::TargetIsStyleSheet;
case Resource::Script:
return ResourceRequest::TargetIsScript;
case Resource::Font:
return ResourceRequest::TargetIsFont;
case Resource::Image:
return ResourceRequest::TargetIsImage;
case Resource::Shader:
case Resource::Raw:
case Resource::ImportResource:
return ResourceRequest::TargetIsSubresource;
case Resource::LinkPrefetch:
return ResourceRequest::TargetIsPrefetch;
case Resource::LinkSubresource:
return ResourceRequest::TargetIsSubresource;
case Resource::TextTrack:
return ResourceRequest::TargetIsTextTrack;
case Resource::SVGDocument:
return ResourceRequest::TargetIsImage;
}
ASSERT_NOT_REACHED();
return ResourceRequest::TargetIsSubresource;
}
ResourceFetcher::ResourceFetcher(DocumentLoader* documentLoader)
: m_document(0)
, m_documentLoader(documentLoader)
, m_requestCount(0)
, m_garbageCollectDocumentResourcesTimer(this, &ResourceFetcher::garbageCollectDocumentResourcesTimerFired)
, m_resourceTimingReportTimer(this, &ResourceFetcher::resourceTimingReportTimerFired)
, m_autoLoadImages(true)
, m_imagesEnabled(true)
, m_allowStaleResources(false)
{
}
ResourceFetcher::~ResourceFetcher()
{
m_documentLoader = 0;
m_document = 0;
clearPreloads();
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
{
if (m_documentLoader)
return m_documentLoader->frame();
if (m_document && m_document->import())
return m_document->import()->frame();
return 0;
}
FetchContext& ResourceFetcher::context() const
{
if (LocalFrame* frame = this->frame())
return frame->fetchContext();
return FetchContext::nullInstance();
}
ResourcePtr<Resource> ResourceFetcher::fetchSynchronously(FetchRequest& request)
{
ASSERT(document());
request.mutableResourceRequest().setTimeoutInterval(10);
ResourceLoaderOptions options(request.options());
options.synchronousPolicy = RequestSynchronously;
request.setOptions(options);
return requestResource(Resource::Raw, request);
}
ResourcePtr<ImageResource> ResourceFetcher::fetchImage(FetchRequest& request)
{
if (LocalFrame* f = frame()) {
if (f->document()->pageDismissalEventBeingDispatched() != Document::NoDismissal) {
KURL requestURL = request.resourceRequest().url();
if (requestURL.isValid() && canRequest(Resource::Image, requestURL, request.options(), request.forPreload(), request.originRestriction()))
PingLoader::loadImage(f, requestURL);
return 0;
}
}
if (request.resourceRequest().url().protocolIsData())
preCacheDataURIImage(request);
request.setDefer(clientDefersImage(request.resourceRequest().url()) ? FetchRequest::DeferredByClient : FetchRequest::NoDefer);
return toImageResource(requestResource(Resource::Image, request));
}
void ResourceFetcher::preCacheDataURIImage(const FetchRequest& request)
{
const KURL& url = request.resourceRequest().url();
ASSERT(url.protocolIsData());
if (memoryCache()->resourceForURL(url))
return;
if (Resource* resource = resourceFromDataURIRequest(request.resourceRequest(), request.options())) {
memoryCache()->add(resource);
scheduleDocumentResourcesGC();
}
}
ResourcePtr<FontResource> ResourceFetcher::fetchFont(FetchRequest& request)
{
return toFontResource(requestResource(Resource::Font, request));
}
ResourcePtr<ShaderResource> ResourceFetcher::fetchShader(FetchRequest& request)
{
return toShaderResource(requestResource(Resource::Shader, request));
}
ResourcePtr<RawResource> ResourceFetcher::fetchImport(FetchRequest& request)
{
return toRawResource(requestResource(Resource::ImportResource, request));
}
ResourcePtr<CSSStyleSheetResource> ResourceFetcher::fetchCSSStyleSheet(FetchRequest& request)
{
return toCSSStyleSheetResource(requestResource(Resource::CSSStyleSheet, request));
}
ResourcePtr<CSSStyleSheetResource> ResourceFetcher::fetchUserCSSStyleSheet(FetchRequest& request)
{
KURL url = MemoryCache::removeFragmentIdentifierIfNeeded(request.resourceRequest().url());
if (Resource* existing = memoryCache()->resourceForURL(url)) {
if (existing->type() == Resource::CSSStyleSheet)
return toCSSStyleSheetResource(existing);
memoryCache()->remove(existing);
}
request.setOptions(ResourceLoaderOptions(SniffContent, BufferData, AllowStoredCredentials, ClientRequestedCredentials, CheckContentSecurityPolicy, DocumentContext));
return toCSSStyleSheetResource(requestResource(Resource::CSSStyleSheet, request));
}
ResourcePtr<ScriptResource> ResourceFetcher::fetchScript(FetchRequest& request)
{
return toScriptResource(requestResource(Resource::Script, request));
}
ResourcePtr<XSLStyleSheetResource> ResourceFetcher::fetchXSLStyleSheet(FetchRequest& request)
{
ASSERT(RuntimeEnabledFeatures::xsltEnabled());
return toXSLStyleSheetResource(requestResource(Resource::XSLStyleSheet, request));
}
ResourcePtr<DocumentResource> ResourceFetcher::fetchSVGDocument(FetchRequest& request)
{
return toDocumentResource(requestResource(Resource::SVGDocument, request));
}
ResourcePtr<Resource> ResourceFetcher::fetchLinkResource(Resource::Type type, FetchRequest& request)
{
ASSERT(frame());
ASSERT(type == Resource::LinkPrefetch || type == Resource::LinkSubresource);
return requestResource(type, request);
}
ResourcePtr<RawResource> ResourceFetcher::fetchRawResource(FetchRequest& request)
{
return toRawResource(requestResource(Resource::Raw, request));
}
ResourcePtr<RawResource> ResourceFetcher::fetchMainResource(FetchRequest& request, const SubstituteData& substituteData)
{
if (substituteData.isValid())
preCacheSubstituteDataForMainResource(request, substituteData);
return toRawResource(requestResource(Resource::MainResource, request));
}
void ResourceFetcher::preCacheSubstituteDataForMainResource(const FetchRequest& request, const SubstituteData& substituteData)
{
const KURL& url = request.url();
if (Resource* oldResource = memoryCache()->resourceForURL(url))
memoryCache()->remove(oldResource);
ResourceResponse response(url, substituteData.mimeType(), substituteData.content()->size(), substituteData.textEncoding(), emptyString());
ResourcePtr<Resource> resource = createResource(Resource::MainResource, request.resourceRequest(), substituteData.textEncoding());
resource->setNeedsSynchronousCacheHit(substituteData.forceSynchronousLoad());
resource->setOptions(request.options());
resource->setDataBufferingPolicy(BufferData);
resource->responseReceived(response);
if (substituteData.content()->size())
resource->setResourceBuffer(substituteData.content());
resource->finish();
memoryCache()->add(resource.get());
}
bool ResourceFetcher::checkInsecureContent(Resource::Type type, const KURL& url, MixedContentBlockingTreatment treatment) const
{
if (treatment == TreatAsDefaultForType) {
switch (type) {
case Resource::XSLStyleSheet:
ASSERT(RuntimeEnabledFeatures::xsltEnabled());
case Resource::Script:
case Resource::SVGDocument:
case Resource::CSSStyleSheet:
case Resource::ImportResource:
treatment = TreatAsActiveContent;
break;
case Resource::TextTrack:
case Resource::Shader:
case Resource::Raw:
case Resource::Image:
case Resource::Font:
treatment = TreatAsPassiveContent;
break;
case Resource::MainResource:
case Resource::LinkPrefetch:
case Resource::LinkSubresource:
treatment = TreatAsAlwaysAllowedContent;
break;
}
}
if (treatment == TreatAsActiveContent) {
if (LocalFrame* f = frame()) {
if (!f->loader().mixedContentChecker()->canRunInsecureContent(m_document->securityOrigin(), url))
return false;
LocalFrame* top = f->tree().top();
if (top != f && !top->loader().mixedContentChecker()->canRunInsecureContent(top->document()->securityOrigin(), url))
return false;
}
} else if (treatment == TreatAsPassiveContent) {
if (LocalFrame* f = frame()) {
LocalFrame* top = f->tree().top();
if (!top->loader().mixedContentChecker()->canDisplayInsecureContent(top->document()->securityOrigin(), url))
return false;
}
} else {
ASSERT(treatment == TreatAsAlwaysAllowedContent);
}
return true;
}
bool ResourceFetcher::canRequest(Resource::Type type, const KURL& url, const ResourceLoaderOptions& options, bool forPreload, FetchRequest::OriginRestriction originRestriction) const
{
SecurityOrigin* securityOrigin = options.securityOrigin.get();
if (!securityOrigin && document())
securityOrigin = document()->securityOrigin();
if (securityOrigin && !securityOrigin->canDisplay(url)) {
if (!forPreload)
context().reportLocalLoadFailed(url);
WTF_LOG(ResourceLoading, "ResourceFetcher::requestResource URL was not allowed by SecurityOrigin::canDisplay");
return 0;
}
bool shouldBypassMainWorldContentSecurityPolicy = (frame() && frame()->script().shouldBypassMainWorldContentSecurityPolicy()) || (options.contentSecurityPolicyOption == DoNotCheckContentSecurityPolicy);
switch (type) {
case Resource::MainResource:
case Resource::Image:
case Resource::CSSStyleSheet:
case Resource::Script:
case Resource::Font:
case Resource::Raw:
case Resource::LinkPrefetch:
case Resource::LinkSubresource:
case Resource::TextTrack:
case Resource::Shader:
case Resource::ImportResource:
if (originRestriction == FetchRequest::RestrictToSameOrigin && !securityOrigin->canRequest(url)) {
printAccessDeniedMessage(url);
return false;
}
break;
case Resource::XSLStyleSheet:
ASSERT(RuntimeEnabledFeatures::xsltEnabled());
case Resource::SVGDocument:
if (!securityOrigin->canRequest(url)) {
printAccessDeniedMessage(url);
return false;
}
break;
}
switch (type) {
case Resource::XSLStyleSheet:
ASSERT(RuntimeEnabledFeatures::xsltEnabled());
if (!shouldBypassMainWorldContentSecurityPolicy && !m_document->contentSecurityPolicy()->allowScriptFromSource(url))
return false;
break;
case Resource::Script:
case Resource::ImportResource:
if (!shouldBypassMainWorldContentSecurityPolicy && !m_document->contentSecurityPolicy()->allowScriptFromSource(url))
return false;
if (frame()) {
Settings* settings = frame()->settings();
if (!frame()->loader().client()->allowScriptFromSource(!settings || settings->scriptEnabled(), url)) {
frame()->loader().client()->didNotAllowScript();
return false;
}
}
break;
case Resource::Shader:
case Resource::CSSStyleSheet:
if (!shouldBypassMainWorldContentSecurityPolicy && !m_document->contentSecurityPolicy()->allowStyleFromSource(url))
return false;
break;
case Resource::SVGDocument:
case Resource::Image:
if (!shouldBypassMainWorldContentSecurityPolicy && !m_document->contentSecurityPolicy()->allowImageFromSource(url))
return false;
break;
case Resource::Font: {
if (!shouldBypassMainWorldContentSecurityPolicy && !m_document->contentSecurityPolicy()->allowFontFromSource(url))
return false;
break;
}
case Resource::MainResource:
case Resource::Raw:
case Resource::LinkPrefetch:
case Resource::LinkSubresource:
break;
case Resource::TextTrack:
if (!shouldBypassMainWorldContentSecurityPolicy && !m_document->contentSecurityPolicy()->allowMediaFromSource(url))
return false;
break;
}
if (!checkInsecureContent(type, url, options.mixedContentBlockingTreatment))
return false;
return true;
}
bool ResourceFetcher::canAccessResource(Resource* resource, SecurityOrigin* sourceOrigin, const KURL& url) const
{
if (!canRequest(resource->type(), url, resource->options(), false, FetchRequest::UseDefaultOriginRestrictionForType))
return false;
if (!sourceOrigin && document())
sourceOrigin = document()->securityOrigin();
if (sourceOrigin->canRequest(url))
return true;
String errorDescription;
if (!resource->passesAccessControlCheck(sourceOrigin, errorDescription)) {
if (frame() && frame()->document()) {
String resourceType = Resource::resourceTypeToString(resource->type(), resource->options().initiatorInfo);
frame()->document()->addConsoleMessage(JSMessageSource, ErrorMessageLevel, resourceType + " from origin '" + SecurityOrigin::create(url)->toString() + "' has been blocked from loading by Cross-Origin Resource Sharing policy: " + errorDescription);
}
return false;
}
return true;
}
bool ResourceFetcher::shouldLoadNewResource(Resource::Type type) const
{
if (!frame())
return false;
if (!m_documentLoader)
return true;
if (type == Resource::MainResource)
return m_documentLoader == frame()->loader().provisionalDocumentLoader();
return m_documentLoader == frame()->loader().documentLoader();
}
bool ResourceFetcher::resourceNeedsLoad(Resource* resource, const FetchRequest& request, RevalidationPolicy policy)
{
if (FetchRequest::DeferredByClient == request.defer())
return false;
if (policy != Use)
return true;
if (resource->stillNeedsLoad())
return true;
return request.options().synchronousPolicy == RequestSynchronously && resource->isLoading();
}
ResourcePtr<Resource> ResourceFetcher::requestResource(Resource::Type type, FetchRequest& request)
{
ASSERT(request.options().synchronousPolicy == RequestAsynchronously || type == Resource::Raw);
TRACE_EVENT0("webkit", "ResourceFetcher::requestResource");
KURL url = request.resourceRequest().url();
WTF_LOG(ResourceLoading, "ResourceFetcher::requestResource '%s', charset '%s', priority=%d, forPreload=%u, type=%s", url.elidedString().latin1().data(), request.charset().latin1().data(), request.priority(), request.forPreload(), ResourceTypeName(type));
url = MemoryCache::removeFragmentIdentifierIfNeeded(url);
if (!url.isValid())
return 0;
if (!canRequest(type, url, request.options(), request.forPreload(), request.originRestriction()))
return 0;
if (LocalFrame* f = frame())
f->loader().client()->dispatchWillRequestResource(&request);
ResourcePtr<Resource> resource = memoryCache()->resourceForURL(url);
const RevalidationPolicy policy = determineRevalidationPolicy(type, request.mutableResourceRequest(), request.forPreload(), resource.get(), request.defer(), request.options());
switch (policy) {
case Reload:
memoryCache()->remove(resource.get());
case Load:
resource = loadResource(type, request, request.charset());
break;
case Revalidate:
resource = revalidateResource(request, resource.get());
break;
case Use:
memoryCache()->updateForAccess(resource.get());
notifyLoadedFromMemoryCache(resource.get());
break;
}
if (!resource)
return 0;
if (!resource->hasClients())
m_deadStatsRecorder.update(policy);
if (policy != Use)
resource->setIdentifier(createUniqueIdentifier());
if (!request.forPreload() || policy != Use) {
ResourceLoadPriority priority = loadPriority(type, request);
if (priority != resource->resourceRequest().priority()) {
resource->resourceRequest().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;
}
if (!m_documentLoader || !m_documentLoader->scheduleArchiveLoad(resource.get(), request.resourceRequest()))
resource->load(this, request.options());
if (resource->errorOccurred()) {
if (memoryCache()->contains(resource.get()))
memoryCache()->remove(resource.get());
return request.options().synchronousPolicy == RequestSynchronously ? resource : 0;
}
}
if (type == Resource::MainResource) {
ASSERT(policy != Use || m_documentLoader->substituteData().isValid());
ASSERT(policy != Revalidate);
memoryCache()->remove(resource.get());
if (request.forPreload())
return 0;
}
if (!request.resourceRequest().url().protocolIsData() && (!m_documentLoader || !m_documentLoader->substituteData().isValid())) {
if (policy == Use && !m_validatedURLs.contains(request.resourceRequest().url())) {
RefPtr<ResourceTimingInfo> info = ResourceTimingInfo::create(request.options().initiatorInfo.name, monotonicallyIncreasingTime());
populateResourceTiming(info.get(), resource.get(), true);
m_scheduledResourceTimingReports.add(info, resource->type() == Resource::MainResource);
if (!m_resourceTimingReportTimer.isActive())
m_resourceTimingReportTimer.startOneShot(0, FROM_HERE);
}
m_validatedURLs.add(request.resourceRequest().url());
}
ASSERT(resource->url() == url.string());
m_documentResources.set(resource->url(), resource);
return resource;
}
void ResourceFetcher::resourceTimingReportTimerFired(Timer<ResourceFetcher>* timer)
{
ASSERT_UNUSED(timer, timer == &m_resourceTimingReportTimer);
HashMap<RefPtr<ResourceTimingInfo>, bool> timingReports;
timingReports.swap(m_scheduledResourceTimingReports);
HashMap<RefPtr<ResourceTimingInfo>, bool>::iterator end = timingReports.end();
for (HashMap<RefPtr<ResourceTimingInfo>, bool>::iterator it = timingReports.begin(); it != end; ++it) {
RefPtr<ResourceTimingInfo> info = it->key;
bool isMainResource = it->value;
reportResourceTiming(info.get(), document(), isMainResource);
}
}
void ResourceFetcher::determineTargetType(ResourceRequest& request, Resource::Type type)
{
ResourceRequest::TargetType targetType = requestTargetType(this, request, type);
request.setTargetType(targetType);
}
ResourceRequestCachePolicy ResourceFetcher::resourceRequestCachePolicy(const ResourceRequest& request, Resource::Type type)
{
if (type == Resource::MainResource) {
FrameLoadType frameLoadType = frame()->loader().loadType();
bool isReload = frameLoadType == FrameLoadTypeReload || frameLoadType == FrameLoadTypeReloadFromOrigin;
if (request.httpMethod() == "POST" && frameLoadType == FrameLoadTypeBackForward)
return ReturnCacheDataDontLoad;
if (!m_documentLoader->overrideEncoding().isEmpty() || frameLoadType == FrameLoadTypeBackForward)
return ReturnCacheDataElseLoad;
if (isReload || frameLoadType == FrameLoadTypeSame || request.isConditional() || request.httpMethod() == "POST")
return ReloadIgnoringCacheData;
if (LocalFrame* parent = frame()->tree().parent())
return parent->document()->fetcher()->resourceRequestCachePolicy(request, type);
return UseProtocolCachePolicy;
}
if (request.isConditional())
return ReloadIgnoringCacheData;
if (m_documentLoader && m_document && !m_document->loadEventFinished()) {
ResourceRequestCachePolicy mainResourceCachePolicy = m_documentLoader->request().cachePolicy();
if (mainResourceCachePolicy == ReturnCacheDataDontLoad)
return ReturnCacheDataElseLoad;
return mainResourceCachePolicy;
}
return UseProtocolCachePolicy;
}
void ResourceFetcher::addAdditionalRequestHeaders(ResourceRequest& request, Resource::Type type)
{
if (!frame())
return;
if (request.cachePolicy() == UseProtocolCachePolicy)
request.setCachePolicy(resourceRequestCachePolicy(request, type));
if (request.targetType() == ResourceRequest::TargetIsUnspecified)
determineTargetType(request, type);
if (type == Resource::LinkPrefetch || type == Resource::LinkSubresource)
request.setHTTPHeaderField("Purpose", "prefetch");
context().addAdditionalRequestHeaders(document(), request, (type == Resource::MainResource) ? FetchMainResource : FetchSubresource);
}
ResourcePtr<Resource> ResourceFetcher::revalidateResource(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<Resource> 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());
storeResourceTimingInitiatorInformation(newResource, request);
TRACE_EVENT_ASYNC_BEGIN2("net", "Resource", newResource.get(), "url", newResource->url().string().ascii(), "priority", newResource->resourceRequest().priority());
return newResource;
}
ResourcePtr<Resource> ResourceFetcher::loadResource(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> resource = createResource(type, request.mutableResourceRequest(), charset);
memoryCache()->add(resource.get());
storeResourceTimingInitiatorInformation(resource, request);
TRACE_EVENT_ASYNC_BEGIN2("net", "Resource", resource.get(), "url", resource->url().string().ascii(), "priority", resource->resourceRequest().priority());
return resource;
}
void ResourceFetcher::storeResourceTimingInitiatorInformation(const ResourcePtr<Resource>& resource, const FetchRequest& request)
{
if (request.options().requestInitiatorContext != DocumentContext)
return;
RefPtr<ResourceTimingInfo> info = ResourceTimingInfo::create(request.options().initiatorInfo.name, monotonicallyIncreasingTime());
if (resource->type() == Resource::MainResource) {
if (frame()->ownerElement() && !frame()->ownerElement()->loadedNonEmptyDocument()) {
info->setInitiatorType(frame()->ownerElement()->localName());
m_resourceTimingInfoMap.add(resource.get(), info);
frame()->ownerElement()->didLoadNonEmptyDocument();
}
} else {
m_resourceTimingInfoMap.add(resource.get(), info);
}
}
ResourceFetcher::RevalidationPolicy ResourceFetcher::determineRevalidationPolicy(Resource::Type type, ResourceRequest& request, bool forPreload, Resource* existingResource, FetchRequest::DeferOption defer, const ResourceLoaderOptions& options) const
{
if (!existingResource)
return Load;
if (forPreload && existingResource->isPreloaded())
return Use;
if (existingResource->type() != type) {
WTF_LOG(ResourceLoading, "ResourceFetcher::determineRevalidationPolicy reloading due to type mismatch.");
return Reload;
}
if (FetchRequest::DeferredByClient == defer)
return Reload;
if (type == Resource::Image && request.url().protocolIsData())
return Use;
if (type == Resource::MainResource && m_documentLoader->substituteData().isValid())
return Use;
if (!existingResource->canReuse(request))
return Reload;
if (request.downloadToFile())
return Reload;
if (request.isConditional())
return Reload;
if (m_allowStaleResources)
return Use;
if (existingResource->isPreloaded())
return Use;
CachePolicy cachePolicy = context().cachePolicy(document());
if (cachePolicy == CachePolicyHistoryBuffer)
return Use;
if (existingResource->response().cacheControlContainsNoStore()) {
WTF_LOG(ResourceLoading, "ResourceFetcher::determineRevalidationPolicy reloading due to Cache-control: no-store.");
return Reload;
}
if (type != Resource::MainResource && options.corsEnabled != existingResource->options().corsEnabled)
return Reload;
if (existingResource->resourceRequest().allowStoredCredentials() != request.allowStoredCredentials()) {
WTF_LOG(ResourceLoading, "ResourceFetcher::determineRevalidationPolicy reloading due to difference in credentials settings.");
return Reload;
}
if (type != Resource::Raw && document() && !document()->loadEventFinished() && m_validatedURLs.contains(existingResource->url()))
return Use;
if (cachePolicy == CachePolicyReload) {
WTF_LOG(ResourceLoading, "ResourceFetcher::determineRevalidationPolicy reloading due to CachePolicyReload.");
return Reload;
}
if (existingResource->errorOccurred()) {
WTF_LOG(ResourceLoading, "ResourceFetcher::determineRevalidationPolicye reloading due to resource being in the error state");
return Reload;
}
if (existingResource->isLoading())
return Use;
if (!existingResource->canReuseRedirectChain()) {
WTF_LOG(ResourceLoading, "ResourceFetcher::determineRevalidationPolicy reloading due to an uncacheable redirect");
return Reload;
}
if (cachePolicy == CachePolicyRevalidate || existingResource->mustRevalidateDueToCacheHeaders()) {
if (existingResource->canUseCacheValidator())
return Revalidate;
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(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
{
return frame() && !frame()->loader().client()->allowImage(m_imagesEnabled, url);
}
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*>(resource)->load(this, defaultResourceOptions());
}
}
void ResourceFetcher::redirectReceived(Resource* resource, const ResourceResponse& redirectResponse)
{
ResourceTimingInfoMap::iterator it = m_resourceTimingInfoMap.find(resource);
if (it != m_resourceTimingInfoMap.end())
it->value->addRedirect(redirectResponse);
}
void ResourceFetcher::didLoadResource(Resource* resource)
{
RefPtr<DocumentLoader> protectDocumentLoader(m_documentLoader);
RefPtr<Document> protectDocument(m_document);
if (resource && resource->response().isHTTP() && ((!resource->errorOccurred() && !resource->wasCanceled()) || resource->response().httpStatusCode() == 304) && document()) {
ResourceTimingInfoMap::iterator it = m_resourceTimingInfoMap.find(resource);
if (it != m_resourceTimingInfoMap.end()) {
RefPtr<ResourceTimingInfo> info = it->value;
m_resourceTimingInfoMap.remove(it);
populateResourceTiming(info.get(), resource, false);
reportResourceTiming(info.get(), document(), resource->type() == Resource::MainResource);
}
}
if (frame())
frame()->loader().loadDone();
scheduleDocumentResourcesGC();
}
void ResourceFetcher::scheduleDocumentResourcesGC()
{
if (!m_garbageCollectDocumentResourcesTimer.isActive())
m_garbageCollectDocumentResourcesTimer.startOneShot(0, FROM_HERE);
}
void ResourceFetcher::garbageCollectDocumentResourcesTimerFired(Timer<ResourceFetcher>* timer)
{
ASSERT_UNUSED(timer, timer == &m_garbageCollectDocumentResourcesTimer);
garbageCollectDocumentResources();
}
void ResourceFetcher::garbageCollectDocumentResources()
{
typedef Vector<String, 10> StringVector;
StringVector resourcesToDelete;
for (DocumentResourceMap::iterator it = m_documentResources.begin(); it != m_documentResources.end(); ++it) {
if (it->value->hasOneHandle())
resourcesToDelete.append(it->key);
}
for (StringVector::const_iterator it = resourcesToDelete.begin(); it != resourcesToDelete.end(); ++it)
m_documentResources.remove(*it);
}
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());
willSendRequest(identifier, request, ResourceResponse(), resource->options().initiatorInfo);
InspectorInstrumentation::markResourceAsCached(frame()->page(), identifier);
context().sendRemainingDelegateMessages(m_documentLoader, 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::preload(Resource::Type type, FetchRequest& request, const String& charset)
{
requestPreload(type, request, charset);
}
void ResourceFetcher::requestPreload(Resource::Type type, FetchRequest& request, const String& charset)
{
String encoding;
if (type == Resource::Script || type == Resource::CSSStyleSheet)
encoding = charset.isEmpty() ? m_document->charset().string() : charset;
request.setCharset(encoding);
request.setForPreload(true);
ResourcePtr<Resource> resource = requestResource(type, request);
if (!resource || (m_preloads && m_preloads->contains(resource.get())))
return;
TRACE_EVENT_ASYNC_STEP_INTO0("net", "Resource", resource.get(), "Preload");
resource->increasePreloadCount();
if (!m_preloads)
m_preloads = adoptPtr(new ListHashSet<Resource*>);
m_preloads->add(resource.get());
#if PRELOAD_DEBUG
printf("PRELOADING %s\n", resource->url().string().latin1().data());
#endif
}
bool ResourceFetcher::isPreloaded(const String& urlString) const
{
const KURL& url = m_document->completeURL(urlString);
if (m_preloads) {
ListHashSet<Resource*>::iterator end = m_preloads->end();
for (ListHashSet<Resource*>::iterator it = m_preloads->begin(); it != end; ++it) {
Resource* resource = *it;
if (resource->url() == url)
return true;
}
}
return false;
}
void ResourceFetcher::clearPreloads()
{
#if PRELOAD_DEBUG
printPreloadStats();
#endif
if (!m_preloads)
return;
ListHashSet<Resource*>::iterator end = m_preloads->end();
for (ListHashSet<Resource*>::iterator it = m_preloads->begin(); it != end; ++it) {
Resource* res = *it;
res->decreasePreloadCount();
bool deleted = res->deleteIfPossible();
if (!deleted && res->preloadResult() == Resource::PreloadNotReferenced)
memoryCache()->remove(res);
}
m_preloads.clear();
}
void ResourceFetcher::didFinishLoading(const Resource* resource, double finishTime, int64_t encodedDataLength)
{
TRACE_EVENT_ASYNC_END0("net", "Resource", resource);
context().dispatchDidFinishLoading(m_documentLoader, 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_documentLoader, resource->identifier(), error);
}
void ResourceFetcher::willSendRequest(unsigned long identifier, ResourceRequest& request, const ResourceResponse& redirectResponse, const FetchInitiatorInfo& initiatorInfo)
{
context().dispatchWillSendRequest(m_documentLoader, identifier, request, redirectResponse, initiatorInfo);
}
void ResourceFetcher::didReceiveResponse(const Resource* resource, const ResourceResponse& response)
{
context().dispatchDidReceiveResponse(m_documentLoader, resource->identifier(), response, resource->loader());
}
void ResourceFetcher::didReceiveData(const Resource* resource, const char* data, int dataLength, int encodedDataLength)
{
context().dispatchDidReceiveData(m_documentLoader, resource->identifier(), data, dataLength, encodedDataLength);
}
void ResourceFetcher::didDownloadData(const Resource* resource, int dataLength, int encodedDataLength)
{
context().dispatchDidDownloadData(m_documentLoader, resource->identifier(), dataLength, encodedDataLength);
}
void ResourceFetcher::subresourceLoaderFinishedLoadingOnePart(ResourceLoader* loader)
{
if (m_multipartLoaders)
m_multipartLoaders->add(loader);
if (m_loaders)
m_loaders->remove(loader);
if (LocalFrame* frame = this->frame())
return frame->loader().checkLoadComplete(m_documentLoader);
}
void ResourceFetcher::didInitializeResourceLoader(ResourceLoader* loader)
{
if (!m_document)
return;
if (!m_loaders)
m_loaders = adoptPtr(new ResourceLoaderSet());
ASSERT(!m_loaders->contains(loader));
m_loaders->add(loader);
}
void ResourceFetcher::willTerminateResourceLoader(ResourceLoader* loader)
{
if (!m_loaders || !m_loaders->contains(loader))
return;
m_loaders->remove(loader);
if (LocalFrame* frame = this->frame())
frame->loader().checkLoadComplete(m_documentLoader);
}
void ResourceFetcher::willStartLoadingResource(ResourceRequest& request)
{
if (m_documentLoader)
m_documentLoader->applicationCacheHost()->willStartLoadingResource(request);
}
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();
}
void ResourceFetcher::setDefersLoading(bool defers)
{
if (m_loaders)
m_loaders->setAllDefersLoading(defers);
}
bool ResourceFetcher::defersLoading() const
{
if (LocalFrame* frame = this->frame())
return frame->page()->defersLoading();
return false;
}
bool ResourceFetcher::isLoadedBy(ResourceLoaderHost* possibleOwner) const
{
return this == possibleOwner;
}
bool ResourceFetcher::canAccessRedirect(Resource* resource, ResourceRequest& request, const ResourceResponse& redirectResponse, ResourceLoaderOptions& options)
{
if (!canRequest(resource->type(), request.url(), options, false, FetchRequest::UseDefaultOriginRestrictionForType))
return false;
if (options.corsEnabled == IsCORSEnabled) {
SecurityOrigin* sourceOrigin = options.securityOrigin.get();
if (!sourceOrigin && document())
sourceOrigin = document()->securityOrigin();
String errorMessage;
if (!CrossOriginAccessControl::handleRedirect(resource, sourceOrigin, request, redirectResponse, options, errorMessage)) {
if (frame() && frame()->document())
frame()->document()->addConsoleMessage(JSMessageSource, ErrorMessageLevel, errorMessage);
return false;
}
}
if (resource->type() == Resource::Image && shouldDeferImageLoad(request.url()))
return false;
return true;
}
void ResourceFetcher::refResourceLoaderHost()
{
ref();
}
void ResourceFetcher::derefResourceLoaderHost()
{
deref();
}
#if PRELOAD_DEBUG
void ResourceFetcher::printPreloadStats()
{
if (!m_preloads)
return;
unsigned scripts = 0;
unsigned scriptMisses = 0;
unsigned stylesheets = 0;
unsigned stylesheetMisses = 0;
unsigned images = 0;
unsigned imageMisses = 0;
ListHashSet<Resource*>::iterator end = m_preloads->end();
for (ListHashSet<Resource*>::iterator it = m_preloads->begin(); it != end; ++it) {
Resource* res = *it;
if (res->preloadResult() == Resource::PreloadNotReferenced)
printf("!! UNREFERENCED PRELOAD %s\n", res->url().string().latin1().data());
else if (res->preloadResult() == Resource::PreloadReferencedWhileComplete)
printf("HIT COMPLETE PRELOAD %s\n", res->url().string().latin1().data());
else if (res->preloadResult() == Resource::PreloadReferencedWhileLoading)
printf("HIT LOADING PRELOAD %s\n", res->url().string().latin1().data());
if (res->type() == Resource::Script) {
scripts++;
if (res->preloadResult() < Resource::PreloadReferencedWhileLoading)
scriptMisses++;
} else if (res->type() == Resource::CSSStyleSheet) {
stylesheets++;
if (res->preloadResult() < Resource::PreloadReferencedWhileLoading)
stylesheetMisses++;
} else {
images++;
if (res->preloadResult() < Resource::PreloadReferencedWhileLoading)
imageMisses++;
}
if (res->errorOccurred())
memoryCache()->remove(res);
res->decreasePreloadCount();
}
m_preloads.clear();
if (scripts)
printf("SCRIPTS: %d (%d hits, hit rate %d%%)\n", scripts, scripts - scriptMisses, (scripts - scriptMisses) * 100 / scripts);
if (stylesheets)
printf("STYLESHEETS: %d (%d hits, hit rate %d%%)\n", stylesheets, stylesheets - stylesheetMisses, (stylesheets - stylesheetMisses) * 100 / stylesheets);
if (images)
printf("IMAGES: %d (%d hits, hit rate %d%%)\n", images, images - imageMisses, (images - imageMisses) * 100 / images);
}
#endif
const ResourceLoaderOptions& ResourceFetcher::defaultResourceOptions()
{
DEFINE_STATIC_LOCAL(ResourceLoaderOptions, options, (SniffContent, BufferData, AllowStoredCredentials, ClientRequestedCredentials, CheckContentSecurityPolicy, 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;
}
}
}