1 /*
2 Copyright (C) 1998 Lars Knoll (knoll@mpi-hd.mpg.de)
3 Copyright (C) 2001 Dirk Mueller (mueller@kde.org)
4 Copyright (C) 2002 Waldo Bastian (bastian@kde.org)
5 Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011 Apple Inc. All rights reserved.
6 Copyright (C) 2009 Torch Mobile Inc. http://www.torchmobile.com/
7
8 This library is free software; you can redistribute it and/or
9 modify it under the terms of the GNU Library General Public
10 License as published by the Free Software Foundation; either
11 version 2 of the License, or (at your option) any later version.
12
13 This library is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 Library General Public License for more details.
17
18 You should have received a copy of the GNU Library General Public License
19 along with this library; see the file COPYING.LIB. If not, write to
20 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
21 Boston, MA 02110-1301, USA.
22
23 This class provides all functionality needed for loading images, style sheets and html
24 pages from the web. It has a memory cache for these objects.
25 */
26
27 #include "config.h"
28 #include "core/fetch/ResourceFetcher.h"
29
30 #include "bindings/core/v8/ScriptController.h"
31 #include "bindings/core/v8/V8DOMActivityLogger.h"
32 #include "core/FetchInitiatorTypeNames.h"
33 #include "core/dom/Document.h"
34 #include "core/fetch/CSSStyleSheetResource.h"
35 #include "core/fetch/CrossOriginAccessControl.h"
36 #include "core/fetch/DocumentResource.h"
37 #include "core/fetch/FetchContext.h"
38 #include "core/fetch/FontResource.h"
39 #include "core/fetch/ImageResource.h"
40 #include "core/fetch/MemoryCache.h"
41 #include "core/fetch/RawResource.h"
42 #include "core/fetch/ResourceLoader.h"
43 #include "core/fetch/ResourceLoaderSet.h"
44 #include "core/fetch/ScriptResource.h"
45 #include "core/fetch/XSLStyleSheetResource.h"
46 #include "core/html/HTMLElement.h"
47 #include "core/html/HTMLFrameOwnerElement.h"
48 #include "core/html/imports/HTMLImportsController.h"
49 #include "core/inspector/ConsoleMessage.h"
50 #include "core/inspector/InspectorInstrumentation.h"
51 #include "core/loader/DocumentLoader.h"
52 #include "core/loader/FrameLoader.h"
53 #include "core/loader/FrameLoaderClient.h"
54 #include "core/loader/PingLoader.h"
55 #include "core/loader/SubstituteData.h"
56 #include "core/loader/UniqueIdentifier.h"
57 #include "core/loader/appcache/ApplicationCacheHost.h"
58 #include "core/frame/LocalDOMWindow.h"
59 #include "core/frame/LocalFrame.h"
60 #include "core/frame/csp/ContentSecurityPolicy.h"
61 #include "core/timing/Performance.h"
62 #include "core/timing/ResourceTimingInfo.h"
63 #include "core/frame/Settings.h"
64 #include "core/svg/graphics/SVGImageChromeClient.h"
65 #include "platform/Logging.h"
66 #include "platform/RuntimeEnabledFeatures.h"
67 #include "platform/TraceEvent.h"
68 #include "platform/weborigin/SchemeRegistry.h"
69 #include "platform/weborigin/SecurityOrigin.h"
70 #include "platform/weborigin/SecurityPolicy.h"
71 #include "public/platform/Platform.h"
72 #include "public/platform/WebURL.h"
73 #include "public/platform/WebURLRequest.h"
74 #include "wtf/text/CString.h"
75 #include "wtf/text/WTFString.h"
76
77 #define PRELOAD_DEBUG 0
78
79 using blink::WebURLRequest;
80
81 namespace blink {
82
createResource(Resource::Type type,const ResourceRequest & request,const String & charset)83 static Resource* createResource(Resource::Type type, const ResourceRequest& request, const String& charset)
84 {
85 switch (type) {
86 case Resource::Image:
87 return new ImageResource(request);
88 case Resource::CSSStyleSheet:
89 return new CSSStyleSheetResource(request, charset);
90 case Resource::Script:
91 return new ScriptResource(request, charset);
92 case Resource::SVGDocument:
93 return new DocumentResource(request, Resource::SVGDocument);
94 case Resource::Font:
95 return new FontResource(request);
96 case Resource::MainResource:
97 case Resource::Raw:
98 case Resource::TextTrack:
99 case Resource::Media:
100 return new RawResource(request, type);
101 case Resource::XSLStyleSheet:
102 return new XSLStyleSheetResource(request, charset);
103 case Resource::LinkPrefetch:
104 return new Resource(request, Resource::LinkPrefetch);
105 case Resource::LinkSubresource:
106 return new Resource(request, Resource::LinkSubresource);
107 case Resource::ImportResource:
108 return new RawResource(request, type);
109 }
110
111 ASSERT_NOT_REACHED();
112 return 0;
113 }
114
loadPriority(Resource::Type type,const FetchRequest & request)115 static ResourceLoadPriority loadPriority(Resource::Type type, const FetchRequest& request)
116 {
117 if (request.priority() != ResourceLoadPriorityUnresolved)
118 return request.priority();
119
120 switch (type) {
121 case Resource::MainResource:
122 return ResourceLoadPriorityVeryHigh;
123 case Resource::CSSStyleSheet:
124 return ResourceLoadPriorityHigh;
125 case Resource::Raw:
126 return request.options().synchronousPolicy == RequestSynchronously ? ResourceLoadPriorityVeryHigh : ResourceLoadPriorityMedium;
127 case Resource::Script:
128 // Async scripts do not block the parser so they get the lowest priority and can be
129 // loaded in parser order with images.
130 if (FetchRequest::LazyLoad == request.defer())
131 return ResourceLoadPriorityLow;
132 return ResourceLoadPriorityMedium;
133 case Resource::Font:
134 case Resource::ImportResource:
135 return ResourceLoadPriorityMedium;
136 case Resource::Image:
137 // Default images to VeryLow, and promote whatever is visible. This improves
138 // speed-index by ~5% on average, ~14% at the 99th percentile.
139 return ResourceLoadPriorityVeryLow;
140 case Resource::Media:
141 return ResourceLoadPriorityLow;
142 case Resource::XSLStyleSheet:
143 ASSERT(RuntimeEnabledFeatures::xsltEnabled());
144 return ResourceLoadPriorityHigh;
145 case Resource::SVGDocument:
146 return ResourceLoadPriorityLow;
147 case Resource::LinkPrefetch:
148 return ResourceLoadPriorityVeryLow;
149 case Resource::LinkSubresource:
150 return ResourceLoadPriorityLow;
151 case Resource::TextTrack:
152 return ResourceLoadPriorityLow;
153 }
154 ASSERT_NOT_REACHED();
155 return ResourceLoadPriorityUnresolved;
156 }
157
resourceFromDataURIRequest(const ResourceRequest & request,const ResourceLoaderOptions & resourceOptions)158 static Resource* resourceFromDataURIRequest(const ResourceRequest& request, const ResourceLoaderOptions& resourceOptions)
159 {
160 const KURL& url = request.url();
161 ASSERT(url.protocolIsData());
162
163 blink::WebString mimetype;
164 blink::WebString charset;
165 RefPtr<SharedBuffer> data = PassRefPtr<SharedBuffer>(blink::Platform::current()->parseDataURL(url, mimetype, charset));
166 if (!data)
167 return 0;
168 ResourceResponse response(url, mimetype, data->size(), charset, String());
169
170 Resource* resource = createResource(Resource::Image, request, charset);
171 resource->setOptions(resourceOptions);
172 resource->responseReceived(response);
173 if (data->size())
174 resource->setResourceBuffer(data);
175 resource->finish();
176 return resource;
177 }
178
populateResourceTiming(ResourceTimingInfo * info,Resource * resource,bool clearLoadTimings)179 static void populateResourceTiming(ResourceTimingInfo* info, Resource* resource, bool clearLoadTimings)
180 {
181 info->setInitialRequest(resource->resourceRequest());
182 info->setFinalResponse(resource->response());
183 if (clearLoadTimings) {
184 info->clearLoadTimings();
185 info->setLoadFinishTime(info->initialTime());
186 } else {
187 info->setLoadFinishTime(resource->loadFinishTime());
188 }
189 }
190
reportResourceTiming(ResourceTimingInfo * info,Document * initiatorDocument,bool isMainResource)191 static void reportResourceTiming(ResourceTimingInfo* info, Document* initiatorDocument, bool isMainResource)
192 {
193 if (initiatorDocument && isMainResource)
194 initiatorDocument = initiatorDocument->parentDocument();
195 if (!initiatorDocument || !initiatorDocument->loader())
196 return;
197 if (LocalDOMWindow* initiatorWindow = initiatorDocument->domWindow())
198 initiatorWindow->performance().addResourceTiming(*info, initiatorDocument);
199 }
200
requestContextFromType(const ResourceFetcher * fetcher,Resource::Type type)201 static WebURLRequest::RequestContext requestContextFromType(const ResourceFetcher* fetcher, Resource::Type type)
202 {
203 switch (type) {
204 case Resource::MainResource:
205 if (fetcher->frame()->tree().parent())
206 return WebURLRequest::RequestContextIframe;
207 // FIXME: Change this to a context frame type (once we introduce them): http://fetch.spec.whatwg.org/#concept-request-context-frame-type
208 return WebURLRequest::RequestContextHyperlink;
209 case Resource::XSLStyleSheet:
210 ASSERT(RuntimeEnabledFeatures::xsltEnabled());
211 case Resource::CSSStyleSheet:
212 return WebURLRequest::RequestContextStyle;
213 case Resource::Script:
214 return WebURLRequest::RequestContextScript;
215 case Resource::Font:
216 return WebURLRequest::RequestContextFont;
217 case Resource::Image:
218 return WebURLRequest::RequestContextImage;
219 case Resource::Raw:
220 return WebURLRequest::RequestContextSubresource;
221 case Resource::ImportResource:
222 return WebURLRequest::RequestContextImport;
223 case Resource::LinkPrefetch:
224 return WebURLRequest::RequestContextPrefetch;
225 case Resource::LinkSubresource:
226 return WebURLRequest::RequestContextSubresource;
227 case Resource::TextTrack:
228 return WebURLRequest::RequestContextTrack;
229 case Resource::SVGDocument:
230 return WebURLRequest::RequestContextImage;
231 case Resource::Media: // TODO: Split this.
232 return WebURLRequest::RequestContextVideo;
233 }
234 ASSERT_NOT_REACHED();
235 return WebURLRequest::RequestContextSubresource;
236 }
237
memoryCachePolicyToResourceRequestCachePolicy(const CachePolicy policy)238 static ResourceRequestCachePolicy memoryCachePolicyToResourceRequestCachePolicy(
239 const CachePolicy policy) {
240 if (policy == CachePolicyVerify)
241 return UseProtocolCachePolicy;
242 if (policy == CachePolicyRevalidate)
243 return ReloadIgnoringCacheData;
244 if (policy == CachePolicyReload)
245 return ReloadBypassingCache;
246 if (policy == CachePolicyHistoryBuffer)
247 return ReturnCacheDataElseLoad;
248 return UseProtocolCachePolicy;
249 }
250
ResourceFetcher(DocumentLoader * documentLoader)251 ResourceFetcher::ResourceFetcher(DocumentLoader* documentLoader)
252 : m_document(nullptr)
253 , m_documentLoader(documentLoader)
254 , m_requestCount(0)
255 , m_garbageCollectDocumentResourcesTimer(this, &ResourceFetcher::garbageCollectDocumentResourcesTimerFired)
256 , m_resourceTimingReportTimer(this, &ResourceFetcher::resourceTimingReportTimerFired)
257 , m_autoLoadImages(true)
258 , m_imagesEnabled(true)
259 , m_allowStaleResources(false)
260 {
261 }
262
~ResourceFetcher()263 ResourceFetcher::~ResourceFetcher()
264 {
265 m_documentLoader = 0;
266 m_document = nullptr;
267
268 clearPreloads();
269
270 // Make sure no requests still point to this ResourceFetcher
271 ASSERT(!m_requestCount);
272 }
273
cachedResource(const KURL & resourceURL) const274 Resource* ResourceFetcher::cachedResource(const KURL& resourceURL) const
275 {
276 KURL url = MemoryCache::removeFragmentIdentifierIfNeeded(resourceURL);
277 return m_documentResources.get(url).get();
278 }
279
frame() const280 LocalFrame* ResourceFetcher::frame() const
281 {
282 if (m_documentLoader)
283 return m_documentLoader->frame();
284 if (m_document && m_document->importsController())
285 return m_document->importsController()->master()->frame();
286 return 0;
287 }
288
context() const289 FetchContext& ResourceFetcher::context() const
290 {
291 if (LocalFrame* frame = this->frame())
292 return frame->fetchContext();
293 return FetchContext::nullInstance();
294 }
295
fetchSynchronously(FetchRequest & request)296 ResourcePtr<Resource> ResourceFetcher::fetchSynchronously(FetchRequest& request)
297 {
298 ASSERT(document());
299 request.mutableResourceRequest().setTimeoutInterval(10);
300 ResourceLoaderOptions options(request.options());
301 options.synchronousPolicy = RequestSynchronously;
302 request.setOptions(options);
303 return requestResource(Resource::Raw, request);
304 }
305
fetchImage(FetchRequest & request)306 ResourcePtr<ImageResource> ResourceFetcher::fetchImage(FetchRequest& request)
307 {
308 if (request.resourceRequest().requestContext() == WebURLRequest::RequestContextUnspecified)
309 request.mutableResourceRequest().setRequestContext(WebURLRequest::RequestContextImage);
310 if (LocalFrame* f = frame()) {
311 if (f->document()->pageDismissalEventBeingDispatched() != Document::NoDismissal) {
312 KURL requestURL = request.resourceRequest().url();
313 if (requestURL.isValid() && canRequest(Resource::Image, request.resourceRequest(), requestURL, request.options(), request.forPreload(), request.originRestriction()))
314 PingLoader::loadImage(f, requestURL);
315 return 0;
316 }
317 }
318
319 if (request.resourceRequest().url().protocolIsData())
320 preCacheDataURIImage(request);
321
322 request.setDefer(clientDefersImage(request.resourceRequest().url()) ? FetchRequest::DeferredByClient : FetchRequest::NoDefer);
323 ResourcePtr<Resource> resource = requestResource(Resource::Image, request);
324 return resource && resource->type() == Resource::Image ? toImageResource(resource) : 0;
325 }
326
preCacheDataURIImage(const FetchRequest & request)327 void ResourceFetcher::preCacheDataURIImage(const FetchRequest& request)
328 {
329 const KURL& url = request.resourceRequest().url();
330 ASSERT(url.protocolIsData());
331
332 if (memoryCache()->resourceForURL(url))
333 return;
334
335 if (Resource* resource = resourceFromDataURIRequest(request.resourceRequest(), request.options())) {
336 memoryCache()->add(resource);
337 scheduleDocumentResourcesGC();
338 }
339 }
340
fetchFont(FetchRequest & request)341 ResourcePtr<FontResource> ResourceFetcher::fetchFont(FetchRequest& request)
342 {
343 ASSERT(request.resourceRequest().frameType() == WebURLRequest::FrameTypeNone);
344 request.mutableResourceRequest().setRequestContext(WebURLRequest::RequestContextFont);
345 return toFontResource(requestResource(Resource::Font, request));
346 }
347
fetchImport(FetchRequest & request)348 ResourcePtr<RawResource> ResourceFetcher::fetchImport(FetchRequest& request)
349 {
350 ASSERT(request.resourceRequest().frameType() == WebURLRequest::FrameTypeNone);
351 request.mutableResourceRequest().setRequestContext(WebURLRequest::RequestContextImport);
352 return toRawResource(requestResource(Resource::ImportResource, request));
353 }
354
fetchCSSStyleSheet(FetchRequest & request)355 ResourcePtr<CSSStyleSheetResource> ResourceFetcher::fetchCSSStyleSheet(FetchRequest& request)
356 {
357 ASSERT(request.resourceRequest().frameType() == WebURLRequest::FrameTypeNone);
358 request.mutableResourceRequest().setRequestContext(WebURLRequest::RequestContextStyle);
359 return toCSSStyleSheetResource(requestResource(Resource::CSSStyleSheet, request));
360 }
361
fetchScript(FetchRequest & request)362 ResourcePtr<ScriptResource> ResourceFetcher::fetchScript(FetchRequest& request)
363 {
364 ASSERT(request.resourceRequest().frameType() == WebURLRequest::FrameTypeNone);
365 request.mutableResourceRequest().setRequestContext(WebURLRequest::RequestContextScript);
366 return toScriptResource(requestResource(Resource::Script, request));
367 }
368
fetchXSLStyleSheet(FetchRequest & request)369 ResourcePtr<XSLStyleSheetResource> ResourceFetcher::fetchXSLStyleSheet(FetchRequest& request)
370 {
371 ASSERT(RuntimeEnabledFeatures::xsltEnabled());
372 request.mutableResourceRequest().setRequestContext(WebURLRequest::RequestContextXSLT);
373 return toXSLStyleSheetResource(requestResource(Resource::XSLStyleSheet, request));
374 }
375
fetchSVGDocument(FetchRequest & request)376 ResourcePtr<DocumentResource> ResourceFetcher::fetchSVGDocument(FetchRequest& request)
377 {
378 ASSERT(request.resourceRequest().frameType() == WebURLRequest::FrameTypeNone);
379 request.mutableResourceRequest().setRequestContext(WebURLRequest::RequestContextImage);
380 return toDocumentResource(requestResource(Resource::SVGDocument, request));
381 }
382
fetchLinkResource(Resource::Type type,FetchRequest & request)383 ResourcePtr<Resource> ResourceFetcher::fetchLinkResource(Resource::Type type, FetchRequest& request)
384 {
385 ASSERT(frame());
386 ASSERT(type == Resource::LinkPrefetch || type == Resource::LinkSubresource);
387 ASSERT(request.resourceRequest().frameType() == WebURLRequest::FrameTypeNone);
388 request.mutableResourceRequest().setRequestContext(type == Resource::LinkPrefetch ? WebURLRequest::RequestContextPrefetch : WebURLRequest::RequestContextSubresource);
389 return requestResource(type, request);
390 }
391
fetchRawResource(FetchRequest & request)392 ResourcePtr<RawResource> ResourceFetcher::fetchRawResource(FetchRequest& request)
393 {
394 ASSERT(request.resourceRequest().frameType() == WebURLRequest::FrameTypeNone);
395 ASSERT(request.resourceRequest().requestContext() != WebURLRequest::RequestContextUnspecified);
396 return toRawResource(requestResource(Resource::Raw, request));
397 }
398
fetchMainResource(FetchRequest & request,const SubstituteData & substituteData)399 ResourcePtr<RawResource> ResourceFetcher::fetchMainResource(FetchRequest& request, const SubstituteData& substituteData)
400 {
401 ASSERT(request.resourceRequest().frameType() != WebURLRequest::FrameTypeNone);
402 ASSERT(request.resourceRequest().requestContext() == WebURLRequest::RequestContextForm || request.resourceRequest().requestContext() == WebURLRequest::RequestContextFrame || request.resourceRequest().requestContext() == WebURLRequest::RequestContextHyperlink || request.resourceRequest().requestContext() == WebURLRequest::RequestContextIframe || request.resourceRequest().requestContext() == WebURLRequest::RequestContextInternal || request.resourceRequest().requestContext() == WebURLRequest::RequestContextLocation);
403
404 if (substituteData.isValid())
405 preCacheSubstituteDataForMainResource(request, substituteData);
406 return toRawResource(requestResource(Resource::MainResource, request));
407 }
408
fetchMedia(FetchRequest & request)409 ResourcePtr<RawResource> ResourceFetcher::fetchMedia(FetchRequest& request)
410 {
411 ASSERT(request.resourceRequest().frameType() == WebURLRequest::FrameTypeNone);
412 // FIXME: Split this into audio and video.
413 request.mutableResourceRequest().setRequestContext(WebURLRequest::RequestContextVideo);
414 return toRawResource(requestResource(Resource::Media, request));
415 }
416
fetchTextTrack(FetchRequest & request)417 ResourcePtr<RawResource> ResourceFetcher::fetchTextTrack(FetchRequest& request)
418 {
419 ASSERT(request.resourceRequest().frameType() == WebURLRequest::FrameTypeNone);
420 request.mutableResourceRequest().setRequestContext(WebURLRequest::RequestContextTrack);
421 return toRawResource(requestResource(Resource::TextTrack, request));
422 }
423
preCacheSubstituteDataForMainResource(const FetchRequest & request,const SubstituteData & substituteData)424 void ResourceFetcher::preCacheSubstituteDataForMainResource(const FetchRequest& request, const SubstituteData& substituteData)
425 {
426 const KURL& url = request.url();
427 if (Resource* oldResource = memoryCache()->resourceForURL(url))
428 memoryCache()->remove(oldResource);
429
430 ResourceResponse response(url, substituteData.mimeType(), substituteData.content()->size(), substituteData.textEncoding(), emptyString());
431 ResourcePtr<Resource> resource = createResource(Resource::MainResource, request.resourceRequest(), substituteData.textEncoding());
432 resource->setNeedsSynchronousCacheHit(substituteData.forceSynchronousLoad());
433 resource->setOptions(request.options());
434 resource->setDataBufferingPolicy(BufferData);
435 resource->responseReceived(response);
436 if (substituteData.content()->size())
437 resource->setResourceBuffer(substituteData.content());
438 resource->finish();
439 memoryCache()->add(resource.get());
440 }
441
canRequest(Resource::Type type,const ResourceRequest & resourceRequest,const KURL & url,const ResourceLoaderOptions & options,bool forPreload,FetchRequest::OriginRestriction originRestriction) const442 bool ResourceFetcher::canRequest(Resource::Type type, const ResourceRequest& resourceRequest, const KURL& url, const ResourceLoaderOptions& options, bool forPreload, FetchRequest::OriginRestriction originRestriction) const
443 {
444 SecurityOrigin* securityOrigin = options.securityOrigin.get();
445 if (!securityOrigin && document())
446 securityOrigin = document()->securityOrigin();
447
448 if (originRestriction != FetchRequest::NoOriginRestriction && securityOrigin && !securityOrigin->canDisplay(url)) {
449 if (!forPreload)
450 context().reportLocalLoadFailed(url);
451 WTF_LOG(ResourceLoading, "ResourceFetcher::requestResource URL was not allowed by SecurityOrigin::canDisplay");
452 return 0;
453 }
454
455 // Some types of resources can be loaded only from the same origin. Other
456 // types of resources, like Images, Scripts, and CSS, can be loaded from
457 // any URL.
458 switch (type) {
459 case Resource::MainResource:
460 case Resource::Image:
461 case Resource::CSSStyleSheet:
462 case Resource::Script:
463 case Resource::Font:
464 case Resource::Raw:
465 case Resource::LinkPrefetch:
466 case Resource::LinkSubresource:
467 case Resource::TextTrack:
468 case Resource::ImportResource:
469 case Resource::Media:
470 // By default these types of resources can be loaded from any origin.
471 // FIXME: Are we sure about Resource::Font?
472 if (originRestriction == FetchRequest::RestrictToSameOrigin && !securityOrigin->canRequest(url)) {
473 printAccessDeniedMessage(url);
474 return false;
475 }
476 break;
477 case Resource::XSLStyleSheet:
478 ASSERT(RuntimeEnabledFeatures::xsltEnabled());
479 case Resource::SVGDocument:
480 if (!securityOrigin->canRequest(url)) {
481 printAccessDeniedMessage(url);
482 return false;
483 }
484 break;
485 }
486
487 // FIXME: Convert this to check the isolated world's Content Security Policy once webkit.org/b/104520 is solved.
488 bool shouldBypassMainWorldCSP = (frame() && frame()->script().shouldBypassMainWorldCSP()) || (options.contentSecurityPolicyOption == DoNotCheckContentSecurityPolicy);
489
490 // Don't send CSP messages for preloads, we might never actually display those items.
491 ContentSecurityPolicy::ReportingStatus cspReporting = forPreload ?
492 ContentSecurityPolicy::SuppressReport : ContentSecurityPolicy::SendReport;
493
494 // m_document can be null, but not in any of the cases where csp is actually used below.
495 // ImageResourceTest.MultipartImage crashes w/o the m_document null check.
496 // I believe it's the Resource::Raw case.
497 const ContentSecurityPolicy* csp = m_document ? m_document->contentSecurityPolicy() : nullptr;
498
499 // FIXME: This would be cleaner if moved this switch into an allowFromSource()
500 // helper on this object which took a Resource::Type, then this block would
501 // collapse to about 10 lines for handling Raw and Script special cases.
502 switch (type) {
503 case Resource::XSLStyleSheet:
504 ASSERT(RuntimeEnabledFeatures::xsltEnabled());
505 if (!shouldBypassMainWorldCSP && !csp->allowScriptFromSource(url, cspReporting))
506 return false;
507 break;
508 case Resource::Script:
509 case Resource::ImportResource:
510 if (!shouldBypassMainWorldCSP && !csp->allowScriptFromSource(url, cspReporting))
511 return false;
512
513 if (frame()) {
514 Settings* settings = frame()->settings();
515 if (!frame()->loader().client()->allowScriptFromSource(!settings || settings->scriptEnabled(), url)) {
516 frame()->loader().client()->didNotAllowScript();
517 return false;
518 }
519 }
520 break;
521 case Resource::CSSStyleSheet:
522 if (!shouldBypassMainWorldCSP && !csp->allowStyleFromSource(url, cspReporting))
523 return false;
524 break;
525 case Resource::SVGDocument:
526 case Resource::Image:
527 if (!shouldBypassMainWorldCSP && !csp->allowImageFromSource(url, cspReporting))
528 return false;
529 break;
530 case Resource::Font: {
531 if (!shouldBypassMainWorldCSP && !csp->allowFontFromSource(url, cspReporting))
532 return false;
533 break;
534 }
535 case Resource::MainResource:
536 case Resource::Raw:
537 case Resource::LinkPrefetch:
538 case Resource::LinkSubresource:
539 break;
540 case Resource::Media:
541 case Resource::TextTrack:
542 if (!shouldBypassMainWorldCSP && !csp->allowMediaFromSource(url, cspReporting))
543 return false;
544
545 if (frame()) {
546 if (!frame()->loader().client()->allowMedia(url))
547 return false;
548 }
549 break;
550 }
551
552 // SVG Images have unique security rules that prevent all subresource requests
553 // except for data urls.
554 if (type != Resource::MainResource) {
555 if (frame() && frame()->chromeClient().isSVGImageChromeClient() && !url.protocolIsData())
556 return false;
557 }
558
559 // Measure the number of legacy URL schemes ('ftp://') and the number of embedded-credential
560 // ('http://user:password@...') resources embedded as subresources. in the hopes that we can
561 // block them at some point in the future.
562 if (resourceRequest.frameType() != WebURLRequest::FrameTypeTopLevel) {
563 if (SchemeRegistry::shouldTreatURLSchemeAsLegacy(url.protocol()) && !SchemeRegistry::shouldTreatURLSchemeAsLegacy(frame()->document()->securityOrigin()->protocol()))
564 UseCounter::count(frame()->document(), UseCounter::LegacyProtocolEmbeddedAsSubresource);
565 if (!url.user().isEmpty() || !url.pass().isEmpty())
566 UseCounter::count(frame()->document(), UseCounter::RequestedSubresourceWithEmbeddedCredentials);
567 }
568
569 // Last of all, check for mixed content. We do this last so that when
570 // folks block mixed content with a CSP policy, they don't get a warning.
571 // They'll still get a warning in the console about CSP blocking the load.
572
573 // If we're loading the main resource of a subframe, ensure that we check
574 // against the parent of the active frame, rather than the frame itself.
575 LocalFrame* effectiveFrame = frame();
576 if (resourceRequest.frameType() == WebURLRequest::FrameTypeNested) {
577 // FIXME: Deal with RemoteFrames.
578 if (frame()->tree().parent()->isLocalFrame())
579 effectiveFrame = toLocalFrame(frame()->tree().parent());
580 }
581
582 return !MixedContentChecker::shouldBlockFetch(effectiveFrame, resourceRequest, url);
583 }
584
canAccessResource(Resource * resource,SecurityOrigin * sourceOrigin,const KURL & url) const585 bool ResourceFetcher::canAccessResource(Resource* resource, SecurityOrigin* sourceOrigin, const KURL& url) const
586 {
587 // Redirects can change the response URL different from one of request.
588 if (!canRequest(resource->type(), resource->resourceRequest(), url, resource->options(), resource->isUnusedPreload(), FetchRequest::UseDefaultOriginRestrictionForType))
589 return false;
590
591 if (!sourceOrigin && document())
592 sourceOrigin = document()->securityOrigin();
593
594 if (sourceOrigin->canRequest(url))
595 return true;
596
597 String errorDescription;
598 if (!resource->passesAccessControlCheck(sourceOrigin, errorDescription)) {
599 if (resource->type() == Resource::Font)
600 toFontResource(resource)->setCORSFailed();
601 if (frame() && frame()->document()) {
602 String resourceType = Resource::resourceTypeToString(resource->type(), resource->options().initiatorInfo);
603 frame()->document()->addConsoleMessage(ConsoleMessage::create(JSMessageSource, ErrorMessageLevel, resourceType + " from origin '" + SecurityOrigin::create(url)->toString() + "' has been blocked from loading by Cross-Origin Resource Sharing policy: " + errorDescription));
604 }
605 return false;
606 }
607 return true;
608 }
609
shouldLoadNewResource(Resource::Type type) const610 bool ResourceFetcher::shouldLoadNewResource(Resource::Type type) const
611 {
612 if (!frame())
613 return false;
614 if (!m_documentLoader)
615 return true;
616 if (type == Resource::MainResource)
617 return m_documentLoader == frame()->loader().provisionalDocumentLoader();
618 return m_documentLoader == frame()->loader().documentLoader();
619 }
620
resourceNeedsLoad(Resource * resource,const FetchRequest & request,RevalidationPolicy policy)621 bool ResourceFetcher::resourceNeedsLoad(Resource* resource, const FetchRequest& request, RevalidationPolicy policy)
622 {
623 if (FetchRequest::DeferredByClient == request.defer())
624 return false;
625 if (policy != Use)
626 return true;
627 if (resource->stillNeedsLoad())
628 return true;
629 return request.options().synchronousPolicy == RequestSynchronously && resource->isLoading();
630 }
631
requestLoadStarted(Resource * resource,const FetchRequest & request,ResourceLoadStartType type)632 void ResourceFetcher::requestLoadStarted(Resource* resource, const FetchRequest& request, ResourceLoadStartType type)
633 {
634 if (type == ResourceLoadingFromCache)
635 notifyLoadedFromMemoryCache(resource);
636
637 if (request.resourceRequest().url().protocolIsData() || (m_documentLoader && m_documentLoader->substituteData().isValid()))
638 return;
639
640 if (type == ResourceLoadingFromCache && !resource->stillNeedsLoad() && !m_validatedURLs.contains(request.resourceRequest().url())) {
641 // Resources loaded from memory cache should be reported the first time they're used.
642 RefPtr<ResourceTimingInfo> info = ResourceTimingInfo::create(request.options().initiatorInfo.name, monotonicallyIncreasingTime());
643 populateResourceTiming(info.get(), resource, true);
644 m_scheduledResourceTimingReports.add(info, resource->type() == Resource::MainResource);
645 if (!m_resourceTimingReportTimer.isActive())
646 m_resourceTimingReportTimer.startOneShot(0, FROM_HERE);
647 }
648
649 m_validatedURLs.add(request.resourceRequest().url());
650 }
651
requestResource(Resource::Type type,FetchRequest & request)652 ResourcePtr<Resource> ResourceFetcher::requestResource(Resource::Type type, FetchRequest& request)
653 {
654 ASSERT(request.options().synchronousPolicy == RequestAsynchronously || type == Resource::Raw);
655
656 TRACE_EVENT0("blink", "ResourceFetcher::requestResource");
657
658 KURL url = request.resourceRequest().url();
659
660 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));
661
662 // If only the fragment identifiers differ, it is the same resource.
663 url = MemoryCache::removeFragmentIdentifierIfNeeded(url);
664
665 if (!url.isValid())
666 return 0;
667
668 if (!canRequest(type, request.resourceRequest(), url, request.options(), request.forPreload(), request.originRestriction()))
669 return 0;
670
671 if (LocalFrame* f = frame())
672 f->loader().client()->dispatchWillRequestResource(&request);
673
674 if (!request.forPreload()) {
675 V8DOMActivityLogger* activityLogger = 0;
676 if (request.options().initiatorInfo.name == FetchInitiatorTypeNames::xmlhttprequest)
677 activityLogger = V8DOMActivityLogger::currentActivityLogger();
678 else
679 activityLogger = V8DOMActivityLogger::currentActivityLoggerIfIsolatedWorld();
680
681 if (activityLogger) {
682 Vector<String> argv;
683 argv.append(Resource::resourceTypeToString(type, request.options().initiatorInfo));
684 argv.append(url);
685 activityLogger->logEvent("blinkRequestResource", argv.size(), argv.data());
686 }
687 }
688
689 // See if we can use an existing resource from the cache.
690 ResourcePtr<Resource> resource = memoryCache()->resourceForURL(url);
691
692 const RevalidationPolicy policy = determineRevalidationPolicy(type, request, resource.get());
693 switch (policy) {
694 case Reload:
695 memoryCache()->remove(resource.get());
696 // Fall through
697 case Load:
698 resource = createResourceForLoading(type, request, request.charset());
699 break;
700 case Revalidate:
701 resource = createResourceForRevalidation(request, resource.get());
702 break;
703 case Use:
704 memoryCache()->updateForAccess(resource.get());
705 break;
706 }
707
708 if (!resource)
709 return 0;
710
711 if (!resource->hasClients())
712 m_deadStatsRecorder.update(policy);
713
714 if (policy != Use)
715 resource->setIdentifier(createUniqueIdentifier());
716
717 if (!request.forPreload() || policy != Use) {
718 ResourceLoadPriority priority = loadPriority(type, request);
719 if (priority != resource->resourceRequest().priority()) {
720 resource->mutableResourceRequest().setPriority(priority);
721 resource->didChangePriority(priority, 0);
722 }
723 }
724
725 if (resourceNeedsLoad(resource.get(), request, policy)) {
726 if (!shouldLoadNewResource(type)) {
727 if (memoryCache()->contains(resource.get()))
728 memoryCache()->remove(resource.get());
729 return 0;
730 }
731
732 if (!m_documentLoader || !m_documentLoader->scheduleArchiveLoad(resource.get(), request.resourceRequest()))
733 resource->load(this, request.options());
734
735 // For asynchronous loads that immediately fail, it's sufficient to return a
736 // null Resource, as it indicates that something prevented the load from starting.
737 // If there's a network error, that failure will happen asynchronously. However, if
738 // a sync load receives a network error, it will have already happened by this point.
739 // In that case, the requester should have access to the relevant ResourceError, so
740 // we need to return a non-null Resource.
741 if (resource->errorOccurred()) {
742 if (memoryCache()->contains(resource.get()))
743 memoryCache()->remove(resource.get());
744 return request.options().synchronousPolicy == RequestSynchronously ? resource : 0;
745 }
746 }
747
748 // FIXME: Temporarily leave main resource caching disabled for chromium,
749 // see https://bugs.webkit.org/show_bug.cgi?id=107962. Before caching main
750 // resources, we should be sure to understand the implications for memory
751 // use.
752 // Remove main resource from cache to prevent reuse.
753 if (type == Resource::MainResource) {
754 ASSERT(policy != Use || m_documentLoader->substituteData().isValid());
755 ASSERT(policy != Revalidate);
756 memoryCache()->remove(resource.get());
757 }
758
759 requestLoadStarted(resource.get(), request, policy == Use ? ResourceLoadingFromCache : ResourceLoadingFromNetwork);
760
761 ASSERT(resource->url() == url.string());
762 m_documentResources.set(resource->url(), resource);
763 return resource;
764 }
765
resourceTimingReportTimerFired(Timer<ResourceFetcher> * timer)766 void ResourceFetcher::resourceTimingReportTimerFired(Timer<ResourceFetcher>* timer)
767 {
768 ASSERT_UNUSED(timer, timer == &m_resourceTimingReportTimer);
769 HashMap<RefPtr<ResourceTimingInfo>, bool> timingReports;
770 timingReports.swap(m_scheduledResourceTimingReports);
771 HashMap<RefPtr<ResourceTimingInfo>, bool>::iterator end = timingReports.end();
772 for (HashMap<RefPtr<ResourceTimingInfo>, bool>::iterator it = timingReports.begin(); it != end; ++it) {
773 RefPtr<ResourceTimingInfo> info = it->key;
774 bool isMainResource = it->value;
775 reportResourceTiming(info.get(), document(), isMainResource);
776 }
777 }
778
determineRequestContext(ResourceRequest & request,Resource::Type type)779 void ResourceFetcher::determineRequestContext(ResourceRequest& request, Resource::Type type)
780 {
781 WebURLRequest::RequestContext requestContext = requestContextFromType(this, type);
782 request.setRequestContext(requestContext);
783 }
784
resourceRequestCachePolicy(const ResourceRequest & request,Resource::Type type)785 ResourceRequestCachePolicy ResourceFetcher::resourceRequestCachePolicy(const ResourceRequest& request, Resource::Type type)
786 {
787 if (type == Resource::MainResource) {
788 FrameLoadType frameLoadType = frame()->loader().loadType();
789 if (request.httpMethod() == "POST" && frameLoadType == FrameLoadTypeBackForward)
790 return ReturnCacheDataDontLoad;
791 if (!m_documentLoader->overrideEncoding().isEmpty() || frameLoadType == FrameLoadTypeBackForward)
792 return ReturnCacheDataElseLoad;
793 if (frameLoadType == FrameLoadTypeReloadFromOrigin)
794 return ReloadBypassingCache;
795 if (frameLoadType == FrameLoadTypeReload || frameLoadType == FrameLoadTypeSame || request.isConditional() || request.httpMethod() == "POST")
796 return ReloadIgnoringCacheData;
797 Frame* parent = frame()->tree().parent();
798 if (parent && parent->isLocalFrame())
799 return toLocalFrame(parent)->document()->fetcher()->resourceRequestCachePolicy(request, type);
800 return UseProtocolCachePolicy;
801 }
802
803 if (request.isConditional())
804 return ReloadIgnoringCacheData;
805
806 if (m_documentLoader && m_document && !m_document->loadEventFinished()) {
807 // For POST requests, we mutate the main resource's cache policy to avoid form resubmission.
808 // This policy should not be inherited by subresources.
809 ResourceRequestCachePolicy mainResourceCachePolicy = m_documentLoader->request().cachePolicy();
810 if (m_documentLoader->request().httpMethod() == "POST") {
811 if (mainResourceCachePolicy == ReturnCacheDataDontLoad)
812 return ReturnCacheDataElseLoad;
813 return UseProtocolCachePolicy;
814 }
815 return memoryCachePolicyToResourceRequestCachePolicy(context().cachePolicy(m_document));
816 }
817 return UseProtocolCachePolicy;
818 }
819
addAdditionalRequestHeaders(ResourceRequest & request,Resource::Type type)820 void ResourceFetcher::addAdditionalRequestHeaders(ResourceRequest& request, Resource::Type type)
821 {
822 if (!frame())
823 return;
824
825 if (request.cachePolicy() == UseProtocolCachePolicy)
826 request.setCachePolicy(resourceRequestCachePolicy(request, type));
827 if (request.requestContext() == WebURLRequest::RequestContextUnspecified)
828 determineRequestContext(request, type);
829 if (type == Resource::LinkPrefetch || type == Resource::LinkSubresource)
830 request.setHTTPHeaderField("Purpose", "prefetch");
831
832 context().addAdditionalRequestHeaders(document(), request, (type == Resource::MainResource) ? FetchMainResource : FetchSubresource);
833 }
834
createResourceForRevalidation(const FetchRequest & request,Resource * resource)835 ResourcePtr<Resource> ResourceFetcher::createResourceForRevalidation(const FetchRequest& request, Resource* resource)
836 {
837 ASSERT(resource);
838 ASSERT(memoryCache()->contains(resource));
839 ASSERT(resource->isLoaded());
840 ASSERT(resource->canUseCacheValidator());
841 ASSERT(!resource->resourceToRevalidate());
842
843 ResourceRequest revalidatingRequest(resource->resourceRequest());
844 revalidatingRequest.clearHTTPReferrer();
845 addAdditionalRequestHeaders(revalidatingRequest, resource->type());
846
847 const AtomicString& lastModified = resource->response().httpHeaderField("Last-Modified");
848 const AtomicString& eTag = resource->response().httpHeaderField("ETag");
849 if (!lastModified.isEmpty() || !eTag.isEmpty()) {
850 ASSERT(context().cachePolicy(document()) != CachePolicyReload);
851 if (context().cachePolicy(document()) == CachePolicyRevalidate)
852 revalidatingRequest.setHTTPHeaderField("Cache-Control", "max-age=0");
853 }
854 if (!lastModified.isEmpty())
855 revalidatingRequest.setHTTPHeaderField("If-Modified-Since", lastModified);
856 if (!eTag.isEmpty())
857 revalidatingRequest.setHTTPHeaderField("If-None-Match", eTag);
858
859 ResourcePtr<Resource> newResource = createResource(resource->type(), revalidatingRequest, resource->encoding());
860 WTF_LOG(ResourceLoading, "Resource %p created to revalidate %p", newResource.get(), resource);
861
862 newResource->setResourceToRevalidate(resource);
863
864 memoryCache()->remove(resource);
865 memoryCache()->add(newResource.get());
866 return newResource;
867 }
868
createResourceForLoading(Resource::Type type,FetchRequest & request,const String & charset)869 ResourcePtr<Resource> ResourceFetcher::createResourceForLoading(Resource::Type type, FetchRequest& request, const String& charset)
870 {
871 ASSERT(!memoryCache()->resourceForURL(request.resourceRequest().url()));
872
873 WTF_LOG(ResourceLoading, "Loading Resource for '%s'.", request.resourceRequest().url().elidedString().latin1().data());
874
875 addAdditionalRequestHeaders(request.mutableResourceRequest(), type);
876 ResourcePtr<Resource> resource = createResource(type, request.resourceRequest(), charset);
877
878 memoryCache()->add(resource.get());
879 return resource;
880 }
881
storeResourceTimingInitiatorInformation(Resource * resource)882 void ResourceFetcher::storeResourceTimingInitiatorInformation(Resource* resource)
883 {
884 if (resource->options().requestInitiatorContext != DocumentContext)
885 return;
886
887 RefPtr<ResourceTimingInfo> info = ResourceTimingInfo::create(resource->options().initiatorInfo.name, monotonicallyIncreasingTime());
888
889 if (resource->isCacheValidator()) {
890 const AtomicString& timingAllowOrigin = resource->resourceToRevalidate()->response().httpHeaderField("Timing-Allow-Origin");
891 if (!timingAllowOrigin.isEmpty())
892 info->setOriginalTimingAllowOrigin(timingAllowOrigin);
893 }
894
895 if (resource->type() == Resource::MainResource) {
896 // <iframe>s should report the initial navigation requested by the parent document, but not subsequent navigations.
897 // FIXME: Resource timing is broken when the parent is a remote frame.
898 if (frame()->deprecatedLocalOwner() && !frame()->deprecatedLocalOwner()->loadedNonEmptyDocument()) {
899 info->setInitiatorType(frame()->deprecatedLocalOwner()->localName());
900 m_resourceTimingInfoMap.add(resource, info);
901 frame()->deprecatedLocalOwner()->didLoadNonEmptyDocument();
902 }
903 } else {
904 m_resourceTimingInfoMap.add(resource, info);
905 }
906 }
907
determineRevalidationPolicy(Resource::Type type,const FetchRequest & fetchRequest,Resource * existingResource) const908 ResourceFetcher::RevalidationPolicy ResourceFetcher::determineRevalidationPolicy(Resource::Type type, const FetchRequest& fetchRequest, Resource* existingResource) const
909 {
910 const ResourceRequest& request = fetchRequest.resourceRequest();
911
912 if (!existingResource)
913 return Load;
914
915 // We already have a preload going for this URL.
916 if (fetchRequest.forPreload() && existingResource->isPreloaded())
917 return Use;
918
919 // If the same URL has been loaded as a different type, we need to reload.
920 if (existingResource->type() != type) {
921 // FIXME: If existingResource is a Preload and the new type is LinkPrefetch
922 // We really should discard the new prefetch since the preload has more
923 // specific type information! crbug.com/379893
924 // fast/dom/HTMLLinkElement/link-and-subresource-test hits this case.
925 WTF_LOG(ResourceLoading, "ResourceFetcher::determineRevalidationPolicy reloading due to type mismatch.");
926 return Reload;
927 }
928
929 // Do not load from cache if images are not enabled. The load for this image will be blocked
930 // in ImageResource::load.
931 if (FetchRequest::DeferredByClient == fetchRequest.defer())
932 return Reload;
933
934 // Always use data uris.
935 // FIXME: Extend this to non-images.
936 if (type == Resource::Image && request.url().protocolIsData())
937 return Use;
938
939 // If a main resource was populated from a SubstituteData load, use it.
940 if (type == Resource::MainResource && m_documentLoader->substituteData().isValid())
941 return Use;
942
943 if (!existingResource->canReuse(request))
944 return Reload;
945
946 // Never use cache entries for downloadToFile requests. The caller expects the resource in a file.
947 if (request.downloadToFile())
948 return Reload;
949
950 // Certain requests (e.g., XHRs) might have manually set headers that require revalidation.
951 // FIXME: In theory, this should be a Revalidate case. In practice, the MemoryCache revalidation path assumes a whole bunch
952 // of things about how revalidation works that manual headers violate, so punt to Reload instead.
953 if (request.isConditional())
954 return Reload;
955
956 // Don't reload resources while pasting.
957 if (m_allowStaleResources)
958 return Use;
959
960 if (!fetchRequest.options().canReuseRequest(existingResource->options()))
961 return Reload;
962
963 // Always use preloads.
964 if (existingResource->isPreloaded())
965 return Use;
966
967 // CachePolicyHistoryBuffer uses the cache no matter what.
968 CachePolicy cachePolicy = context().cachePolicy(document());
969 if (cachePolicy == CachePolicyHistoryBuffer)
970 return Use;
971
972 // Don't reuse resources with Cache-control: no-store.
973 if (existingResource->hasCacheControlNoStoreHeader()) {
974 WTF_LOG(ResourceLoading, "ResourceFetcher::determineRevalidationPolicy reloading due to Cache-control: no-store.");
975 return Reload;
976 }
977
978 // If credentials were sent with the previous request and won't be
979 // with this one, or vice versa, re-fetch the resource.
980 //
981 // This helps with the case where the server sends back
982 // "Access-Control-Allow-Origin: *" all the time, but some of the
983 // client's requests are made without CORS and some with.
984 if (existingResource->resourceRequest().allowStoredCredentials() != request.allowStoredCredentials()) {
985 WTF_LOG(ResourceLoading, "ResourceFetcher::determineRevalidationPolicy reloading due to difference in credentials settings.");
986 return Reload;
987 }
988
989 // During the initial load, avoid loading the same resource multiple times for a single document,
990 // even if the cache policies would tell us to.
991 // We also group loads of the same resource together.
992 // Raw resources are exempted, as XHRs fall into this category and may have user-set Cache-Control:
993 // headers or other factors that require separate requests.
994 if (type != Resource::Raw) {
995 if (document() && !document()->loadEventFinished() && m_validatedURLs.contains(existingResource->url()))
996 return Use;
997 if (existingResource->isLoading())
998 return Use;
999 }
1000
1001 // CachePolicyReload always reloads
1002 if (cachePolicy == CachePolicyReload) {
1003 WTF_LOG(ResourceLoading, "ResourceFetcher::determineRevalidationPolicy reloading due to CachePolicyReload.");
1004 return Reload;
1005 }
1006
1007 // We'll try to reload the resource if it failed last time.
1008 if (existingResource->errorOccurred()) {
1009 WTF_LOG(ResourceLoading, "ResourceFetcher::determineRevalidationPolicye reloading due to resource being in the error state");
1010 return Reload;
1011 }
1012
1013 // List of available images logic allows images to be re-used without cache validation. We restrict this only to images
1014 // from memory cache which are the same as the version in the current document.
1015 if (type == Resource::Image && existingResource == cachedResource(request.url()))
1016 return Use;
1017
1018 // If any of the redirects in the chain to loading the resource were not cacheable, we cannot reuse our cached resource.
1019 if (!existingResource->canReuseRedirectChain()) {
1020 WTF_LOG(ResourceLoading, "ResourceFetcher::determineRevalidationPolicy reloading due to an uncacheable redirect");
1021 return Reload;
1022 }
1023
1024 // Check if the cache headers requires us to revalidate (cache expiration for example).
1025 if (cachePolicy == CachePolicyRevalidate || existingResource->mustRevalidateDueToCacheHeaders()
1026 || request.cacheControlContainsNoCache()) {
1027 // See if the resource has usable ETag or Last-modified headers.
1028 if (existingResource->canUseCacheValidator())
1029 return Revalidate;
1030
1031 // No, must reload.
1032 WTF_LOG(ResourceLoading, "ResourceFetcher::determineRevalidationPolicy reloading due to missing cache validators.");
1033 return Reload;
1034 }
1035
1036 return Use;
1037 }
1038
printAccessDeniedMessage(const KURL & url) const1039 void ResourceFetcher::printAccessDeniedMessage(const KURL& url) const
1040 {
1041 if (url.isNull())
1042 return;
1043
1044 if (!frame())
1045 return;
1046
1047 String message;
1048 if (!m_document || m_document->url().isNull())
1049 message = "Unsafe attempt to load URL " + url.elidedString() + '.';
1050 else
1051 message = "Unsafe attempt to load URL " + url.elidedString() + " from frame with URL " + m_document->url().elidedString() + ". Domains, protocols and ports must match.\n";
1052
1053 frame()->document()->addConsoleMessage(ConsoleMessage::create(SecurityMessageSource, ErrorMessageLevel, message));
1054 }
1055
setAutoLoadImages(bool enable)1056 void ResourceFetcher::setAutoLoadImages(bool enable)
1057 {
1058 if (enable == m_autoLoadImages)
1059 return;
1060
1061 m_autoLoadImages = enable;
1062
1063 if (!m_autoLoadImages)
1064 return;
1065
1066 reloadImagesIfNotDeferred();
1067 }
1068
setImagesEnabled(bool enable)1069 void ResourceFetcher::setImagesEnabled(bool enable)
1070 {
1071 if (enable == m_imagesEnabled)
1072 return;
1073
1074 m_imagesEnabled = enable;
1075
1076 if (!m_imagesEnabled)
1077 return;
1078
1079 reloadImagesIfNotDeferred();
1080 }
1081
clientDefersImage(const KURL & url) const1082 bool ResourceFetcher::clientDefersImage(const KURL& url) const
1083 {
1084 return frame() && !frame()->loader().client()->allowImage(m_imagesEnabled, url);
1085 }
1086
shouldDeferImageLoad(const KURL & url) const1087 bool ResourceFetcher::shouldDeferImageLoad(const KURL& url) const
1088 {
1089 return clientDefersImage(url) || !m_autoLoadImages;
1090 }
1091
reloadImagesIfNotDeferred()1092 void ResourceFetcher::reloadImagesIfNotDeferred()
1093 {
1094 DocumentResourceMap::iterator end = m_documentResources.end();
1095 for (DocumentResourceMap::iterator it = m_documentResources.begin(); it != end; ++it) {
1096 Resource* resource = it->value.get();
1097 if (resource->type() == Resource::Image && resource->stillNeedsLoad() && !clientDefersImage(resource->url()))
1098 const_cast<Resource*>(resource)->load(this, defaultResourceOptions());
1099 }
1100 }
1101
redirectReceived(Resource * resource,const ResourceResponse & redirectResponse)1102 void ResourceFetcher::redirectReceived(Resource* resource, const ResourceResponse& redirectResponse)
1103 {
1104 ResourceTimingInfoMap::iterator it = m_resourceTimingInfoMap.find(resource);
1105 if (it != m_resourceTimingInfoMap.end())
1106 it->value->addRedirect(redirectResponse);
1107 }
1108
didLoadResource(Resource * resource)1109 void ResourceFetcher::didLoadResource(Resource* resource)
1110 {
1111 RefPtr<DocumentLoader> protectDocumentLoader(m_documentLoader);
1112 RefPtrWillBeRawPtr<Document> protectDocument(m_document.get());
1113
1114 if (resource && resource->response().isHTTP() && ((!resource->errorOccurred() && !resource->wasCanceled()) || resource->response().httpStatusCode() == 304) && document()) {
1115 ResourceTimingInfoMap::iterator it = m_resourceTimingInfoMap.find(resource);
1116 if (it != m_resourceTimingInfoMap.end()) {
1117 RefPtr<ResourceTimingInfo> info = it->value;
1118 m_resourceTimingInfoMap.remove(it);
1119 populateResourceTiming(info.get(), resource, false);
1120 reportResourceTiming(info.get(), document(), resource->type() == Resource::MainResource);
1121 }
1122 }
1123
1124 if (frame())
1125 frame()->loader().loadDone();
1126 scheduleDocumentResourcesGC();
1127 }
1128
scheduleDocumentResourcesGC()1129 void ResourceFetcher::scheduleDocumentResourcesGC()
1130 {
1131 if (!m_garbageCollectDocumentResourcesTimer.isActive())
1132 m_garbageCollectDocumentResourcesTimer.startOneShot(0, FROM_HERE);
1133 }
1134
1135 // Garbage collecting m_documentResources is a workaround for the
1136 // ResourcePtrs on the RHS being strong references. Ideally this
1137 // would be a weak map, however ResourcePtrs perform additional
1138 // bookkeeping on Resources, so instead pseudo-GC them -- when the
1139 // reference count reaches 1, m_documentResources is the only reference, so
1140 // remove it from the map.
garbageCollectDocumentResourcesTimerFired(Timer<ResourceFetcher> * timer)1141 void ResourceFetcher::garbageCollectDocumentResourcesTimerFired(Timer<ResourceFetcher>* timer)
1142 {
1143 ASSERT_UNUSED(timer, timer == &m_garbageCollectDocumentResourcesTimer);
1144 garbageCollectDocumentResources();
1145 }
1146
garbageCollectDocumentResources()1147 void ResourceFetcher::garbageCollectDocumentResources()
1148 {
1149 typedef Vector<String, 10> StringVector;
1150 StringVector resourcesToDelete;
1151
1152 for (DocumentResourceMap::iterator it = m_documentResources.begin(); it != m_documentResources.end(); ++it) {
1153 if (it->value->hasOneHandle())
1154 resourcesToDelete.append(it->key);
1155 }
1156
1157 m_documentResources.removeAll(resourcesToDelete);
1158 }
1159
notifyLoadedFromMemoryCache(Resource * resource)1160 void ResourceFetcher::notifyLoadedFromMemoryCache(Resource* resource)
1161 {
1162 if (!frame() || !frame()->page() || resource->status() != Resource::Cached || m_validatedURLs.contains(resource->url()))
1163 return;
1164
1165 ResourceRequest request(resource->url());
1166 unsigned long identifier = createUniqueIdentifier();
1167 context().dispatchDidLoadResourceFromMemoryCache(request, resource->response());
1168 // FIXME: If willSendRequest changes the request, we don't respect it.
1169 willSendRequest(identifier, request, ResourceResponse(), resource->options().initiatorInfo);
1170 InspectorInstrumentation::markResourceAsCached(frame()->page(), identifier);
1171 context().sendRemainingDelegateMessages(m_documentLoader, identifier, resource->response(), resource->encodedSize());
1172 }
1173
incrementRequestCount(const Resource * res)1174 void ResourceFetcher::incrementRequestCount(const Resource* res)
1175 {
1176 if (res->ignoreForRequestCount())
1177 return;
1178
1179 ++m_requestCount;
1180 }
1181
decrementRequestCount(const Resource * res)1182 void ResourceFetcher::decrementRequestCount(const Resource* res)
1183 {
1184 if (res->ignoreForRequestCount())
1185 return;
1186
1187 --m_requestCount;
1188 ASSERT(m_requestCount > -1);
1189 }
1190
preload(Resource::Type type,FetchRequest & request,const String & charset)1191 void ResourceFetcher::preload(Resource::Type type, FetchRequest& request, const String& charset)
1192 {
1193 requestPreload(type, request, charset);
1194 }
1195
requestPreload(Resource::Type type,FetchRequest & request,const String & charset)1196 void ResourceFetcher::requestPreload(Resource::Type type, FetchRequest& request, const String& charset)
1197 {
1198 // Ensure main resources aren't preloaded, since the cache can't actually reuse the preload.
1199 if (type == Resource::MainResource)
1200 return;
1201
1202 String encoding;
1203 if (type == Resource::Script || type == Resource::CSSStyleSheet)
1204 encoding = charset.isEmpty() ? m_document->charset().string() : charset;
1205
1206 request.setCharset(encoding);
1207 request.setForPreload(true);
1208
1209 ResourcePtr<Resource> resource;
1210 // Loading images involves several special cases, so use dedicated fetch method instead.
1211 if (type == Resource::Image)
1212 resource = fetchImage(request);
1213 if (!resource)
1214 resource = requestResource(type, request);
1215 if (!resource || (m_preloads && m_preloads->contains(resource.get())))
1216 return;
1217 TRACE_EVENT_ASYNC_STEP_INTO0("net", "Resource", resource.get(), "Preload");
1218 resource->increasePreloadCount();
1219
1220 if (!m_preloads)
1221 m_preloads = adoptPtr(new ListHashSet<Resource*>);
1222 m_preloads->add(resource.get());
1223
1224 #if PRELOAD_DEBUG
1225 printf("PRELOADING %s\n", resource->url().string().latin1().data());
1226 #endif
1227 }
1228
isPreloaded(const String & urlString) const1229 bool ResourceFetcher::isPreloaded(const String& urlString) const
1230 {
1231 const KURL& url = m_document->completeURL(urlString);
1232
1233 if (m_preloads) {
1234 ListHashSet<Resource*>::iterator end = m_preloads->end();
1235 for (ListHashSet<Resource*>::iterator it = m_preloads->begin(); it != end; ++it) {
1236 Resource* resource = *it;
1237 if (resource->url() == url)
1238 return true;
1239 }
1240 }
1241
1242 return false;
1243 }
1244
clearPreloads()1245 void ResourceFetcher::clearPreloads()
1246 {
1247 #if PRELOAD_DEBUG
1248 printPreloadStats();
1249 #endif
1250 if (!m_preloads)
1251 return;
1252
1253 ListHashSet<Resource*>::iterator end = m_preloads->end();
1254 for (ListHashSet<Resource*>::iterator it = m_preloads->begin(); it != end; ++it) {
1255 Resource* res = *it;
1256 res->decreasePreloadCount();
1257 bool deleted = res->deleteIfPossible();
1258 if (!deleted && res->preloadResult() == Resource::PreloadNotReferenced)
1259 memoryCache()->remove(res);
1260 }
1261 m_preloads.clear();
1262 }
1263
didFinishLoading(const Resource * resource,double finishTime,int64_t encodedDataLength)1264 void ResourceFetcher::didFinishLoading(const Resource* resource, double finishTime, int64_t encodedDataLength)
1265 {
1266 TRACE_EVENT_ASYNC_END0("net", "Resource", resource);
1267 context().dispatchDidFinishLoading(m_documentLoader, resource->identifier(), finishTime, encodedDataLength);
1268 }
1269
didChangeLoadingPriority(const Resource * resource,ResourceLoadPriority loadPriority,int intraPriorityValue)1270 void ResourceFetcher::didChangeLoadingPriority(const Resource* resource, ResourceLoadPriority loadPriority, int intraPriorityValue)
1271 {
1272 TRACE_EVENT_ASYNC_STEP_INTO1("net", "Resource", resource, "ChangePriority", "priority", loadPriority);
1273 context().dispatchDidChangeResourcePriority(resource->identifier(), loadPriority, intraPriorityValue);
1274 }
1275
didFailLoading(const Resource * resource,const ResourceError & error)1276 void ResourceFetcher::didFailLoading(const Resource* resource, const ResourceError& error)
1277 {
1278 TRACE_EVENT_ASYNC_END0("net", "Resource", resource);
1279 context().dispatchDidFail(m_documentLoader, resource->identifier(), error);
1280 }
1281
willSendRequest(unsigned long identifier,ResourceRequest & request,const ResourceResponse & redirectResponse,const FetchInitiatorInfo & initiatorInfo)1282 void ResourceFetcher::willSendRequest(unsigned long identifier, ResourceRequest& request, const ResourceResponse& redirectResponse, const FetchInitiatorInfo& initiatorInfo)
1283 {
1284 context().dispatchWillSendRequest(m_documentLoader, identifier, request, redirectResponse, initiatorInfo);
1285 }
1286
didReceiveResponse(const Resource * resource,const ResourceResponse & response)1287 void ResourceFetcher::didReceiveResponse(const Resource* resource, const ResourceResponse& response)
1288 {
1289 MixedContentChecker::checkMixedPrivatePublic(frame(), response.remoteIPAddress());
1290
1291 // If the response is fetched via ServiceWorker, the original URL of the response could be different from the URL of the request.
1292 if (response.wasFetchedViaServiceWorker()) {
1293 if (!canRequest(resource->type(), resource->resourceRequest(), response.url(), resource->options(), false, FetchRequest::UseDefaultOriginRestrictionForType)) {
1294 resource->loader()->cancel();
1295 context().dispatchDidFail(m_documentLoader, resource->identifier(), ResourceError(errorDomainBlinkInternal, 0, response.url().string(), "Unsafe attempt to load URL " + response.url().elidedString() + " fetched by a ServiceWorker."));
1296 return;
1297 }
1298 }
1299 context().dispatchDidReceiveResponse(m_documentLoader, resource->identifier(), response, resource->loader());
1300 }
1301
didReceiveData(const Resource * resource,const char * data,int dataLength,int encodedDataLength)1302 void ResourceFetcher::didReceiveData(const Resource* resource, const char* data, int dataLength, int encodedDataLength)
1303 {
1304 context().dispatchDidReceiveData(m_documentLoader, resource->identifier(), data, dataLength, encodedDataLength);
1305 }
1306
didDownloadData(const Resource * resource,int dataLength,int encodedDataLength)1307 void ResourceFetcher::didDownloadData(const Resource* resource, int dataLength, int encodedDataLength)
1308 {
1309 context().dispatchDidDownloadData(m_documentLoader, resource->identifier(), dataLength, encodedDataLength);
1310 }
1311
subresourceLoaderFinishedLoadingOnePart(ResourceLoader * loader)1312 void ResourceFetcher::subresourceLoaderFinishedLoadingOnePart(ResourceLoader* loader)
1313 {
1314 if (!m_multipartLoaders)
1315 m_multipartLoaders = ResourceLoaderSet::create();
1316 m_multipartLoaders->add(loader);
1317 m_loaders->remove(loader);
1318 if (LocalFrame* frame = this->frame())
1319 return frame->loader().checkLoadComplete();
1320 }
1321
didInitializeResourceLoader(ResourceLoader * loader)1322 void ResourceFetcher::didInitializeResourceLoader(ResourceLoader* loader)
1323 {
1324 if (!m_document)
1325 return;
1326 if (!m_loaders)
1327 m_loaders = ResourceLoaderSet::create();
1328 ASSERT(!m_loaders->contains(loader));
1329 m_loaders->add(loader);
1330 }
1331
willTerminateResourceLoader(ResourceLoader * loader)1332 void ResourceFetcher::willTerminateResourceLoader(ResourceLoader* loader)
1333 {
1334 if (m_loaders && m_loaders->contains(loader))
1335 m_loaders->remove(loader);
1336 if (m_multipartLoaders && m_multipartLoaders->contains(loader))
1337 m_multipartLoaders->remove(loader);
1338 if (LocalFrame* frame = this->frame())
1339 frame->loader().checkLoadComplete();
1340 }
1341
willStartLoadingResource(Resource * resource,ResourceRequest & request)1342 void ResourceFetcher::willStartLoadingResource(Resource* resource, ResourceRequest& request)
1343 {
1344 if (m_documentLoader)
1345 m_documentLoader->applicationCacheHost()->willStartLoadingResource(request);
1346
1347 storeResourceTimingInitiatorInformation(resource);
1348 TRACE_EVENT_ASYNC_BEGIN2("net", "Resource", resource, "url", resource->url().string().ascii(), "priority", resource->resourceRequest().priority());
1349 }
1350
stopFetching()1351 void ResourceFetcher::stopFetching()
1352 {
1353 if (m_multipartLoaders)
1354 m_multipartLoaders->cancelAll();
1355 if (m_loaders)
1356 m_loaders->cancelAll();
1357 }
1358
isFetching() const1359 bool ResourceFetcher::isFetching() const
1360 {
1361 return m_loaders && !m_loaders->isEmpty();
1362 }
1363
setDefersLoading(bool defers)1364 void ResourceFetcher::setDefersLoading(bool defers)
1365 {
1366 if (m_loaders)
1367 m_loaders->setAllDefersLoading(defers);
1368 }
1369
defersLoading() const1370 bool ResourceFetcher::defersLoading() const
1371 {
1372 if (LocalFrame* frame = this->frame())
1373 return frame->page()->defersLoading();
1374 return false;
1375 }
1376
isLoadedBy(ResourceLoaderHost * possibleOwner) const1377 bool ResourceFetcher::isLoadedBy(ResourceLoaderHost* possibleOwner) const
1378 {
1379 return this == possibleOwner;
1380 }
1381
canAccessRedirect(Resource * resource,ResourceRequest & request,const ResourceResponse & redirectResponse,ResourceLoaderOptions & options)1382 bool ResourceFetcher::canAccessRedirect(Resource* resource, ResourceRequest& request, const ResourceResponse& redirectResponse, ResourceLoaderOptions& options)
1383 {
1384 if (!canRequest(resource->type(), request, request.url(), options, resource->isUnusedPreload(), FetchRequest::UseDefaultOriginRestrictionForType))
1385 return false;
1386 if (options.corsEnabled == IsCORSEnabled) {
1387 SecurityOrigin* sourceOrigin = options.securityOrigin.get();
1388 if (!sourceOrigin && document())
1389 sourceOrigin = document()->securityOrigin();
1390
1391 String errorMessage;
1392 if (!CrossOriginAccessControl::handleRedirect(resource, sourceOrigin, request, redirectResponse, options, errorMessage)) {
1393 if (resource->type() == Resource::Font)
1394 toFontResource(resource)->setCORSFailed();
1395 if (frame() && frame()->document())
1396 frame()->document()->addConsoleMessage(ConsoleMessage::create(JSMessageSource, ErrorMessageLevel, errorMessage));
1397 return false;
1398 }
1399 }
1400 if (resource->type() == Resource::Image && shouldDeferImageLoad(request.url()))
1401 return false;
1402 return true;
1403 }
1404
1405 #if !ENABLE(OILPAN)
refResourceLoaderHost()1406 void ResourceFetcher::refResourceLoaderHost()
1407 {
1408 ref();
1409 }
1410
derefResourceLoaderHost()1411 void ResourceFetcher::derefResourceLoaderHost()
1412 {
1413 deref();
1414 }
1415 #endif
1416
1417 #if PRELOAD_DEBUG
printPreloadStats()1418 void ResourceFetcher::printPreloadStats()
1419 {
1420 if (!m_preloads)
1421 return;
1422
1423 unsigned scripts = 0;
1424 unsigned scriptMisses = 0;
1425 unsigned stylesheets = 0;
1426 unsigned stylesheetMisses = 0;
1427 unsigned images = 0;
1428 unsigned imageMisses = 0;
1429 ListHashSet<Resource*>::iterator end = m_preloads->end();
1430 for (ListHashSet<Resource*>::iterator it = m_preloads->begin(); it != end; ++it) {
1431 Resource* res = *it;
1432 if (res->preloadResult() == Resource::PreloadNotReferenced)
1433 printf("!! UNREFERENCED PRELOAD %s\n", res->url().string().latin1().data());
1434 else if (res->preloadResult() == Resource::PreloadReferencedWhileComplete)
1435 printf("HIT COMPLETE PRELOAD %s\n", res->url().string().latin1().data());
1436 else if (res->preloadResult() == Resource::PreloadReferencedWhileLoading)
1437 printf("HIT LOADING PRELOAD %s\n", res->url().string().latin1().data());
1438
1439 if (res->type() == Resource::Script) {
1440 scripts++;
1441 if (res->preloadResult() < Resource::PreloadReferencedWhileLoading)
1442 scriptMisses++;
1443 } else if (res->type() == Resource::CSSStyleSheet) {
1444 stylesheets++;
1445 if (res->preloadResult() < Resource::PreloadReferencedWhileLoading)
1446 stylesheetMisses++;
1447 } else {
1448 images++;
1449 if (res->preloadResult() < Resource::PreloadReferencedWhileLoading)
1450 imageMisses++;
1451 }
1452
1453 if (res->errorOccurred())
1454 memoryCache()->remove(res);
1455
1456 res->decreasePreloadCount();
1457 }
1458 m_preloads.clear();
1459
1460 if (scripts)
1461 printf("SCRIPTS: %d (%d hits, hit rate %d%%)\n", scripts, scripts - scriptMisses, (scripts - scriptMisses) * 100 / scripts);
1462 if (stylesheets)
1463 printf("STYLESHEETS: %d (%d hits, hit rate %d%%)\n", stylesheets, stylesheets - stylesheetMisses, (stylesheets - stylesheetMisses) * 100 / stylesheets);
1464 if (images)
1465 printf("IMAGES: %d (%d hits, hit rate %d%%)\n", images, images - imageMisses, (images - imageMisses) * 100 / images);
1466 }
1467 #endif
1468
defaultResourceOptions()1469 const ResourceLoaderOptions& ResourceFetcher::defaultResourceOptions()
1470 {
1471 DEFINE_STATIC_LOCAL(ResourceLoaderOptions, options, (BufferData, AllowStoredCredentials, ClientRequestedCredentials, CheckContentSecurityPolicy, DocumentContext));
1472 return options;
1473 }
1474
DeadResourceStatsRecorder()1475 ResourceFetcher::DeadResourceStatsRecorder::DeadResourceStatsRecorder()
1476 : m_useCount(0)
1477 , m_revalidateCount(0)
1478 , m_loadCount(0)
1479 {
1480 }
1481
~DeadResourceStatsRecorder()1482 ResourceFetcher::DeadResourceStatsRecorder::~DeadResourceStatsRecorder()
1483 {
1484 blink::Platform::current()->histogramCustomCounts(
1485 "WebCore.ResourceFetcher.HitCount", m_useCount, 0, 1000, 50);
1486 blink::Platform::current()->histogramCustomCounts(
1487 "WebCore.ResourceFetcher.RevalidateCount", m_revalidateCount, 0, 1000, 50);
1488 blink::Platform::current()->histogramCustomCounts(
1489 "WebCore.ResourceFetcher.LoadCount", m_loadCount, 0, 1000, 50);
1490 }
1491
update(RevalidationPolicy policy)1492 void ResourceFetcher::DeadResourceStatsRecorder::update(RevalidationPolicy policy)
1493 {
1494 switch (policy) {
1495 case Reload:
1496 case Load:
1497 ++m_loadCount;
1498 return;
1499 case Revalidate:
1500 ++m_revalidateCount;
1501 return;
1502 case Use:
1503 ++m_useCount;
1504 return;
1505 }
1506 }
1507
trace(Visitor * visitor)1508 void ResourceFetcher::trace(Visitor* visitor)
1509 {
1510 visitor->trace(m_document);
1511 visitor->trace(m_loaders);
1512 visitor->trace(m_multipartLoaders);
1513 ResourceLoaderHost::trace(visitor);
1514 }
1515
1516 }
1517