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