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