Subclass CachedFont for SVG fonts
[WebKit-https.git] / Source / WebCore / loader / cache / CachedResourceLoader.cpp
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 "CachedResourceLoader.h"
29
30 #include "CachedCSSStyleSheet.h"
31 #include "CachedSVGDocument.h"
32 #include "CachedFont.h"
33 #include "CachedImage.h"
34 #include "CachedRawResource.h"
35 #include "CachedResourceRequest.h"
36 #include "CachedSVGFont.h"
37 #include "CachedScript.h"
38 #include "CachedXSLStyleSheet.h"
39 #include "Chrome.h"
40 #include "ChromeClient.h"
41 #include "ContentSecurityPolicy.h"
42 #include "DOMWindow.h"
43 #include "Document.h"
44 #include "DocumentLoader.h"
45 #include "Frame.h"
46 #include "FrameLoader.h"
47 #include "FrameLoaderClient.h"
48 #include "HTMLElement.h"
49 #include "HTMLFrameOwnerElement.h"
50 #include "LoaderStrategy.h"
51 #include "Logging.h"
52 #include "MemoryCache.h"
53 #include "Page.h"
54 #include "PingLoader.h"
55 #include "PlatformStrategies.h"
56 #include "RenderElement.h"
57 #include "ResourceLoadScheduler.h"
58 #include "ScriptController.h"
59 #include "SecurityOrigin.h"
60 #include "SessionID.h"
61 #include "Settings.h"
62 #include <wtf/text/CString.h>
63 #include <wtf/text/WTFString.h>
64
65 #if ENABLE(VIDEO_TRACK)
66 #include "CachedTextTrack.h"
67 #endif
68
69 #if ENABLE(RESOURCE_TIMING)
70 #include "Performance.h"
71 #endif
72
73 #define PRELOAD_DEBUG 0
74
75 namespace WebCore {
76
77 static CachedResource* createResource(CachedResource::Type type, ResourceRequest& request, const String& charset, SessionID sessionID)
78 {
79     switch (type) {
80     case CachedResource::ImageResource:
81         return new CachedImage(request, sessionID);
82     case CachedResource::CSSStyleSheet:
83         return new CachedCSSStyleSheet(request, charset, sessionID);
84     case CachedResource::Script:
85         return new CachedScript(request, charset, sessionID);
86     case CachedResource::SVGDocumentResource:
87         return new CachedSVGDocument(request, sessionID);
88 #if ENABLE(SVG_FONTS)
89     case CachedResource::SVGFontResource:
90         return new CachedSVGFont(request, sessionID);
91 #endif
92     case CachedResource::FontResource:
93         return new CachedFont(request, sessionID);
94     case CachedResource::RawResource:
95     case CachedResource::MainResource:
96         return new CachedRawResource(request, type, sessionID);
97 #if ENABLE(XSLT)
98     case CachedResource::XSLStyleSheet:
99         return new CachedXSLStyleSheet(request, sessionID);
100 #endif
101 #if ENABLE(LINK_PREFETCH)
102     case CachedResource::LinkPrefetch:
103         return new CachedResource(request, CachedResource::LinkPrefetch, sessionID);
104     case CachedResource::LinkSubresource:
105         return new CachedResource(request, CachedResource::LinkSubresource, sessionID);
106 #endif
107 #if ENABLE(VIDEO_TRACK)
108     case CachedResource::TextTrackResource:
109         return new CachedTextTrack(request, sessionID);
110 #endif
111     }
112     ASSERT_NOT_REACHED();
113     return 0;
114 }
115
116 CachedResourceLoader::CachedResourceLoader(DocumentLoader* documentLoader)
117     : m_document(0)
118     , m_documentLoader(documentLoader)
119     , m_requestCount(0)
120     , m_garbageCollectDocumentResourcesTimer(this, &CachedResourceLoader::garbageCollectDocumentResourcesTimerFired)
121     , m_autoLoadImages(true)
122     , m_imagesEnabled(true)
123     , m_allowStaleResources(false)
124 {
125 }
126
127 CachedResourceLoader::~CachedResourceLoader()
128 {
129     m_documentLoader = 0;
130     m_document = 0;
131
132     clearPreloads();
133     DocumentResourceMap::iterator end = m_documentResources.end();
134     for (DocumentResourceMap::iterator it = m_documentResources.begin(); it != end; ++it)
135         it->value->setOwningCachedResourceLoader(0);
136
137     // Make sure no requests still point to this CachedResourceLoader
138     ASSERT(m_requestCount == 0);
139 }
140
141 CachedResource* CachedResourceLoader::cachedResource(const String& resourceURL) const 
142 {
143     URL url = m_document->completeURL(resourceURL);
144     return cachedResource(url); 
145 }
146
147 CachedResource* CachedResourceLoader::cachedResource(const URL& resourceURL) const
148 {
149     URL url = MemoryCache::removeFragmentIdentifierIfNeeded(resourceURL);
150     return m_documentResources.get(url).get(); 
151 }
152
153 Frame* CachedResourceLoader::frame() const
154 {
155     return m_documentLoader ? m_documentLoader->frame() : 0;
156 }
157
158 SessionID CachedResourceLoader::sessionID() const
159 {
160     SessionID sessionID = SessionID::defaultSessionID();
161
162     if (Frame* f = frame())
163         sessionID = f->page()->sessionID();
164
165     return sessionID;
166 }
167
168 CachedResourceHandle<CachedImage> CachedResourceLoader::requestImage(CachedResourceRequest& request)
169 {
170     if (Frame* frame = this->frame()) {
171         if (frame->loader().pageDismissalEventBeingDispatched() != FrameLoader::NoDismissal) {
172             URL requestURL = request.resourceRequest().url();
173             if (requestURL.isValid() && canRequest(CachedResource::ImageResource, requestURL, request.options(), request.forPreload()))
174                 PingLoader::loadImage(*frame, requestURL);
175             return nullptr;
176         }
177     }
178     
179     request.setDefer(clientDefersImage(request.resourceRequest().url()) ? CachedResourceRequest::DeferredByClient : CachedResourceRequest::NoDefer);
180     return downcast<CachedImage>(requestResource(CachedResource::ImageResource, request).get());
181 }
182
183 CachedResourceHandle<CachedFont> CachedResourceLoader::requestFont(CachedResourceRequest& request, bool isSVG)
184 {
185     if (isSVG)
186         return downcast<CachedSVGFont>(requestResource(CachedResource::SVGFontResource, request).get());
187     return downcast<CachedFont>(requestResource(CachedResource::FontResource, request).get());
188 }
189
190 #if ENABLE(VIDEO_TRACK)
191 CachedResourceHandle<CachedTextTrack> CachedResourceLoader::requestTextTrack(CachedResourceRequest& request)
192 {
193     return downcast<CachedTextTrack>(requestResource(CachedResource::TextTrackResource, request).get());
194 }
195 #endif
196
197 CachedResourceHandle<CachedCSSStyleSheet> CachedResourceLoader::requestCSSStyleSheet(CachedResourceRequest& request)
198 {
199     return downcast<CachedCSSStyleSheet>(requestResource(CachedResource::CSSStyleSheet, request).get());
200 }
201
202 CachedResourceHandle<CachedCSSStyleSheet> CachedResourceLoader::requestUserCSSStyleSheet(CachedResourceRequest& request)
203 {
204     URL url = MemoryCache::removeFragmentIdentifierIfNeeded(request.resourceRequest().url());
205
206 #if ENABLE(CACHE_PARTITIONING)
207     request.mutableResourceRequest().setDomainForCachePartition(document()->topOrigin()->domainForCachePartition());
208 #endif
209
210     if (CachedResource* existing = memoryCache()->resourceForRequest(request.resourceRequest(), sessionID())) {
211         if (is<CachedCSSStyleSheet>(*existing))
212             return downcast<CachedCSSStyleSheet>(existing);
213         memoryCache()->remove(existing);
214     }
215     if (url.string() != request.resourceRequest().url())
216         request.mutableResourceRequest().setURL(url);
217
218     CachedResourceHandle<CachedCSSStyleSheet> userSheet = new CachedCSSStyleSheet(request.resourceRequest(), request.charset(), sessionID());
219
220     memoryCache()->add(userSheet.get());
221     // FIXME: loadResource calls setOwningCachedResourceLoader() if the resource couldn't be added to cache. Does this function need to call it, too?
222
223     userSheet->load(this, ResourceLoaderOptions(DoNotSendCallbacks, SniffContent, BufferData, AllowStoredCredentials, AskClientForAllCredentials, SkipSecurityCheck, UseDefaultOriginRestrictionsForType, DoNotIncludeCertificateInfo));
224     
225     return userSheet;
226 }
227
228 CachedResourceHandle<CachedScript> CachedResourceLoader::requestScript(CachedResourceRequest& request)
229 {
230     return downcast<CachedScript>(requestResource(CachedResource::Script, request).get());
231 }
232
233 #if ENABLE(XSLT)
234 CachedResourceHandle<CachedXSLStyleSheet> CachedResourceLoader::requestXSLStyleSheet(CachedResourceRequest& request)
235 {
236     return downcast<CachedXSLStyleSheet>(requestResource(CachedResource::XSLStyleSheet, request).get());
237 }
238 #endif
239
240 CachedResourceHandle<CachedSVGDocument> CachedResourceLoader::requestSVGDocument(CachedResourceRequest& request)
241 {
242     return downcast<CachedSVGDocument>(requestResource(CachedResource::SVGDocumentResource, request).get());
243 }
244
245 #if ENABLE(LINK_PREFETCH)
246 CachedResourceHandle<CachedResource> CachedResourceLoader::requestLinkResource(CachedResource::Type type, CachedResourceRequest& request)
247 {
248     ASSERT(frame());
249     ASSERT(type == CachedResource::LinkPrefetch || type == CachedResource::LinkSubresource);
250     return requestResource(type, request);
251 }
252 #endif
253
254 CachedResourceHandle<CachedRawResource> CachedResourceLoader::requestRawResource(CachedResourceRequest& request)
255 {
256     return downcast<CachedRawResource>(requestResource(CachedResource::RawResource, request).get());
257 }
258
259 CachedResourceHandle<CachedRawResource> CachedResourceLoader::requestMainResource(CachedResourceRequest& request)
260 {
261     return downcast<CachedRawResource>(requestResource(CachedResource::MainResource, request).get());
262 }
263
264 bool CachedResourceLoader::checkInsecureContent(CachedResource::Type type, const URL& url) const
265 {
266     switch (type) {
267     case CachedResource::Script:
268 #if ENABLE(XSLT)
269     case CachedResource::XSLStyleSheet:
270 #endif
271     case CachedResource::SVGDocumentResource:
272     case CachedResource::CSSStyleSheet:
273         // These resource can inject script into the current document (Script,
274         // XSL) or exfiltrate the content of the current document (CSS).
275         if (Frame* f = frame())
276             if (!f->loader().mixedContentChecker().canRunInsecureContent(m_document->securityOrigin(), url))
277                 return false;
278         break;
279 #if ENABLE(VIDEO_TRACK)
280     case CachedResource::TextTrackResource:
281 #endif
282     case CachedResource::RawResource:
283     case CachedResource::ImageResource:
284 #if ENABLE(SVG_FONTS)
285     case CachedResource::SVGFontResource:
286 #endif
287     case CachedResource::FontResource: {
288         // These resources can corrupt only the frame's pixels.
289         if (Frame* f = frame()) {
290             Frame& topFrame = f->tree().top();
291             if (!topFrame.loader().mixedContentChecker().canDisplayInsecureContent(topFrame.document()->securityOrigin(), url))
292                 return false;
293         }
294         break;
295     }
296     case CachedResource::MainResource:
297 #if ENABLE(LINK_PREFETCH)
298     case CachedResource::LinkPrefetch:
299     case CachedResource::LinkSubresource:
300         // Prefetch cannot affect the current document.
301 #endif
302         break;
303     }
304     return true;
305 }
306
307 bool CachedResourceLoader::canRequest(CachedResource::Type type, const URL& url, const ResourceLoaderOptions& options, bool forPreload)
308 {
309     if (document() && !document()->securityOrigin()->canDisplay(url)) {
310         if (!forPreload)
311             FrameLoader::reportLocalLoadFailed(frame(), url.stringCenterEllipsizedToLength());
312         LOG(ResourceLoading, "CachedResourceLoader::requestResource URL was not allowed by SecurityOrigin::canDisplay");
313         return 0;
314     }
315
316     // FIXME: Convert this to check the isolated world's Content Security Policy once webkit.org/b/104520 is solved.
317     bool shouldBypassMainWorldContentSecurityPolicy = (frame() && frame()->script().shouldBypassMainWorldContentSecurityPolicy());
318
319     // Some types of resources can be loaded only from the same origin.  Other
320     // types of resources, like Images, Scripts, and CSS, can be loaded from
321     // any URL.
322     switch (type) {
323     case CachedResource::MainResource:
324     case CachedResource::ImageResource:
325     case CachedResource::CSSStyleSheet:
326     case CachedResource::Script:
327 #if ENABLE(SVG_FONTS)
328     case CachedResource::SVGFontResource:
329 #endif
330     case CachedResource::FontResource:
331     case CachedResource::RawResource:
332 #if ENABLE(LINK_PREFETCH)
333     case CachedResource::LinkPrefetch:
334     case CachedResource::LinkSubresource:
335 #endif
336 #if ENABLE(VIDEO_TRACK)
337     case CachedResource::TextTrackResource:
338 #endif
339         if (options.requestOriginPolicy() == RestrictToSameOrigin && !m_document->securityOrigin()->canRequest(url)) {
340             printAccessDeniedMessage(url);
341             return false;
342         }
343         break;
344     case CachedResource::SVGDocumentResource:
345 #if ENABLE(XSLT)
346     case CachedResource::XSLStyleSheet:
347         if (!m_document->securityOrigin()->canRequest(url)) {
348             printAccessDeniedMessage(url);
349             return false;
350         }
351 #endif
352         break;
353     }
354
355     switch (type) {
356 #if ENABLE(XSLT)
357     case CachedResource::XSLStyleSheet:
358         if (!shouldBypassMainWorldContentSecurityPolicy && !m_document->contentSecurityPolicy()->allowScriptFromSource(url))
359             return false;
360         break;
361 #endif
362     case CachedResource::Script:
363         if (!shouldBypassMainWorldContentSecurityPolicy && !m_document->contentSecurityPolicy()->allowScriptFromSource(url))
364             return false;
365         if (frame() && !frame()->settings().isScriptEnabled())
366             return false;
367         break;
368     case CachedResource::CSSStyleSheet:
369         if (!shouldBypassMainWorldContentSecurityPolicy && !m_document->contentSecurityPolicy()->allowStyleFromSource(url))
370             return false;
371         break;
372     case CachedResource::SVGDocumentResource:
373     case CachedResource::ImageResource:
374         if (!shouldBypassMainWorldContentSecurityPolicy && !m_document->contentSecurityPolicy()->allowImageFromSource(url))
375             return false;
376         break;
377 #if ENABLE(SVG_FONTS)
378     case CachedResource::SVGFontResource:
379 #endif
380     case CachedResource::FontResource: {
381         if (!shouldBypassMainWorldContentSecurityPolicy && !m_document->contentSecurityPolicy()->allowFontFromSource(url))
382             return false;
383         break;
384     }
385     case CachedResource::MainResource:
386     case CachedResource::RawResource:
387 #if ENABLE(LINK_PREFETCH)
388     case CachedResource::LinkPrefetch:
389     case CachedResource::LinkSubresource:
390 #endif
391         break;
392 #if ENABLE(VIDEO_TRACK)
393     case CachedResource::TextTrackResource:
394         if (!shouldBypassMainWorldContentSecurityPolicy && !m_document->contentSecurityPolicy()->allowMediaFromSource(url))
395             return false;
396         break;
397 #endif
398     }
399
400     // SVG Images have unique security rules that prevent all subresource requests except for data urls.
401     if (type != CachedResource::MainResource && frame() && frame()->page()) {
402         if (frame()->page()->chrome().client().isSVGImageChromeClient() && !url.protocolIsData())
403             return false;
404     }
405
406     // Last of all, check for insecure content. We do this last so that when
407     // folks block insecure content with a CSP policy, they don't get a warning.
408     // They'll still get a warning in the console about CSP blocking the load.
409
410     // FIXME: Should we consider forPreload here?
411     if (!checkInsecureContent(type, url))
412         return false;
413
414     return true;
415 }
416
417 bool CachedResourceLoader::shouldContinueAfterNotifyingLoadedFromMemoryCache(const CachedResourceRequest& request, CachedResource* resource)
418 {
419     if (!resource || !frame() || resource->status() != CachedResource::Cached)
420         return true;
421
422     ResourceRequest newRequest = ResourceRequest(resource->url());
423 #if ENABLE(INSPECTOR)
424     if (request.resourceRequest().hiddenFromInspector())
425         newRequest.setHiddenFromInspector(true);
426 #else
427     UNUSED_PARAM(request);
428 #endif
429     frame()->loader().loadedResourceFromMemoryCache(resource, newRequest);
430     
431     // FIXME <http://webkit.org/b/113251>: If the delegate modifies the request's
432     // URL, it is no longer appropriate to use this CachedResource.
433     return !newRequest.isNull();
434 }
435
436 CachedResourceHandle<CachedResource> CachedResourceLoader::requestResource(CachedResource::Type type, CachedResourceRequest& request)
437 {
438     URL url = request.resourceRequest().url();
439     
440     LOG(ResourceLoading, "CachedResourceLoader::requestResource '%s', charset '%s', priority=%d, forPreload=%u", url.stringCenterEllipsizedToLength().latin1().data(), request.charset().latin1().data(), request.priority(), request.forPreload());
441     
442     // If only the fragment identifiers differ, it is the same resource.
443     url = MemoryCache::removeFragmentIdentifierIfNeeded(url);
444
445     if (!url.isValid())
446         return 0;
447
448     if (!canRequest(type, url, request.options(), request.forPreload()))
449         return 0;
450
451     if (Frame* f = frame())
452         f->loader().client().dispatchWillRequestResource(&request);
453
454     if (memoryCache()->disabled()) {
455         DocumentResourceMap::iterator it = m_documentResources.find(url.string());
456         if (it != m_documentResources.end()) {
457             it->value->setOwningCachedResourceLoader(0);
458             m_documentResources.remove(it);
459         }
460     }
461
462     // See if we can use an existing resource from the cache.
463     CachedResourceHandle<CachedResource> resource;
464 #if ENABLE(CACHE_PARTITIONING)
465     if (document())
466         request.mutableResourceRequest().setDomainForCachePartition(document()->topOrigin()->domainForCachePartition());
467 #endif
468
469     resource = memoryCache()->resourceForRequest(request.resourceRequest(), sessionID());
470
471     const RevalidationPolicy policy = determineRevalidationPolicy(type, request.mutableResourceRequest(), request.forPreload(), resource.get(), request.defer());
472     switch (policy) {
473     case Reload:
474         memoryCache()->remove(resource.get());
475         FALLTHROUGH;
476     case Load:
477         resource = loadResource(type, request);
478         break;
479     case Revalidate:
480         resource = revalidateResource(request, resource.get());
481         break;
482     case Use:
483         if (!shouldContinueAfterNotifyingLoadedFromMemoryCache(request, resource.get()))
484             return 0;
485         memoryCache()->resourceAccessed(resource.get());
486         break;
487     }
488
489     if (!resource)
490         return 0;
491
492     if (!request.forPreload() || policy != Use)
493         resource->setLoadPriority(request.priority());
494
495     if ((policy != Use || resource->stillNeedsLoad()) && CachedResourceRequest::NoDefer == request.defer()) {
496         resource->load(this, request.options());
497
498         // We don't support immediate loads, but we do support immediate failure.
499         if (resource->errorOccurred()) {
500             if (resource->inCache())
501                 memoryCache()->remove(resource.get());
502             return 0;
503         }
504     }
505
506     if (document() && !document()->loadEventFinished() && !request.resourceRequest().url().protocolIsData())
507         m_validatedURLs.add(request.resourceRequest().url());
508
509     ASSERT(resource->url() == url.string());
510     m_documentResources.set(resource->url(), resource);
511     return resource;
512 }
513
514 void CachedResourceLoader::documentDidFinishLoadEvent()
515 {
516     m_validatedURLs.clear();
517 }
518
519 CachedResourceHandle<CachedResource> CachedResourceLoader::revalidateResource(const CachedResourceRequest& request, CachedResource* resource)
520 {
521     ASSERT(resource);
522     ASSERT(resource->inCache());
523     ASSERT(!memoryCache()->disabled());
524     ASSERT(resource->canUseCacheValidator());
525     ASSERT(!resource->resourceToRevalidate());
526     ASSERT(resource->sessionID() == sessionID());
527
528     // Copy the URL out of the resource to be revalidated in case it gets deleted by the remove() call below.
529     String url = resource->url();
530     CachedResourceHandle<CachedResource> newResource = createResource(resource->type(), resource->resourceRequest(), resource->encoding(), resource->sessionID());
531     
532     LOG(ResourceLoading, "Resource %p created to revalidate %p", newResource.get(), resource);
533     newResource->setResourceToRevalidate(resource);
534     
535     memoryCache()->remove(resource);
536     memoryCache()->add(newResource.get());
537 #if ENABLE(RESOURCE_TIMING)
538     storeResourceTimingInitiatorInformation(resource, request);
539 #else
540     UNUSED_PARAM(request);
541 #endif
542     return newResource;
543 }
544
545 CachedResourceHandle<CachedResource> CachedResourceLoader::loadResource(CachedResource::Type type, CachedResourceRequest& request)
546 {
547     ASSERT(!memoryCache()->resourceForRequest(request.resourceRequest(), sessionID()));
548
549     LOG(ResourceLoading, "Loading CachedResource for '%s'.", request.resourceRequest().url().stringCenterEllipsizedToLength().latin1().data());
550
551     CachedResourceHandle<CachedResource> resource = createResource(type, request.mutableResourceRequest(), request.charset(), sessionID());
552
553     if (!memoryCache()->add(resource.get()))
554         resource->setOwningCachedResourceLoader(this);
555 #if ENABLE(RESOURCE_TIMING)
556     storeResourceTimingInitiatorInformation(resource, request);
557 #endif
558     return resource;
559 }
560
561 #if ENABLE(RESOURCE_TIMING)
562 void CachedResourceLoader::storeResourceTimingInitiatorInformation(const CachedResourceHandle<CachedResource>& resource, const CachedResourceRequest& request)
563 {
564     if (resource->type() == CachedResource::MainResource) {
565         // <iframe>s should report the initial navigation requested by the parent document, but not subsequent navigations.
566         if (frame()->ownerElement() && m_documentLoader->frameLoader()->stateMachine().committingFirstRealLoad()) {
567             InitiatorInfo info = { frame()->ownerElement()->localName(), monotonicallyIncreasingTime() };
568             m_initiatorMap.add(resource.get(), info);
569         }
570     } else {
571         InitiatorInfo info = { request.initiatorName(), monotonicallyIncreasingTime() };
572         m_initiatorMap.add(resource.get(), info);
573     }
574 }
575 #endif // ENABLE(RESOURCE_TIMING)
576
577 CachedResourceLoader::RevalidationPolicy CachedResourceLoader::determineRevalidationPolicy(CachedResource::Type type, ResourceRequest& request, bool forPreload, CachedResource* existingResource, CachedResourceRequest::DeferOption defer) const
578 {
579     if (!existingResource)
580         return Load;
581
582     // We already have a preload going for this URL.
583     if (forPreload && existingResource->isPreloaded())
584         return Use;
585
586     // If the same URL has been loaded as a different type, we need to reload.
587     if (existingResource->type() != type) {
588         LOG(ResourceLoading, "CachedResourceLoader::determineRevalidationPolicy reloading due to type mismatch.");
589         return Reload;
590     }
591
592     if (!existingResource->canReuse(request))
593         return Reload;
594
595     // Conditional requests should have failed canReuse check.
596     ASSERT(!request.isConditional());
597
598     // Do not load from cache if images are not enabled. The load for this image will be blocked
599     // in CachedImage::load.
600     if (CachedResourceRequest::DeferredByClient == defer)
601         return Reload;
602     
603     // Don't reload resources while pasting.
604     if (m_allowStaleResources)
605         return Use;
606     
607     // Alwaus use preloads.
608     if (existingResource->isPreloaded())
609         return Use;
610     
611     // CachePolicyHistoryBuffer uses the cache no matter what.
612     if (cachePolicy(type) == CachePolicyHistoryBuffer)
613         return Use;
614
615     // Don't reuse resources with Cache-control: no-store.
616     if (existingResource->response().cacheControlContainsNoStore()) {
617         LOG(ResourceLoading, "CachedResourceLoader::determineRevalidationPolicy reloading due to Cache-control: no-store.");
618         return Reload;
619     }
620
621     // Validate the redirect chain
622     if (!existingResource->redirectChainAllowsReuse()) {
623         LOG(ResourceLoading, "CachedResourceLoader::determineRevalidationPolicy reloading due to not cached or expired redirections.");
624         return Reload;
625     }
626
627     // If credentials were sent with the previous request and won't be
628     // with this one, or vice versa, re-fetch the resource.
629     //
630     // This helps with the case where the server sends back
631     // "Access-Control-Allow-Origin: *" all the time, but some of the
632     // client's requests are made without CORS and some with.
633     if (existingResource->resourceRequest().allowCookies() != request.allowCookies()) {
634         LOG(ResourceLoading, "CachedResourceLoader::determineRevalidationPolicy reloading due to difference in credentials settings.");
635         return Reload;
636     }
637
638     // During the initial load, avoid loading the same resource multiple times for a single document, even if the cache policies would tell us to.
639     if (document() && !document()->loadEventFinished() && m_validatedURLs.contains(existingResource->url()))
640         return Use;
641
642     // CachePolicyReload always reloads
643     if (cachePolicy(type) == CachePolicyReload) {
644         LOG(ResourceLoading, "CachedResourceLoader::determineRevalidationPolicy reloading due to CachePolicyReload.");
645         return Reload;
646     }
647     
648     // We'll try to reload the resource if it failed last time.
649     if (existingResource->errorOccurred()) {
650         LOG(ResourceLoading, "CachedResourceLoader::determineRevalidationPolicye reloading due to resource being in the error state");
651         return Reload;
652     }
653     
654     // For resources that are not yet loaded we ignore the cache policy.
655     if (existingResource->isLoading())
656         return Use;
657
658     // Check if the cache headers requires us to revalidate (cache expiration for example).
659     if (existingResource->mustRevalidateDueToCacheHeaders(cachePolicy(type))) {
660         // See if the resource has usable ETag or Last-modified headers.
661         if (existingResource->canUseCacheValidator())
662             return Revalidate;
663         
664         // No, must reload.
665         LOG(ResourceLoading, "CachedResourceLoader::determineRevalidationPolicy reloading due to missing cache validators.");            
666         return Reload;
667     }
668
669     return Use;
670 }
671
672 void CachedResourceLoader::printAccessDeniedMessage(const URL& url) const
673 {
674     if (url.isNull())
675         return;
676
677     if (!frame())
678         return;
679
680     String message;
681     if (!m_document || m_document->url().isNull())
682         message = "Unsafe attempt to load URL " + url.stringCenterEllipsizedToLength() + '.';
683     else
684         message = "Unsafe attempt to load URL " + url.stringCenterEllipsizedToLength() + " from frame with URL " + m_document->url().stringCenterEllipsizedToLength() + ". Domains, protocols and ports must match.\n";
685
686     frame()->document()->addConsoleMessage(MessageSource::Security, MessageLevel::Error, message);
687 }
688
689 void CachedResourceLoader::setAutoLoadImages(bool enable)
690 {
691     if (enable == m_autoLoadImages)
692         return;
693
694     m_autoLoadImages = enable;
695
696     if (!m_autoLoadImages)
697         return;
698
699     reloadImagesIfNotDeferred();
700 }
701
702 void CachedResourceLoader::setImagesEnabled(bool enable)
703 {
704     if (enable == m_imagesEnabled)
705         return;
706
707     m_imagesEnabled = enable;
708
709     if (!m_imagesEnabled)
710         return;
711
712     reloadImagesIfNotDeferred();
713 }
714
715 bool CachedResourceLoader::clientDefersImage(const URL&) const
716 {
717     return !m_imagesEnabled;
718 }
719
720 bool CachedResourceLoader::shouldDeferImageLoad(const URL& url) const
721 {
722     return clientDefersImage(url) || !m_autoLoadImages;
723 }
724
725 void CachedResourceLoader::reloadImagesIfNotDeferred()
726 {
727     DocumentResourceMap::iterator end = m_documentResources.end();
728     for (DocumentResourceMap::iterator it = m_documentResources.begin(); it != end; ++it) {
729         CachedResource* resource = it->value.get();
730         if (is<CachedImage>(*resource) && resource->stillNeedsLoad() && !clientDefersImage(resource->url()))
731             downcast<CachedImage>(*resource).load(this, defaultCachedResourceOptions());
732     }
733 }
734
735 CachePolicy CachedResourceLoader::cachePolicy(CachedResource::Type type) const
736 {
737     if (!frame())
738         return CachePolicyVerify;
739
740     if (type != CachedResource::MainResource)
741         return frame()->loader().subresourceCachePolicy();
742     
743     if (frame()->loader().loadType() == FrameLoadType::ReloadFromOrigin || frame()->loader().loadType() == FrameLoadType::Reload)
744         return CachePolicyReload;
745     return CachePolicyVerify;
746 }
747
748 void CachedResourceLoader::removeCachedResource(CachedResource* resource) const
749 {
750 #ifndef NDEBUG
751     DocumentResourceMap::iterator it = m_documentResources.find(resource->url());
752     if (it != m_documentResources.end())
753         ASSERT(it->value.get() == resource);
754 #endif
755     m_documentResources.remove(resource->url());
756 }
757
758 void CachedResourceLoader::loadDone(CachedResource* resource, bool shouldPerformPostLoadActions)
759 {
760     RefPtr<DocumentLoader> protectDocumentLoader(m_documentLoader);
761     RefPtr<Document> protectDocument(m_document);
762
763 #if ENABLE(RESOURCE_TIMING)
764     if (resource && resource->response().isHTTP() && ((!resource->errorOccurred() && !resource->wasCanceled()) || resource->response().httpStatusCode() == 304)) {
765         HashMap<CachedResource*, InitiatorInfo>::iterator initiatorIt = m_initiatorMap.find(resource);
766         if (initiatorIt != m_initiatorMap.end()) {
767             ASSERT(document());
768             Document* initiatorDocument = document();
769             if (resource->type() == CachedResource::MainResource)
770                 initiatorDocument = document()->parentDocument();
771             ASSERT(initiatorDocument);
772             const InitiatorInfo& info = initiatorIt->value;
773             initiatorDocument->domWindow()->performance()->addResourceTiming(info.name, initiatorDocument, resource->resourceRequest(), resource->response(), info.startTime, resource->loadFinishTime());
774             m_initiatorMap.remove(initiatorIt);
775         }
776     }
777 #else
778     UNUSED_PARAM(resource);
779 #endif // ENABLE(RESOURCE_TIMING)
780
781     if (frame())
782         frame()->loader().loadDone();
783
784     if (shouldPerformPostLoadActions)
785         performPostLoadActions();
786
787     if (!m_garbageCollectDocumentResourcesTimer.isActive())
788         m_garbageCollectDocumentResourcesTimer.startOneShot(0);
789 }
790
791 // Garbage collecting m_documentResources is a workaround for the
792 // CachedResourceHandles on the RHS being strong references. Ideally this
793 // would be a weak map, however CachedResourceHandles perform additional
794 // bookkeeping on CachedResources, so instead pseudo-GC them -- when the
795 // reference count reaches 1, m_documentResources is the only reference, so
796 // remove it from the map.
797 void CachedResourceLoader::garbageCollectDocumentResourcesTimerFired(Timer& timer)
798 {
799     ASSERT_UNUSED(timer, &timer == &m_garbageCollectDocumentResourcesTimer);
800     garbageCollectDocumentResources();
801 }
802
803 void CachedResourceLoader::garbageCollectDocumentResources()
804 {
805     typedef Vector<String, 10> StringVector;
806     StringVector resourcesToDelete;
807
808     for (DocumentResourceMap::iterator it = m_documentResources.begin(); it != m_documentResources.end(); ++it) {
809         if (it->value->hasOneHandle()) {
810             resourcesToDelete.append(it->key);
811             it->value->setOwningCachedResourceLoader(0);
812         }
813     }
814
815     for (StringVector::const_iterator it = resourcesToDelete.begin(); it != resourcesToDelete.end(); ++it)
816         m_documentResources.remove(*it);
817 }
818
819 void CachedResourceLoader::performPostLoadActions()
820 {
821     checkForPendingPreloads();
822
823     platformStrategies()->loaderStrategy()->resourceLoadScheduler()->servePendingRequests();
824 }
825
826 void CachedResourceLoader::incrementRequestCount(const CachedResource* res)
827 {
828     if (res->ignoreForRequestCount())
829         return;
830
831     ++m_requestCount;
832 }
833
834 void CachedResourceLoader::decrementRequestCount(const CachedResource* res)
835 {
836     if (res->ignoreForRequestCount())
837         return;
838
839     --m_requestCount;
840     ASSERT(m_requestCount > -1);
841 }
842
843 void CachedResourceLoader::preload(CachedResource::Type type, CachedResourceRequest& request, const String& charset)
844 {
845     // We always preload resources on iOS. See <https://bugs.webkit.org/show_bug.cgi?id=91276>.
846     // FIXME: We should consider adding a setting to toggle aggressive preloading behavior as opposed
847     // to making this behavior specific to iOS.
848 #if !PLATFORM(IOS)
849     bool hasRendering = m_document->body() && m_document->renderView();
850     bool canBlockParser = type == CachedResource::Script || type == CachedResource::CSSStyleSheet;
851     if (!hasRendering && !canBlockParser) {
852         // Don't preload subresources that can't block the parser before we have something to draw.
853         // This helps prevent preloads from delaying first display when bandwidth is limited.
854         PendingPreload pendingPreload = { type, request, charset };
855         m_pendingPreloads.append(pendingPreload);
856         return;
857     }
858 #endif
859     requestPreload(type, request, charset);
860 }
861
862 void CachedResourceLoader::checkForPendingPreloads() 
863 {
864     if (m_pendingPreloads.isEmpty() || !m_document->body() || !m_document->body()->renderer())
865         return;
866 #if PLATFORM(IOS)
867     // We always preload resources on iOS. See <https://bugs.webkit.org/show_bug.cgi?id=91276>.
868     // So, we should never have any pending preloads.
869     // FIXME: We should look to avoid compiling this code entirely when building for iOS.
870     ASSERT_NOT_REACHED();
871 #endif
872     while (!m_pendingPreloads.isEmpty()) {
873         PendingPreload preload = m_pendingPreloads.takeFirst();
874         // Don't request preload if the resource already loaded normally (this will result in double load if the page is being reloaded with cached results ignored).
875         if (!cachedResource(preload.m_request.resourceRequest().url()))
876             requestPreload(preload.m_type, preload.m_request, preload.m_charset);
877     }
878     m_pendingPreloads.clear();
879 }
880
881 void CachedResourceLoader::requestPreload(CachedResource::Type type, CachedResourceRequest& request, const String& charset)
882 {
883     String encoding;
884     if (type == CachedResource::Script || type == CachedResource::CSSStyleSheet)
885         encoding = charset.isEmpty() ? m_document->charset() : charset;
886
887     request.setCharset(encoding);
888     request.setForPreload(true);
889
890     CachedResourceHandle<CachedResource> resource = requestResource(type, request);
891     if (!resource || (m_preloads && m_preloads->contains(resource.get())))
892         return;
893     resource->increasePreloadCount();
894
895     if (!m_preloads)
896         m_preloads = std::make_unique<ListHashSet<CachedResource*>>();
897     m_preloads->add(resource.get());
898
899 #if PRELOAD_DEBUG
900     printf("PRELOADING %s\n",  resource->url().latin1().data());
901 #endif
902 }
903
904 bool CachedResourceLoader::isPreloaded(const String& urlString) const
905 {
906     const URL& url = m_document->completeURL(urlString);
907
908     if (m_preloads) {
909         ListHashSet<CachedResource*>::iterator end = m_preloads->end();
910         for (ListHashSet<CachedResource*>::iterator it = m_preloads->begin(); it != end; ++it) {
911             CachedResource* resource = *it;
912             if (resource->url() == url)
913                 return true;
914         }
915     }
916
917     Deque<PendingPreload>::const_iterator dequeEnd = m_pendingPreloads.end();
918     for (Deque<PendingPreload>::const_iterator it = m_pendingPreloads.begin(); it != dequeEnd; ++it) {
919         PendingPreload pendingPreload = *it;
920         if (pendingPreload.m_request.resourceRequest().url() == url)
921             return true;
922     }
923     return false;
924 }
925
926 void CachedResourceLoader::clearPreloads()
927 {
928 #if PRELOAD_DEBUG
929     printPreloadStats();
930 #endif
931     if (!m_preloads)
932         return;
933
934     ListHashSet<CachedResource*>::iterator end = m_preloads->end();
935     for (ListHashSet<CachedResource*>::iterator it = m_preloads->begin(); it != end; ++it) {
936         CachedResource* res = *it;
937         res->decreasePreloadCount();
938         bool deleted = res->deleteIfPossible();
939         if (!deleted && res->preloadResult() == CachedResource::PreloadNotReferenced)
940             memoryCache()->remove(res);
941     }
942     m_preloads = nullptr;
943 }
944
945 void CachedResourceLoader::clearPendingPreloads()
946 {
947     m_pendingPreloads.clear();
948 }
949
950 #if PRELOAD_DEBUG
951 void CachedResourceLoader::printPreloadStats()
952 {
953     unsigned scripts = 0;
954     unsigned scriptMisses = 0;
955     unsigned stylesheets = 0;
956     unsigned stylesheetMisses = 0;
957     unsigned images = 0;
958     unsigned imageMisses = 0;
959     ListHashSet<CachedResource*>::iterator end = m_preloads.end();
960     for (ListHashSet<CachedResource*>::iterator it = m_preloads.begin(); it != end; ++it) {
961         CachedResource* res = *it;
962         if (res->preloadResult() == CachedResource::PreloadNotReferenced)
963             printf("!! UNREFERENCED PRELOAD %s\n", res->url().latin1().data());
964         else if (res->preloadResult() == CachedResource::PreloadReferencedWhileComplete)
965             printf("HIT COMPLETE PRELOAD %s\n", res->url().latin1().data());
966         else if (res->preloadResult() == CachedResource::PreloadReferencedWhileLoading)
967             printf("HIT LOADING PRELOAD %s\n", res->url().latin1().data());
968         
969         if (res->type() == CachedResource::Script) {
970             scripts++;
971             if (res->preloadResult() < CachedResource::PreloadReferencedWhileLoading)
972                 scriptMisses++;
973         } else if (res->type() == CachedResource::CSSStyleSheet) {
974             stylesheets++;
975             if (res->preloadResult() < CachedResource::PreloadReferencedWhileLoading)
976                 stylesheetMisses++;
977         } else {
978             images++;
979             if (res->preloadResult() < CachedResource::PreloadReferencedWhileLoading)
980                 imageMisses++;
981         }
982         
983         if (res->errorOccurred())
984             memoryCache()->remove(res);
985         
986         res->decreasePreloadCount();
987     }
988     m_preloads = nullptr;
989     
990     if (scripts)
991         printf("SCRIPTS: %d (%d hits, hit rate %d%%)\n", scripts, scripts - scriptMisses, (scripts - scriptMisses) * 100 / scripts);
992     if (stylesheets)
993         printf("STYLESHEETS: %d (%d hits, hit rate %d%%)\n", stylesheets, stylesheets - stylesheetMisses, (stylesheets - stylesheetMisses) * 100 / stylesheets);
994     if (images)
995         printf("IMAGES:  %d (%d hits, hit rate %d%%)\n", images, images - imageMisses, (images - imageMisses) * 100 / images);
996 }
997 #endif
998
999 const ResourceLoaderOptions& CachedResourceLoader::defaultCachedResourceOptions()
1000 {
1001     static ResourceLoaderOptions options(SendCallbacks, SniffContent, BufferData, AllowStoredCredentials, AskClientForAllCredentials, DoSecurityCheck, UseDefaultOriginRestrictionsForType, DoNotIncludeCertificateInfo);
1002     return options;
1003 }
1004
1005 }