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, 2008 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 "CachedFont.h"
32#include "CachedImage.h"
33#include "CachedResourceRequest.h"
34#include "CachedScript.h"
35#include "CachedXSLStyleSheet.h"
36#include "Console.h"
37#include "ContentSecurityPolicy.h"
38#include "DOMWindow.h"
39#include "Document.h"
40#include "Frame.h"
41#include "FrameLoader.h"
42#include "FrameLoaderClient.h"
43#include "HTMLElement.h"
44#include "Logging.h"
45#include "MemoryCache.h"
46#include "PingLoader.h"
47#include "ResourceLoadScheduler.h"
48#include "SecurityOrigin.h"
49#include "Settings.h"
50#include <wtf/UnusedParam.h>
51#include <wtf/text/CString.h>
52#include <wtf/text/StringConcatenate.h>
53
54#define PRELOAD_DEBUG 0
55
56namespace WebCore {
57
58static CachedResource* createResource(CachedResource::Type type, const KURL& url, const String& charset)
59{
60    switch (type) {
61    case CachedResource::ImageResource:
62        return new CachedImage(url.string());
63    case CachedResource::CSSStyleSheet:
64        return new CachedCSSStyleSheet(url.string(), charset);
65    case CachedResource::Script:
66        return new CachedScript(url.string(), charset);
67    case CachedResource::FontResource:
68        return new CachedFont(url.string());
69#if ENABLE(XSLT)
70    case CachedResource::XSLStyleSheet:
71        return new CachedXSLStyleSheet(url.string());
72#endif
73#if ENABLE(LINK_PREFETCH)
74    case CachedResource::LinkResource:
75        return new CachedResource(url.string(), CachedResource::LinkResource);
76#endif
77    }
78    ASSERT_NOT_REACHED();
79    return 0;
80}
81
82CachedResourceLoader::CachedResourceLoader(Document* document)
83    : m_document(document)
84    , m_requestCount(0)
85    , m_loadDoneActionTimer(this, &CachedResourceLoader::loadDoneActionTimerFired)
86    , m_autoLoadImages(true)
87    , m_loadFinishing(false)
88    , m_allowStaleResources(false)
89#ifdef ANDROID_BLOCK_NETWORK_IMAGE
90    , m_blockNetworkImage(false)
91#endif
92{
93}
94
95CachedResourceLoader::~CachedResourceLoader()
96{
97    m_document = 0;
98
99    cancelRequests();
100    clearPreloads();
101    DocumentResourceMap::iterator end = m_documentResources.end();
102    for (DocumentResourceMap::iterator it = m_documentResources.begin(); it != end; ++it)
103        it->second->setOwningCachedResourceLoader(0);
104
105    // Make sure no requests still point to this CachedResourceLoader
106    ASSERT(m_requestCount == 0);
107}
108
109CachedResource* CachedResourceLoader::cachedResource(const String& resourceURL) const
110{
111    KURL url = m_document->completeURL(resourceURL);
112    return cachedResource(url);
113}
114
115CachedResource* CachedResourceLoader::cachedResource(const KURL& resourceURL) const
116{
117    KURL url = MemoryCache::removeFragmentIdentifierIfNeeded(resourceURL);
118    return m_documentResources.get(url).get();
119}
120
121Frame* CachedResourceLoader::frame() const
122{
123    return m_document ? m_document->frame() : 0;
124}
125
126CachedImage* CachedResourceLoader::requestImage(const String& url)
127{
128    if (Frame* f = frame()) {
129        Settings* settings = f->settings();
130        if (!f->loader()->client()->allowImages(!settings || settings->areImagesEnabled()))
131            return 0;
132
133        if (f->loader()->pageDismissalEventBeingDispatched()) {
134            KURL completeURL = m_document->completeURL(url);
135            if (completeURL.isValid() && canRequest(CachedResource::ImageResource, completeURL))
136                PingLoader::loadImage(f, completeURL);
137            return 0;
138        }
139    }
140    CachedImage* resource = static_cast<CachedImage*>(requestResource(CachedResource::ImageResource, url, String()));
141    if (resource) {
142#ifdef ANDROID_BLOCK_NETWORK_IMAGE
143        resource->setAutoLoadWasPreventedBySettings(!autoLoadImages() || shouldBlockNetworkImage(url));
144#else
145        resource->setAutoLoadWasPreventedBySettings(!autoLoadImages());
146#endif
147        if (autoLoadImages() && resource->stillNeedsLoad()) {
148#ifdef ANDROID_BLOCK_NETWORK_IMAGE
149            if (shouldBlockNetworkImage(url)) {
150                return resource;
151            }
152#endif
153            resource->setLoading(true);
154            load(resource, true);
155        }
156    }
157    return resource;
158}
159
160CachedFont* CachedResourceLoader::requestFont(const String& url)
161{
162    return static_cast<CachedFont*>(requestResource(CachedResource::FontResource, url, String()));
163}
164
165CachedCSSStyleSheet* CachedResourceLoader::requestCSSStyleSheet(const String& url, const String& charset, ResourceLoadPriority priority)
166{
167    return static_cast<CachedCSSStyleSheet*>(requestResource(CachedResource::CSSStyleSheet, url, charset, priority));
168}
169
170CachedCSSStyleSheet* CachedResourceLoader::requestUserCSSStyleSheet(const String& requestURL, const String& charset)
171{
172    KURL url = MemoryCache::removeFragmentIdentifierIfNeeded(KURL(KURL(), requestURL));
173
174    if (CachedResource* existing = memoryCache()->resourceForURL(url)) {
175        if (existing->type() == CachedResource::CSSStyleSheet)
176            return static_cast<CachedCSSStyleSheet*>(existing);
177        memoryCache()->remove(existing);
178    }
179    CachedCSSStyleSheet* userSheet = new CachedCSSStyleSheet(url, charset);
180
181    bool inCache = memoryCache()->add(userSheet);
182    if (!inCache)
183        userSheet->setInCache(true);
184
185    userSheet->load(this, /*incremental*/ false, SkipSecurityCheck, /*sendResourceLoadCallbacks*/ false);
186
187    if (!inCache)
188        userSheet->setInCache(false);
189
190    return userSheet;
191}
192
193CachedScript* CachedResourceLoader::requestScript(const String& url, const String& charset)
194{
195    return static_cast<CachedScript*>(requestResource(CachedResource::Script, url, charset));
196}
197
198#if ENABLE(XSLT)
199CachedXSLStyleSheet* CachedResourceLoader::requestXSLStyleSheet(const String& url)
200{
201    return static_cast<CachedXSLStyleSheet*>(requestResource(CachedResource::XSLStyleSheet, url, String()));
202}
203#endif
204
205#if ENABLE(LINK_PREFETCH)
206CachedResource* CachedResourceLoader::requestLinkResource(const String& url, ResourceLoadPriority priority)
207{
208    ASSERT(frame());
209    return requestResource(CachedResource::LinkResource, url, String(), priority);
210}
211#endif
212
213bool CachedResourceLoader::canRequest(CachedResource::Type type, const KURL& url)
214{
215    // Some types of resources can be loaded only from the same origin.  Other
216    // types of resources, like Images, Scripts, and CSS, can be loaded from
217    // any URL.
218    switch (type) {
219    case CachedResource::ImageResource:
220    case CachedResource::CSSStyleSheet:
221    case CachedResource::Script:
222    case CachedResource::FontResource:
223#if ENABLE(LINK_PREFETCH)
224    case CachedResource::LinkResource:
225#endif
226        // These types of resources can be loaded from any origin.
227        // FIXME: Are we sure about CachedResource::FontResource?
228        break;
229#if ENABLE(XSLT)
230    case CachedResource::XSLStyleSheet:
231        if (!m_document->securityOrigin()->canRequest(url)) {
232            printAccessDeniedMessage(url);
233            return false;
234        }
235        break;
236#endif
237    }
238
239    // Given that the load is allowed by the same-origin policy, we should
240    // check whether the load passes the mixed-content policy.
241    //
242    // Note: Currently, we always allow mixed content, but we generate a
243    //       callback to the FrameLoaderClient in case the embedder wants to
244    //       update any security indicators.
245    //
246    switch (type) {
247    case CachedResource::Script:
248#if ENABLE(XSLT)
249    case CachedResource::XSLStyleSheet:
250#endif
251        // These resource can inject script into the current document.
252        if (Frame* f = frame())
253            f->loader()->checkIfRunInsecureContent(m_document->securityOrigin(), url);
254        break;
255    case CachedResource::ImageResource:
256    case CachedResource::CSSStyleSheet:
257    case CachedResource::FontResource: {
258        // These resources can corrupt only the frame's pixels.
259        if (Frame* f = frame()) {
260            Frame* top = f->tree()->top();
261            top->loader()->checkIfDisplayInsecureContent(top->document()->securityOrigin(), url);
262        }
263        break;
264    }
265#if ENABLE(LINK_PREFETCH)
266    case CachedResource::LinkResource:
267        // Prefetch cannot affect the current document.
268        break;
269#endif
270    }
271    // FIXME: Consider letting the embedder block mixed content loads.
272
273    switch (type) {
274    case CachedResource::Script:
275        if (!m_document->contentSecurityPolicy()->allowScriptFromSource(url))
276            return false;
277        break;
278#if ENABLE(XSLT)
279    case CachedResource::XSLStyleSheet:
280#endif
281    case CachedResource::CSSStyleSheet:
282        if (!m_document->contentSecurityPolicy()->allowStyleFromSource(url))
283            return false;
284        break;
285    case CachedResource::ImageResource:
286        if (!m_document->contentSecurityPolicy()->allowImageFromSource(url))
287            return false;
288        break;
289    case CachedResource::FontResource: {
290        if (!m_document->contentSecurityPolicy()->allowFontFromSource(url))
291            return false;
292        break;
293    }
294#if ENABLE(LINK_PREFETCH)
295    case CachedResource::LinkResource:
296        break;
297#endif
298    }
299
300    return true;
301}
302
303CachedResource* CachedResourceLoader::requestResource(CachedResource::Type type, const String& resourceURL, const String& charset, ResourceLoadPriority priority, bool forPreload)
304{
305    KURL url = m_document->completeURL(resourceURL);
306
307    LOG(ResourceLoading, "CachedResourceLoader::requestResource '%s', charset '%s', priority=%d, forPreload=%u", url.string().latin1().data(), charset.latin1().data(), priority, forPreload);
308
309    // If only the fragment identifiers differ, it is the same resource.
310    url = MemoryCache::removeFragmentIdentifierIfNeeded(url);
311
312    if (!url.isValid())
313        return 0;
314
315    if (!canRequest(type, url))
316        return 0;
317
318    // FIXME: Figure out what is the correct way to merge this security check with the one above.
319    if (!document()->securityOrigin()->canDisplay(url)) {
320        if (!forPreload)
321            FrameLoader::reportLocalLoadFailed(document()->frame(), url.string());
322        LOG(ResourceLoading, "CachedResourceLoader::requestResource URL was not allowed by SecurityOrigin::canDisplay");
323        return 0;
324    }
325
326    if (memoryCache()->disabled()) {
327        DocumentResourceMap::iterator it = m_documentResources.find(url.string());
328        if (it != m_documentResources.end()) {
329            it->second->setOwningCachedResourceLoader(0);
330            m_documentResources.remove(it);
331        }
332    }
333
334    // See if we can use an existing resource from the cache.
335    CachedResource* resource = memoryCache()->resourceForURL(url);
336
337    switch (determineRevalidationPolicy(type, forPreload, resource)) {
338    case Load:
339        resource = loadResource(type, url, charset, priority);
340        break;
341    case Reload:
342        memoryCache()->remove(resource);
343        resource = loadResource(type, url, charset, priority);
344        break;
345    case Revalidate:
346        resource = revalidateResource(resource, priority);
347        break;
348    case Use:
349        memoryCache()->resourceAccessed(resource);
350        notifyLoadedFromMemoryCache(resource);
351        break;
352    }
353
354    if (!resource)
355        return 0;
356
357    ASSERT(resource->url() == url.string());
358    m_documentResources.set(resource->url(), resource);
359
360    return resource;
361}
362
363CachedResource* CachedResourceLoader::revalidateResource(CachedResource* resource, ResourceLoadPriority priority)
364{
365    ASSERT(resource);
366    ASSERT(resource->inCache());
367    ASSERT(!memoryCache()->disabled());
368    ASSERT(resource->canUseCacheValidator());
369    ASSERT(!resource->resourceToRevalidate());
370
371    // Copy the URL out of the resource to be revalidated in case it gets deleted by the remove() call below.
372    String url = resource->url();
373    CachedResource* newResource = createResource(resource->type(), KURL(ParsedURLString, url), resource->encoding());
374
375    LOG(ResourceLoading, "Resource %p created to revalidate %p", newResource, resource);
376    newResource->setResourceToRevalidate(resource);
377
378    memoryCache()->remove(resource);
379    memoryCache()->add(newResource);
380
381    newResource->setLoadPriority(priority);
382    newResource->load(this);
383
384    m_validatedURLs.add(url);
385    return newResource;
386}
387
388CachedResource* CachedResourceLoader::loadResource(CachedResource::Type type, const KURL& url, const String& charset, ResourceLoadPriority priority)
389{
390    ASSERT(!memoryCache()->resourceForURL(url));
391
392    LOG(ResourceLoading, "Loading CachedResource for '%s'.", url.string().latin1().data());
393
394    CachedResource* resource = createResource(type, url, charset);
395
396    bool inCache = memoryCache()->add(resource);
397
398    // Pretend the resource is in the cache, to prevent it from being deleted during the load() call.
399    // FIXME: CachedResource should just use normal refcounting instead.
400    if (!inCache)
401        resource->setInCache(true);
402
403    resource->setLoadPriority(priority);
404    resource->load(this);
405
406    if (!inCache) {
407        resource->setOwningCachedResourceLoader(this);
408        resource->setInCache(false);
409    }
410
411    // We don't support immediate loads, but we do support immediate failure.
412    if (resource->errorOccurred()) {
413        if (inCache)
414            memoryCache()->remove(resource);
415        else
416            delete resource;
417        return 0;
418    }
419
420    m_validatedURLs.add(url.string());
421    return resource;
422}
423
424CachedResourceLoader::RevalidationPolicy CachedResourceLoader::determineRevalidationPolicy(CachedResource::Type type, bool forPreload, CachedResource* existingResource) const
425{
426    if (!existingResource)
427        return Load;
428
429    // We already have a preload going for this URL.
430    if (forPreload && existingResource->isPreloaded())
431        return Use;
432
433    // If the same URL has been loaded as a different type, we need to reload.
434    if (existingResource->type() != type) {
435        LOG(ResourceLoading, "CachedResourceLoader::determineRevalidationPolicy reloading due to type mismatch.");
436        return Reload;
437    }
438
439    // Don't reload resources while pasting.
440    if (m_allowStaleResources)
441        return Use;
442
443    // Alwaus use preloads.
444    if (existingResource->isPreloaded())
445        return Use;
446
447    // CachePolicyHistoryBuffer uses the cache no matter what.
448    if (cachePolicy() == CachePolicyHistoryBuffer)
449        return Use;
450
451    // Don't reuse resources with Cache-control: no-store.
452    if (existingResource->response().cacheControlContainsNoStore()) {
453        LOG(ResourceLoading, "CachedResourceLoader::determineRevalidationPolicy reloading due to Cache-control: no-store.");
454        return Reload;
455    }
456
457    // Avoid loading the same resource multiple times for a single document, even if the cache policies would tell us to.
458    if (m_validatedURLs.contains(existingResource->url()))
459        return Use;
460
461    // CachePolicyReload always reloads
462    if (cachePolicy() == CachePolicyReload) {
463        LOG(ResourceLoading, "CachedResourceLoader::determineRevalidationPolicy reloading due to CachePolicyReload.");
464        return Reload;
465    }
466
467    // We'll try to reload the resource if it failed last time.
468    if (existingResource->errorOccurred()) {
469        LOG(ResourceLoading, "CachedResourceLoader::determineRevalidationPolicye reloading due to resource being in the error state");
470        return Reload;
471    }
472
473    // For resources that are not yet loaded we ignore the cache policy.
474    if (existingResource->isLoading())
475        return Use;
476
477    // Check if the cache headers requires us to revalidate (cache expiration for example).
478    if (existingResource->mustRevalidateDueToCacheHeaders(cachePolicy())) {
479        // See if the resource has usable ETag or Last-modified headers.
480        if (existingResource->canUseCacheValidator())
481            return Revalidate;
482
483        // No, must reload.
484        LOG(ResourceLoading, "CachedResourceLoader::determineRevalidationPolicy reloading due to missing cache validators.");
485        return Reload;
486    }
487
488    return Use;
489}
490
491void CachedResourceLoader::printAccessDeniedMessage(const KURL& url) const
492{
493    if (url.isNull())
494        return;
495
496    if (!frame())
497        return;
498
499    Settings* settings = frame()->settings();
500    if (!settings || settings->privateBrowsingEnabled())
501        return;
502
503    String message = m_document->url().isNull() ?
504        makeString("Unsafe attempt to load URL ", url.string(), '.') :
505        makeString("Unsafe attempt to load URL ", url.string(), " from frame with URL ", m_document->url().string(), ". Domains, protocols and ports must match.\n");
506
507    // FIXME: provide a real line number and source URL.
508    frame()->domWindow()->console()->addMessage(OtherMessageSource, LogMessageType, ErrorMessageLevel, message, 1, String());
509}
510
511void CachedResourceLoader::setAutoLoadImages(bool enable)
512{
513    if (enable == m_autoLoadImages)
514        return;
515
516    m_autoLoadImages = enable;
517
518    if (!m_autoLoadImages)
519        return;
520
521    DocumentResourceMap::iterator end = m_documentResources.end();
522    for (DocumentResourceMap::iterator it = m_documentResources.begin(); it != end; ++it) {
523        CachedResource* resource = it->second.get();
524        if (resource->type() == CachedResource::ImageResource) {
525            CachedImage* image = const_cast<CachedImage*>(static_cast<const CachedImage*>(resource));
526#ifdef ANDROID_BLOCK_NETWORK_IMAGE
527            if (shouldBlockNetworkImage(image->url()))
528                continue;
529#endif
530            image->setAutoLoadWasPreventedBySettings(false);
531
532            if (image->stillNeedsLoad()) {
533                image->setLoading(true);
534                load(image, true);
535            }
536        }
537    }
538}
539
540#ifdef ANDROID_BLOCK_NETWORK_IMAGE
541bool CachedResourceLoader::shouldBlockNetworkImage(const String& url) const
542{
543    if (!m_blockNetworkImage)
544        return false;
545
546    KURL kurl = m_document->completeURL(url);
547    if (kurl.protocolIs("http") || kurl.protocolIs("https"))
548        return true;
549    return false;
550}
551
552void CachedResourceLoader::setBlockNetworkImage(bool block)
553{
554    if (block == m_blockNetworkImage)
555        return;
556
557    m_blockNetworkImage = block;
558
559    if (!m_autoLoadImages || m_blockNetworkImage)
560        return;
561
562    DocumentResourceMap::iterator end = m_documentResources.end();
563    for (DocumentResourceMap::iterator it = m_documentResources.begin(); it != end; ++it) {
564        CachedResource* resource = it->second.get();
565        if (resource->type() == CachedResource::ImageResource) {
566            CachedImage* image = const_cast<CachedImage*>(static_cast<const CachedImage*>(resource));
567            image->setAutoLoadWasPreventedBySettings(false);
568            if (image->stillNeedsLoad()) {
569                image->setLoading(true);
570                load(image, true);
571            }
572        }
573    }
574}
575#endif
576
577CachePolicy CachedResourceLoader::cachePolicy() const
578{
579    return frame() ? frame()->loader()->subresourceCachePolicy() : CachePolicyVerify;
580}
581
582void CachedResourceLoader::removeCachedResource(CachedResource* resource) const
583{
584#ifndef NDEBUG
585    DocumentResourceMap::iterator it = m_documentResources.find(resource->url());
586    if (it != m_documentResources.end())
587        ASSERT(it->second.get() == resource);
588#endif
589    m_documentResources.remove(resource->url());
590}
591
592void CachedResourceLoader::load(CachedResource* resource, bool incremental, SecurityCheckPolicy securityCheck, bool sendResourceLoadCallbacks)
593{
594    incrementRequestCount(resource);
595
596    RefPtr<CachedResourceRequest> request = CachedResourceRequest::load(this, resource, incremental, securityCheck, sendResourceLoadCallbacks);
597    if (request)
598        m_requests.add(request);
599}
600
601void CachedResourceLoader::loadDone(CachedResourceRequest* request)
602{
603    m_loadFinishing = false;
604    RefPtr<CachedResourceRequest> protect(request);
605    if (request)
606        m_requests.remove(request);
607    if (frame())
608        frame()->loader()->loadDone();
609
610    if (!request) {
611        // If the request passed to this function is null, loadDone finished synchronously from when
612        // the load was started, so we want to kick off our next set of loads (via checkForPendingPreloads
613        // and servePendingRequests) asynchronously.
614        m_loadDoneActionTimer.startOneShot(0);
615        return;
616    }
617
618    performPostLoadActions();
619}
620
621void CachedResourceLoader::loadDoneActionTimerFired(Timer<CachedResourceLoader>*)
622{
623    performPostLoadActions();
624}
625
626void CachedResourceLoader::performPostLoadActions()
627{
628    checkForPendingPreloads();
629    resourceLoadScheduler()->servePendingRequests();
630}
631
632void CachedResourceLoader::cancelRequests()
633{
634    clearPendingPreloads();
635    Vector<CachedResourceRequest*, 256> requestsToCancel;
636    RequestSet::iterator end = m_requests.end();
637    for (RequestSet::iterator i = m_requests.begin(); i != end; ++i)
638        requestsToCancel.append((*i).get());
639
640    for (unsigned i = 0; i < requestsToCancel.size(); ++i)
641        requestsToCancel[i]->didFail(true);
642}
643
644void CachedResourceLoader::notifyLoadedFromMemoryCache(CachedResource* resource)
645{
646    if (!resource || !frame() || resource->status() != CachedResource::Cached)
647        return;
648
649    // FIXME: If the WebKit client changes or cancels the request, WebCore does not respect this and continues the load.
650    frame()->loader()->loadedResourceFromMemoryCache(resource);
651}
652
653void CachedResourceLoader::incrementRequestCount(const CachedResource* res)
654{
655    if (res->isLinkResource())
656        return;
657
658    ++m_requestCount;
659}
660
661void CachedResourceLoader::decrementRequestCount(const CachedResource* res)
662{
663    if (res->isLinkResource())
664        return;
665
666    --m_requestCount;
667    ASSERT(m_requestCount > -1);
668}
669
670int CachedResourceLoader::requestCount()
671{
672    if (m_loadFinishing)
673         return m_requestCount + 1;
674    return m_requestCount;
675}
676
677void CachedResourceLoader::preload(CachedResource::Type type, const String& url, const String& charset, bool referencedFromBody)
678{
679    // FIXME: Rip this out when we are sure it is no longer necessary (even for mobile).
680    UNUSED_PARAM(referencedFromBody);
681
682    bool hasRendering = m_document->body() && m_document->body()->renderer();
683    bool canBlockParser = type == CachedResource::Script || type == CachedResource::CSSStyleSheet;
684    if (!hasRendering && !canBlockParser) {
685        // Don't preload subresources that can't block the parser before we have something to draw.
686        // This helps prevent preloads from delaying first display when bandwidth is limited.
687        PendingPreload pendingPreload = { type, url, charset };
688        m_pendingPreloads.append(pendingPreload);
689        return;
690    }
691    requestPreload(type, url, charset);
692}
693
694void CachedResourceLoader::checkForPendingPreloads()
695{
696    if (m_pendingPreloads.isEmpty() || !m_document->body() || !m_document->body()->renderer())
697        return;
698    while (!m_pendingPreloads.isEmpty()) {
699        PendingPreload preload = m_pendingPreloads.takeFirst();
700        // 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).
701        if (!cachedResource(m_document->completeURL(preload.m_url)))
702            requestPreload(preload.m_type, preload.m_url, preload.m_charset);
703    }
704    m_pendingPreloads.clear();
705}
706
707void CachedResourceLoader::requestPreload(CachedResource::Type type, const String& url, const String& charset)
708{
709    String encoding;
710    if (type == CachedResource::Script || type == CachedResource::CSSStyleSheet)
711        encoding = charset.isEmpty() ? m_document->charset() : charset;
712
713    CachedResource* resource = requestResource(type, url, encoding, ResourceLoadPriorityUnresolved, true);
714    if (!resource || (m_preloads && m_preloads->contains(resource)))
715        return;
716    resource->increasePreloadCount();
717
718    if (!m_preloads)
719        m_preloads = adoptPtr(new ListHashSet<CachedResource*>);
720    m_preloads->add(resource);
721
722#if PRELOAD_DEBUG
723    printf("PRELOADING %s\n",  resource->url().latin1().data());
724#endif
725}
726
727void CachedResourceLoader::clearPreloads()
728{
729#if PRELOAD_DEBUG
730    printPreloadStats();
731#endif
732    if (!m_preloads)
733        return;
734
735    ListHashSet<CachedResource*>::iterator end = m_preloads->end();
736    for (ListHashSet<CachedResource*>::iterator it = m_preloads->begin(); it != end; ++it) {
737        CachedResource* res = *it;
738        res->decreasePreloadCount();
739        if (res->canDelete() && !res->inCache())
740            delete res;
741        else if (res->preloadResult() == CachedResource::PreloadNotReferenced)
742            memoryCache()->remove(res);
743    }
744    m_preloads.clear();
745}
746
747void CachedResourceLoader::clearPendingPreloads()
748{
749    m_pendingPreloads.clear();
750}
751
752#if PRELOAD_DEBUG
753void CachedResourceLoader::printPreloadStats()
754{
755    unsigned scripts = 0;
756    unsigned scriptMisses = 0;
757    unsigned stylesheets = 0;
758    unsigned stylesheetMisses = 0;
759    unsigned images = 0;
760    unsigned imageMisses = 0;
761    ListHashSet<CachedResource*>::iterator end = m_preloads.end();
762    for (ListHashSet<CachedResource*>::iterator it = m_preloads.begin(); it != end; ++it) {
763        CachedResource* res = *it;
764        if (res->preloadResult() == CachedResource::PreloadNotReferenced)
765            printf("!! UNREFERENCED PRELOAD %s\n", res->url().latin1().data());
766        else if (res->preloadResult() == CachedResource::PreloadReferencedWhileComplete)
767            printf("HIT COMPLETE PRELOAD %s\n", res->url().latin1().data());
768        else if (res->preloadResult() == CachedResource::PreloadReferencedWhileLoading)
769            printf("HIT LOADING PRELOAD %s\n", res->url().latin1().data());
770
771        if (res->type() == CachedResource::Script) {
772            scripts++;
773            if (res->preloadResult() < CachedResource::PreloadReferencedWhileLoading)
774                scriptMisses++;
775        } else if (res->type() == CachedResource::CSSStyleSheet) {
776            stylesheets++;
777            if (res->preloadResult() < CachedResource::PreloadReferencedWhileLoading)
778                stylesheetMisses++;
779        } else {
780            images++;
781            if (res->preloadResult() < CachedResource::PreloadReferencedWhileLoading)
782                imageMisses++;
783        }
784
785        if (res->errorOccurred())
786            memoryCache()->remove(res);
787
788        res->decreasePreloadCount();
789    }
790    m_preloads.clear();
791
792    if (scripts)
793        printf("SCRIPTS: %d (%d hits, hit rate %d%%)\n", scripts, scripts - scriptMisses, (scripts - scriptMisses) * 100 / scripts);
794    if (stylesheets)
795        printf("STYLESHEETS: %d (%d hits, hit rate %d%%)\n", stylesheets, stylesheets - stylesheetMisses, (stylesheets - stylesheetMisses) * 100 / stylesheets);
796    if (images)
797        printf("IMAGES:  %d (%d hits, hit rate %d%%)\n", images, images - imageMisses, (images - imageMisses) * 100 / images);
798}
799#endif
800
801}
802